diff --git a/src/main.rs b/src/main.rs index f68230f..9272ba2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod objects; mod wayland; use crate::core::destroy_queue; +use crate::nodes::items::camera; use crate::nodes::{audio, drawable, hmd, input}; use crate::objects::input::eye_pointer::EyePointer; use crate::objects::input::mouse_pointer::MousePointer; @@ -238,6 +239,7 @@ fn main() { let _span = _span.enter(); hmd::frame(sk); + camera::update(sk); #[cfg(feature = "wayland")] wayland.frame_event(sk); destroy_queue::clear(); diff --git a/src/nodes/items/camera.rs b/src/nodes/items/camera.rs new file mode 100644 index 0000000..71aaf2d --- /dev/null +++ b/src/nodes/items/camera.rs @@ -0,0 +1,182 @@ +use super::{Item, ItemType}; +use crate::{ + core::{ + client::{Client, INTERNAL_CLIENT}, + registry::Registry, + }, + nodes::{ + drawable::{model::ModelPart, shaders::UNLIT_SHADER_BYTES, Drawable}, + items::TypeInfo, + spatial::{find_spatial_parent, parse_transform, Spatial}, + Message, Node, + }, +}; +use color_eyre::eyre::{bail, Result}; +use glam::Mat4; +use lazy_static::lazy_static; +use mint::{RowMatrix4, Vector2}; +use nanoid::nanoid; +use once_cell::sync::OnceCell; +use parking_lot::Mutex; +use serde::Deserialize; +use stardust_xr::{ + schemas::flex::{deserialize, serialize}, + values::Transform, +}; +use std::sync::Arc; +use stereokit::{ + Color128, Material, Rect, RenderLayer, StereoKitDraw, Tex, TextureType, Transparency, +}; + +lazy_static! { + pub(super) static ref ITEM_TYPE_INFO_CAMERA: TypeInfo = TypeInfo { + type_name: "camera", + aliased_local_signals: vec!["frame"], + aliased_local_methods: vec![], + aliased_remote_signals: vec![], + ui: Default::default(), + items: Registry::new(), + acceptors: Registry::new(), + }; +} + +struct FrameInfo { + proj_matrix: Mat4, + px_size: Vector2, +} + +pub struct CameraItem { + space: Arc, + frame_info: Mutex, + sk_tex: OnceCell, + sk_mat: OnceCell>, + applied_to: Registry, + apply_to: Registry, +} +impl CameraItem { + pub fn add_to(node: &Arc, proj_matrix: Mat4, px_size: Vector2) { + Item::add_to( + node, + nanoid!(), + &ITEM_TYPE_INFO_CAMERA, + ItemType::Camera(CameraItem { + space: node.spatial.get().unwrap().clone(), + frame_info: Mutex::new(FrameInfo { + proj_matrix, + px_size, + }), + sk_tex: OnceCell::new(), + sk_mat: OnceCell::new(), + applied_to: Registry::new(), + apply_to: Registry::new(), + }), + ); + // node.add_local_method("frame", CameraItem::frame_flex); + node.add_local_signal( + "apply_preview_material", + CameraItem::apply_preview_material_flex, + ); + } + + // fn frame_flex(node: &Node, _calling_client: Arc, message: Message) -> Result { + // let ItemType::Camera(camera) = &node.item.get().unwrap().specialization else { + // return Err(eyre!("Wrong item type?")) + // }; + // Ok(serialize(())?.into()) + // } + + fn apply_preview_material_flex( + node: &Node, + calling_client: Arc, + message: Message, + ) -> Result<()> { + let ItemType::Camera(camera) = &node.item.get().unwrap().specialization else { + bail!("Wrong item type?") + }; + let model_part_node = + calling_client.get_node("Model part", deserialize(&message.data).unwrap())?; + let Drawable::ModelPart(model_part) = model_part_node.get_aspect("Model part", "model part", |n| &n.drawable)? else {bail!("Drawable is not a model node")}; + camera.applied_to.add_raw(model_part); + camera.apply_to.add_raw(model_part); + Ok(()) + } + + pub fn serialize_start_data(&self, id: &str) -> Result { + Ok(serialize(id)?.into()) + } + + pub fn update(&self, sk: &impl StereoKitDraw) { + let frame_info = self.frame_info.lock(); + let sk_tex = self.sk_tex.get_or_init(|| { + sk.tex_gen_color( + Color128::default(), + frame_info.px_size.x as i32, + frame_info.px_size.y as i32, + TextureType::RENDER_TARGET, + stereokit::TextureFormat::RGBA32Linear, + ) + }); + let sk_mat = self.sk_mat.get_or_init(|| { + let shader = sk.shader_create_mem(&UNLIT_SHADER_BYTES).unwrap(); + let mat = sk.material_create(&shader); + sk.material_set_texture(&mat, "diffuse", sk_tex.as_ref()); + sk.material_set_transparency(&mat, Transparency::Blend); + Arc::new(mat) + }); + for model_part in self.apply_to.take_valid_contents() { + model_part.replace_material(sk_mat.clone()) + } + + if !self.applied_to.is_empty() { + sk.render_to( + sk_tex, + frame_info.proj_matrix, + self.space.global_transform(), + RenderLayer::all(), + stereokit::RenderClear::All, + Rect { + x: 0.0, + y: 0.0, + w: 0.0, + h: 0.0, + }, + ); + } + } +} + +pub fn update(sk: &impl StereoKitDraw) { + for camera in ITEM_TYPE_INFO_CAMERA.items.get_valid_contents() { + let ItemType::Camera(camera) = &camera.specialization else {continue}; + camera.update(sk); + } +} + +pub(super) fn create_camera_item_flex( + _node: &Node, + calling_client: Arc, + message: Message, +) -> Result<()> { + #[derive(Deserialize)] + struct CreateCameraItemInfo<'a> { + name: &'a str, + parent_path: &'a str, + transform: Transform, + proj_matrix: RowMatrix4, + px_size: Vector2, + } + let info: CreateCameraItemInfo = deserialize(message.as_ref())?; + let parent_name = format!("/item/{}/item", ITEM_TYPE_INFO_CAMERA.type_name); + let space = find_spatial_parent(&calling_client, info.parent_path)?; + let transform = parse_transform(info.transform, true, true, false); + + let node = + Node::create(&INTERNAL_CLIENT, &parent_name, info.name, false).add_to_scenegraph()?; + Spatial::add_to(&node, None, transform * space.global_transform(), false)?; + CameraItem::add_to(&node, info.proj_matrix.into(), info.px_size); + node.item + .get() + .unwrap() + .make_alias_named(&calling_client, &parent_name, info.name)?; + Ok(()) +} diff --git a/src/nodes/items/mod.rs b/src/nodes/items/mod.rs index 09ae37a..0911208 100644 --- a/src/nodes/items/mod.rs +++ b/src/nodes/items/mod.rs @@ -1,6 +1,8 @@ +pub mod camera; mod environment; pub mod panel; +use self::camera::CameraItem; use self::environment::{EnvironmentItem, ITEM_TYPE_INFO_ENVIRONMENT}; use self::panel::{PanelItemTrait, ITEM_TYPE_INFO_PANEL}; use super::fields::Field; @@ -171,12 +173,14 @@ impl Drop for Item { } pub enum ItemType { + Camera(CameraItem), Environment(EnvironmentItem), Panel(Arc), } impl ItemType { fn serialize_start_data(&self, id: &str) -> Result { match self { + ItemType::Camera(c) => c.serialize_start_data(id), ItemType::Environment(e) => e.serialize_start_data(id), ItemType::Panel(p) => p.serialize_start_data(id), } @@ -384,6 +388,7 @@ impl Drop for ItemAcceptor { pub fn create_interface(client: &Arc) -> Result<()> { let node = Node::create(client, "", "item", false); + node.add_local_signal("create_camera_item", camera::create_camera_item_flex); node.add_local_signal( "create_environment_item", environment::create_environment_item_flex,