use super::session::Mailbox;
use crate::initialize::create_sub_agent_dir;
use crate::session::SendUIMessage;
use crate::transport::http_handler::{download_object, download_scene_group, download_texture};
use actix::{AsyncContext, Handler, Message, WrapFuture};
use glam::{Quat, Vec3};
use log::{error, info, warn};
use metaverse_agent::avatar::Avatar;
use metaverse_agent::avatar::OutfitObject;
use metaverse_agent::skeleton::update_global_avatar_skeleton;
use metaverse_inventory::agent::get_current_outfit;
use metaverse_mesh::generate::generate_skinned_mesh;
use metaverse_messages::http::capabilities::Capability;
use metaverse_messages::packet::message::UIMessage;
use metaverse_messages::udp::agent::avatar_appearance::AvatarAppearance;
use metaverse_messages::ui::camera_position::CameraPosition;
use metaverse_messages::ui::mesh_update::{MeshType, MeshUpdate};
use metaverse_messages::utils::object_types::ObjectType;
use metaverse_messages::utils::render_data::{AvatarObject, RenderObject};
use serde::Serialize;
use std::fs::{self, File};
use std::io::{self, Write};
use std::path::PathBuf;
use std::time::Duration;
use uuid::Uuid;
#[derive(Debug, Message)]
#[rtype(result = "()")]
pub struct DownloadAgentAsset {
pub asset_id: Uuid,
pub agent_id: Uuid,
pub item_type: ObjectType,
}
#[derive(Debug, Message)]
#[rtype(result = "()")]
pub struct AddObjectToAvatar {
pub agent_id: Uuid,
pub object: OutfitObject,
}
#[derive(Debug, Message)]
#[rtype(result = "()")]
pub struct SetOutfitSize {
pub agent_id: Uuid,
pub outfit_size: usize,
}
#[derive(Debug, Message)]
#[rtype(result = "()")]
pub struct FinalizeAvatar {
pub avatar: Avatar,
}
#[derive(Debug, Message)]
#[rtype(result = "()")]
pub struct HandleNewAvatar {
pub avatar: Avatar,
}
#[derive(Debug, Message)]
#[rtype(result = "()")]
pub struct HandleNewAvatarAppearance {
pub avatar_appearance: AvatarAppearance,
}
impl Handler<HandleNewAvatarAppearance> for Mailbox {
type Result = ();
fn handle(
&mut self,
_msg: HandleNewAvatarAppearance,
_ctx: &mut Self::Context,
) -> Self::Result {
warn!("AvatarAppearance packet received. Currently unimplemented");
}
}
impl Handler<SetOutfitSize> for Mailbox {
type Result = ();
fn handle(&mut self, msg: SetOutfitSize, _ctx: &mut Self::Context) -> Self::Result {
if let Some(session) = self.session.as_mut()
&& let Some(agent) = session.avatars.get_mut(&msg.agent_id) {
agent.outfit_size = msg.outfit_size;
}
}
}
impl Handler<AddObjectToAvatar> for Mailbox {
type Result = ();
fn handle(&mut self, msg: AddObjectToAvatar, ctx: &mut Self::Context) -> Self::Result {
if let Some(session) = self.session.as_mut() {
if let Some(avatar) = session.avatars.get_mut(&msg.agent_id) {
match &msg.object {
OutfitObject::MeshObject(path) => {
let json_str = fs::read_to_string(&path)
.unwrap_or_else(|_| panic!("Failed to read {:?}", path));
let parts: Vec<RenderObject> = serde_json::from_str(&json_str).unwrap();
if let Some(skin) = &parts[0].skin {
update_global_avatar_skeleton(avatar, &skin.skeleton);
}
}
_ => {
}
}
avatar.items.push(msg.object);
if avatar.items.len() == avatar.outfit_size {
ctx.address().do_send(FinalizeAvatar {
avatar: avatar.clone(),
});
}
} else {
warn!("Agent not found for agent_id {:?}", &msg.agent_id);
}
}
}
}
impl Handler<FinalizeAvatar> for Mailbox {
type Result = ();
fn handle(&mut self, msg: FinalizeAvatar, ctx: &mut Self::Context) -> Self::Result {
let agent_dir = match create_sub_agent_dir(&msg.avatar.agent_id.to_string()) {
Ok(dir) => dir,
Err(e) => {
warn!("Failed to create base agent directory: {:?}", e);
return;
}
};
let json_paths: Vec<PathBuf> = msg
.avatar
.items
.iter()
.filter_map(|item| {
if let OutfitObject::MeshObject(path) = item {
Some(path.clone())
} else {
None
}
})
.collect();
let avatar = AvatarObject {
objects: json_paths,
global_skeleton: msg.avatar.skeleton,
};
let json_path = write_json(
&avatar,
msg.avatar.agent_id,
msg.avatar.agent_id.to_string(),
)
.unwrap();
let glb_path = agent_dir.join(format!("{:?}_high.glb", msg.avatar.agent_id));
match generate_skinned_mesh(json_path, glb_path.clone()) {
Ok(_) => {
info!(
"Rendering avatar {:?} at {:?}",
msg.avatar.agent_id, msg.avatar.position
)
}
Err(e) => warn!("{:?}", e),
};
ctx.address().do_send(SendUIMessage {
ui_message: UIMessage::new_mesh_update(MeshUpdate {
position: msg.avatar.position,
scale: Vec3::ONE,
rotation: Quat::IDENTITY,
parent: None,
scene_id: None,
path: glb_path,
mesh_type: MeshType::Avatar,
id: Some(msg.avatar.agent_id),
}),
});
}
}
impl Handler<DownloadAgentAsset> for Mailbox {
type Result = ();
fn handle(&mut self, msg: DownloadAgentAsset, ctx: &mut Self::Context) -> Self::Result {
if let Some(session) = self.session.as_mut() {
let server_endpoint = session
.capability_urls
.get(&Capability::ViewerAsset)
.unwrap()
.to_string();
let addr = ctx.address();
ctx.spawn(
async move {
match download_object(msg.item_type.to_string(), msg.asset_id, &server_endpoint)
.await
{
Ok(scene_group) => {
let base_dir = match create_sub_agent_dir(&msg.agent_id.to_string()) {
Ok(base_dir) => base_dir,
Err(e) => {
error!("failed to create base dir: {:?}", e);
return;
}
};
let texture_id = scene_group.parts[0].shape.texture.texture_id;
let texture_path = base_dir.join(format!("{:?}.png", texture_id));
let texture_path = match download_texture(
ObjectType::Texture.to_string(),
texture_id,
&server_endpoint,
&texture_path,
)
.await
{
Ok(_) => texture_path,
Err(e) => {
error!("Failed to download texture: {:?}", e);
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("assets")
.join("benthic_default_texture.png")
}
};
let render_objects = match download_scene_group(
&scene_group,
&server_endpoint,
&texture_path,
)
.await
{
Ok(objects) => objects,
Err(e) => {
error!("{:?}", e);
return;
}
};
let json_path = format!(
"{:?}_{}",
scene_group.parts[0].sculpt.texture,
scene_group.parts[0].metadata.name
);
let json = match write_json(&render_objects, msg.agent_id, json_path) {
Ok(json) => json,
Err(e) => {
error!("Failed to write json: {:?}", e);
return;
}
};
addr.do_send(AddObjectToAvatar {
object: OutfitObject::MeshObject(json),
agent_id: msg.agent_id,
});
}
Err(e) => {
error!("{:?}, {:?}", e, msg);
}
}
}
.into_actor(self),
);
}
}
}
impl Handler<HandleNewAvatar> for Mailbox {
type Result = ();
fn handle(&mut self, msg: HandleNewAvatar, ctx: &mut Self::Context) -> Self::Result {
if let Some(session) = self.session.as_mut() {
let addr = ctx.address();
if session.agent_id == msg.avatar.agent_id {
addr.do_send(SendUIMessage {
ui_message: UIMessage::new_camera_position(CameraPosition {
position: msg.avatar.position,
}),
});
if session.inventory_data.inventory_init {
session.avatars.insert(msg.avatar.agent_id, msg.avatar);
let agent_id = session.agent_id;
let db_conn = self.inventory_db_connection.clone();
ctx.spawn(
async move {
match get_current_outfit(&db_conn).await {
Ok(outfit) => {
if let Err(err) = addr
.send(SetOutfitSize {
agent_id,
outfit_size: outfit.len(),
})
.await
{
error!(
"Failed to set outfit size for {:?}: {:?}",
agent_id, err
);
return;
};
for item in outfit {
match item.2 {
ObjectType::Object => {
addr.do_send(DownloadAgentAsset {
asset_id: item.1,
item_type: item.2,
agent_id,
});
}
ObjectType::Bodypart => {
addr.do_send(AddObjectToAvatar {
object: OutfitObject::Bodypart,
agent_id,
});
}
ObjectType::Clothing => {
addr.do_send(AddObjectToAvatar {
object: OutfitObject::Clothing,
agent_id,
});
}
_ => {
addr.do_send(AddObjectToAvatar {
object: OutfitObject::Other,
agent_id,
});
}
}
}
}
Err(e) => {
error!("Failed to retrieve inventory {:?}", e);
}
};
}
.into_actor(self),
);
} else {
warn!("Inventory not yet ready. Requeueing avatar download...");
ctx.notify_later(msg, Duration::from_secs(1));
}
} else {
warn!("Non-user avatar updates not yet supported...");
}
}
}
}
fn write_json<T: Serialize>(data: &T, agent_id: Uuid, filename: String) -> io::Result<PathBuf> {
match create_sub_agent_dir(&agent_id.to_string()) {
Ok(mut agent_dir) => match serde_json::to_string(&data) {
Ok(json) => {
agent_dir.push(format!("{}.json", filename));
let mut file = File::create(&agent_dir).unwrap();
file.write_all(json.as_bytes()).unwrap();
Ok(agent_dir)
}
Err(e) => {
error!("Failed to serialize scene group {:?}, {:?}", filename, e);
Err(io::Error::other(e))
}
},
Err(e) => {
error!(
"Failed to create agent dir for {:?}. Unable to cache downloaded items.",
e
);
Err(io::Error::other(e))
}
}
}