diff --git a/src/core/resource.rs b/src/core/resource.rs index c457daf..5c15b21 100644 --- a/src/core/resource.rs +++ b/src/core/resource.rs @@ -1,88 +1,46 @@ -use color_eyre::eyre::eyre; -use serde::{de::Visitor, Deserialize}; +use stardust_xr::values::ResourceID; use std::{ffi::OsStr, path::PathBuf}; +use super::client::Client; + lazy_static::lazy_static! { static ref THEMES: Vec = std::env::var("STARDUST_THEMES").map(|s| s.split(":").map(PathBuf::from).collect()).unwrap_or_default(); } -#[derive(Debug)] -pub enum ResourceID { - File(PathBuf), - Namespaced { namespace: String, path: PathBuf }, -} -impl ResourceID { - pub fn get_file(&self, prefixes: &[PathBuf], extensions: &[&OsStr]) -> Option { - match self { - ResourceID::File(file) => (file.is_absolute() - && file.exists() && Self::has_extension(file, extensions)) - .then_some(file.clone()), - ResourceID::Namespaced { namespace, path } => { - let file_name = path.file_name()?; - THEMES - .iter() - .chain(prefixes.iter()) - .filter_map(|prefix| { - let prefixed_path = prefix.clone().join(namespace).join(path); - let parent = prefixed_path.parent()?; - std::fs::read_dir(parent).ok() - }) - .flatten() - .filter_map(|item| item.ok()) - .map(|dir_entry| dir_entry.path()) - .filter(|path| path.file_stem() == Some(file_name)) - .find(|path| Self::has_extension(path, extensions)) - } - } +fn has_extension(path: &PathBuf, extensions: &[&OsStr]) -> bool { + if let Some(path_extension) = path.extension() { + extensions.contains(&path_extension) + } else { + false } +} - fn has_extension(path: &PathBuf, extensions: &[&OsStr]) -> bool { - if let Some(path_extension) = path.extension() { - extensions.contains(&path_extension) - } else { - false +pub fn get_resource_file( + resource: &ResourceID, + client: &Client, + extensions: &[&OsStr], +) -> Option { + match resource { + ResourceID::Direct(file) => { + (file.is_absolute() && file.exists() && has_extension(file, extensions)) + .then_some(file.clone()) + } + ResourceID::Namespaced { namespace, path } => { + let file_name = path.file_name()?; + let base_prefixes = client.base_resource_prefixes.lock().clone(); + THEMES + .iter() + .chain(base_prefixes.iter()) + .filter_map(|prefix| { + let prefixed_path = prefix.clone().join(namespace).join(path); + let parent = prefixed_path.parent()?; + std::fs::read_dir(parent).ok() + }) + .flatten() + .filter_map(|item| item.ok()) + .map(|dir_entry| dir_entry.path()) + .filter(|path| path.file_stem() == Some(file_name)) + .find(|path| has_extension(path, extensions)) } } } -impl<'de> Deserialize<'de> for ResourceID { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_any(ResourceVisitor) - } -} - -struct ResourceVisitor; -impl<'de> Visitor<'de> for ResourceVisitor { - type Value = ResourceID; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("A string containing an absolute path to file or \"[namespace]:[path]\" for a namespaced resource") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - Ok(if v.starts_with('/') { - let path = PathBuf::from(v); - path.metadata().map_err(serde::de::Error::custom)?; - ResourceID::File(path) - } else if let Some((namespace, path)) = v.split_once(':') { - ResourceID::Namespaced { - namespace: namespace.to_string(), - path: PathBuf::from(path), - } - } else { - return Err(serde::de::Error::custom(eyre!("Invalid format for string"))); - }) - } - - fn visit_string(self, v: String) -> Result - where - E: serde::de::Error, - { - self.visit_str(&v) - } -} diff --git a/src/nodes/audio.rs b/src/nodes/audio.rs index f487e96..0b63806 100644 --- a/src/nodes/audio.rs +++ b/src/nodes/audio.rs @@ -2,7 +2,7 @@ use super::{Message, Node}; use crate::core::client::Client; use crate::core::destroy_queue; use crate::core::registry::Registry; -use crate::core::resource::ResourceID; +use crate::core::resource::get_resource_file; use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial, Transform}; use color_eyre::eyre::{ensure, eyre, Result}; use glam::{vec3, Vec4Swizzles}; @@ -11,6 +11,7 @@ use parking_lot::Mutex; use send_wrapper::SendWrapper; use serde::Deserialize; use stardust_xr::schemas::flex::deserialize; +use stardust_xr::values::ResourceID; use std::ops::DerefMut; use std::{ffi::OsStr, path::PathBuf, sync::Arc}; @@ -35,17 +36,12 @@ impl Sound { node.spatial.get().is_some(), "Internal: Node does not have a spatial attached!" ); - let pending_audio_path = resource_id - .get_file( - &node - .get_client() - .ok_or_else(|| eyre!("Client not found"))? - .base_resource_prefixes - .lock() - .clone(), - &[OsStr::new("wav"), OsStr::new("mp3")], - ) - .ok_or_else(|| eyre!("Resource not found"))?; + let pending_audio_path = get_resource_file( + &resource_id, + &*node.get_client().ok_or_else(|| eyre!("Client not found"))?, + &[OsStr::new("wav"), OsStr::new("mp3")], + ) + .ok_or_else(|| eyre!("Resource not found"))?; let sound = Sound { space: node.spatial.get().unwrap().clone(), volume: 1.0, diff --git a/src/nodes/drawable/lines.rs b/src/nodes/drawable/lines.rs index e16794f..ce3f0d6 100644 --- a/src/nodes/drawable/lines.rs +++ b/src/nodes/drawable/lines.rs @@ -1,53 +1,25 @@ +use super::{Drawable, Line, LinesAspect}; use crate::{ core::{client::Client, registry::Registry}, - nodes::{ - spatial::{find_spatial_parent, parse_transform, Spatial, Transform}, - Message, Node, - }, + nodes::{spatial::Spatial, Node}, }; use color_eyre::eyre::{bail, ensure, Result}; use glam::Vec3A; -use mint::Vector3; use parking_lot::Mutex; use portable_atomic::{AtomicBool, Ordering}; -use prisma::{Flatten, Lerp, Rgba}; -use serde::Deserialize; -use stardust_xr::schemas::flex::deserialize; +use prisma::Lerp; use std::{collections::VecDeque, sync::Arc}; use stereokit::{bounds_grow_to_fit_pt, Bounds, Color128, LinePoint as SkLinePoint, StereoKitDraw}; -use super::Drawable; - static LINES_REGISTRY: Registry = Registry::new(); -#[derive(Debug, Clone, Deserialize)] -struct LinePointRaw { - point: Vector3, - thickness: f32, - color: [f32; 4], -} -#[derive(Debug, Clone, Deserialize)] -struct Line { - points: Vec, - cyclic: bool, -} -impl Line { - fn degamma(&mut self) { - for p in &mut self.points { - p.color[0] = p.color[0].powf(2.2); - p.color[1] = p.color[1].powf(2.2); - p.color[2] = p.color[2].powf(2.2); - } - } -} - pub struct Lines { enabled: Arc, space: Arc, data: Mutex>, } impl Lines { - fn add_to(node: &Arc, lines: Vec) -> Result> { + pub fn add_to(node: &Arc, lines: Vec) -> Result> { ensure!( node.drawable.get().is_none(), "Internal: Node already has a drawable attached!" @@ -63,7 +35,6 @@ impl Lines { bounds = bounds_grow_to_fit_pt(bounds, point.point); } } - bounds }); @@ -72,7 +43,7 @@ impl Lines { space: node.get_aspect("Lines", "spatial", |n| &n.spatial)?.clone(), data: Mutex::new(lines), }); - node.add_local_signal("set_lines", Lines::set_lines_flex); + ::add_node_members(node); let _ = node.drawable.set(Drawable::Lines(lines.clone())); Ok(lines) @@ -88,14 +59,25 @@ impl Lines { .map(|p| SkLinePoint { pt: transform_mat.transform_point3a(Vec3A::from(p.point)).into(), thickness: p.thickness, - color: p.color.map(|c| (c * 255.0) as u8).into(), + color: stereokit::sys::color128::from([ + p.color.c.r, + p.color.c.g, + p.color.c.b, + p.color.a, + ]) + .into(), }) .collect(); if line.cyclic && !points.is_empty() { let first = line.points.first().unwrap(); let last = line.points.last().unwrap(); - let color = - Rgba::from_slice(&first.color).lerp(&Rgba::from_slice(&last.color), 0.5); + + let color = Color128 { + r: first.color.c.r.lerp(&last.color.c.r, 0.5), + g: first.color.c.g.lerp(&last.color.c.g, 0.5), + b: first.color.c.b.lerp(&last.color.c.b, 0.5), + a: first.color.a.lerp(&last.color.a, 0.5), + }; let connect_point = SkLinePoint { pt: transform_mat .transform_point3a( @@ -103,13 +85,7 @@ impl Lines { ) .into(), thickness: (first.thickness + last.thickness) * 0.5, - color: Color128::from([ - color.red(), - color.green(), - color.blue(), - color.alpha(), - ]) - .into(), + color: color.into(), }; points.push_front(connect_point.clone()); points.push_back(connect_point); @@ -117,21 +93,14 @@ impl Lines { draw_ctx.line_add_listv(points.make_contiguous()); } } - - pub fn set_lines_flex( - node: Arc, - _calling_client: Arc, - message: Message, - ) -> Result<()> { - let Some(Drawable::Lines(lines)) = node.drawable.get() else { +} +impl LinesAspect for Lines { + fn set_lines(node: Arc, _calling_client: Arc, lines: Vec) -> Result<()> { + let Some(Drawable::Lines(lines_aspect)) = node.drawable.get() else { bail!("Not a drawable??") }; - let mut new_lines: Vec = deserialize(message.as_ref())?; - for l in &mut new_lines { - l.degamma(); - } - *lines.data.lock() = new_lines; + *lines_aspect.data.lock() = lines; Ok(()) } } @@ -148,26 +117,3 @@ pub fn draw_all(draw_ctx: &impl StereoKitDraw) { } } } - -pub fn create_flex(_node: Arc, calling_client: Arc, message: Message) -> Result<()> { - #[derive(Deserialize)] - struct CreateLinesInfo<'a> { - name: &'a str, - parent_path: &'a str, - transform: Transform, - lines: Vec, - } - let mut info: CreateLinesInfo = deserialize(message.as_ref())?; - let node = Node::create_parent_name(&calling_client, "/drawable/lines", info.name, true); - let parent = find_spatial_parent(&calling_client, info.parent_path)?; - let transform = parse_transform(info.transform, true, true, true); - - for l in &mut info.lines { - l.degamma(); - } - - let node = node.add_to_scenegraph()?; - Spatial::add_to(&node, Some(parent), transform, false)?; - Lines::add_to(&node, info.lines)?; - Ok(()) -} diff --git a/src/nodes/drawable/mod.rs b/src/nodes/drawable/mod.rs index 5c65872..e90eb1e 100644 --- a/src/nodes/drawable/mod.rs +++ b/src/nodes/drawable/mod.rs @@ -9,24 +9,19 @@ use self::{ text::Text, }; -use super::{Message, Node}; -use crate::core::client::Client; -use color_eyre::eyre::Result; +use super::{ + spatial::{get_spatial, Spatial, Transform}, + Node, +}; +use crate::{ + core::{client::Client, resource::get_resource_file}, + create_interface, +}; +use color_eyre::eyre::{self, Result}; use parking_lot::Mutex; -use serde::Deserialize; -use stardust_xr::schemas::flex::deserialize; -use std::{path::PathBuf, sync::Arc}; +use stardust_xr::values::ResourceID; +use std::{ffi::OsStr, path::PathBuf, sync::Arc}; use stereokit::StereoKitDraw; -use tracing::instrument; - -pub fn create_interface(client: &Arc) -> Result<()> { - let node = Node::create_parent_name(client, "", "drawable", false); - node.add_local_signal("create_lines", lines::create_flex); - node.add_local_signal("create_model", model::create_flex); - node.add_local_signal("create_text", text::create_flex); - node.add_local_signal("set_sky_file", set_sky_file_flex); - node.add_to_scenegraph().map(|_| ()) -} pub enum Drawable { Lines(Arc), @@ -35,7 +30,7 @@ pub enum Drawable { Text(Arc), } -#[instrument(level = "debug", skip(sk))] +// #[instrument(level = "debug", skip(sk))] pub fn draw(sk: &impl StereoKitDraw) { lines::draw_all(sk); model::draw_all(sk); @@ -56,25 +51,88 @@ pub fn draw(sk: &impl StereoKitDraw) { static QUEUED_SKYLIGHT: Mutex> = Mutex::new(None); static QUEUED_SKYTEX: Mutex> = Mutex::new(None); -fn set_sky_file_flex( - _node: Arc, - _calling_client: Arc, - message: Message, -) -> Result<()> { - #[derive(Deserialize)] - struct SkyFileInfo { - path: PathBuf, - skytex: Option, - skylight: Option, - } - let info: SkyFileInfo = deserialize(message.as_ref())?; - info.path.metadata()?; - if info.skytex.unwrap_or_default() { - QUEUED_SKYTEX.lock().replace(info.path.clone()); - } - if info.skylight.unwrap_or_default() { - QUEUED_SKYLIGHT.lock().replace(info.path); +stardust_xr_server_codegen::codegen_drawable_protocol!(); +create_interface!(DrawableInterface, DrawableInterfaceAspect, "/drawable"); + +pub struct DrawableInterface; +impl DrawableInterfaceAspect for DrawableInterface { + #[doc = "Set the sky lignt/texture to a given HDRI file."] + fn set_sky( + _node: Arc, + calling_client: Arc, + tex: Option, + light: Option, + ) -> Result<()> { + if let Some(tex) = tex { + let resource_path = get_resource_file(&tex, &calling_client, &[OsStr::new("hdr")]) + .ok_or(eyre::eyre!("Could not find resource"))?; + QUEUED_SKYTEX.lock().replace(resource_path); + } + if let Some(light) = light { + let resource_path = get_resource_file(&light, &calling_client, &[OsStr::new("hdr")]) + .ok_or(eyre::eyre!("Could not find resource"))?; + QUEUED_SKYLIGHT.lock().replace(resource_path); + } + Ok(()) } - Ok(()) + #[doc = "Create a lines node"] + fn create_lines( + _node: Arc, + calling_client: Arc, + name: String, + parent: Arc, + transform: Transform, + lines: Vec, + ) -> Result<()> { + let node = + Node::create_parent_name(&calling_client, Self::CREATE_LINES_PARENT_PATH, &name, true); + let parent = get_spatial(&parent, "Spatial parent")?; + let transform = transform.to_mat4(true, true, true); + + let node = node.add_to_scenegraph()?; + Spatial::add_to(&node, Some(parent), transform, false)?; + Lines::add_to(&node, lines)?; + Ok(()) + } + + #[doc = "Load a GLTF model into a Model node"] + fn load_model( + _node: Arc, + calling_client: Arc, + name: String, + parent: Arc, + transform: Transform, + model: ResourceID, + ) -> Result<()> { + let node = + Node::create_parent_name(&calling_client, Self::LOAD_MODEL_PARENT_PATH, &name, true); + let parent = get_spatial(&parent, "Spatial parent")?; + let transform = transform.to_mat4(true, true, true); + let node = node.add_to_scenegraph()?; + Spatial::add_to(&node, Some(parent), transform, false)?; + Model::add_to(&node, model)?; + Ok(()) + } + + #[doc = "Create a text node"] + fn create_text( + _node: Arc, + calling_client: Arc, + name: String, + parent: Arc, + transform: Transform, + text: String, + style: TextStyle, + ) -> Result<()> { + let node = + Node::create_parent_name(&calling_client, Self::CREATE_TEXT_PARENT_PATH, &name, true); + let parent = get_spatial(&parent, "Spatial parent")?; + let transform = transform.to_mat4(true, true, true); + + let node = node.add_to_scenegraph()?; + Spatial::add_to(&node, Some(parent), transform, false)?; + Text::add_to(&node, text, style)?; + Ok(()) + } } diff --git a/src/nodes/drawable/model.rs b/src/nodes/drawable/model.rs index d7b731c..62cabf1 100644 --- a/src/nodes/drawable/model.rs +++ b/src/nodes/drawable/model.rs @@ -1,21 +1,17 @@ -use super::Node; +use super::{MaterialParameter, ModelAspect, ModelPartAspect, Node}; use crate::core::client::Client; use crate::core::node_collections::LifeLinkedNodeMap; use crate::core::registry::Registry; -use crate::core::resource::ResourceID; +use crate::core::resource::get_resource_file; use crate::nodes::drawable::Drawable; -use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial, Transform}; -use crate::nodes::Message; +use crate::nodes::spatial::Spatial; use crate::SK_MULTITHREAD; use color_eyre::eyre::{bail, ensure, eyre, Result}; -use glam::Mat4; -use mint::{ColumnMatrix4, Vector2, Vector3, Vector4}; use once_cell::sync::OnceCell; use parking_lot::Mutex; use portable_atomic::{AtomicBool, Ordering}; use rustc_hash::FxHashMap; -use serde::Deserialize; -use stardust_xr::schemas::flex::deserialize; +use stardust_xr::values::ResourceID; use std::ffi::OsStr; use std::path::PathBuf; @@ -29,26 +25,6 @@ use stereokit::{ static MODEL_REGISTRY: Registry = Registry::new(); static HOLDOUT_MATERIAL: OnceCell> = OnceCell::new(); -#[derive(Deserialize, Debug)] -#[serde(tag = "t", content = "c")] -pub enum MaterialParameter { - Float(f32), - Vector2(Vector2), - Vector3(Vector3), - Vector4(Vector4), - Color([f32; 4]), - Int(i32), - Int2(Vector2), - Int3(Vector3), - Int4(Vector4), - Bool(bool), - UInt(u32), - UInt2(Vector2), - UInt3(Vector3), - UInt4(Vector4), - Matrix(ColumnMatrix4), - Texture(ResourceID), -} impl MaterialParameter { fn apply_to_material( &self, @@ -58,56 +34,35 @@ impl MaterialParameter { parameter_name: &str, ) { match self { - MaterialParameter::Float(val) => { - sk.material_set_float(material, parameter_name, *val); - } - MaterialParameter::Vector2(val) => { - sk.material_set_vector2(material, parameter_name, *val); - } - MaterialParameter::Vector3(val) => { - sk.material_set_vector3(material, parameter_name, *val); - } - MaterialParameter::Vector4(val) => { - sk.material_set_vector4(material, parameter_name, *val); - } - MaterialParameter::Color(val) => { - sk.material_set_color(material, parameter_name, Color128::from(val.clone())); + MaterialParameter::Bool(val) => { + sk.material_set_bool(material, parameter_name, *val); } MaterialParameter::Int(val) => { sk.material_set_int(material, parameter_name, *val); } - MaterialParameter::Int2(val) => { - sk.material_set_int2(material, parameter_name, val.x, val.y); - } - MaterialParameter::Int3(val) => { - sk.material_set_int3(material, parameter_name, val.x, val.y, val.z); - } - MaterialParameter::Int4(val) => { - sk.material_set_int4(material, parameter_name, val.w, val.x, val.y, val.z); - } - MaterialParameter::Bool(val) => { - sk.material_set_bool(material, parameter_name, *val); - } MaterialParameter::UInt(val) => { sk.material_set_uint(material, parameter_name, *val); } - MaterialParameter::UInt2(val) => { - sk.material_set_uint2(material, parameter_name, val.x, val.y); + MaterialParameter::Float(val) => { + sk.material_set_float(material, parameter_name, *val); } - MaterialParameter::UInt3(val) => { - sk.material_set_uint3(material, parameter_name, val.x, val.y, val.z); + MaterialParameter::Vec2(val) => { + sk.material_set_vector2(material, parameter_name, *val); } - MaterialParameter::UInt4(val) => { - sk.material_set_uint4(material, parameter_name, val.w, val.x, val.y, val.z); + MaterialParameter::Vec3(val) => { + sk.material_set_vector3(material, parameter_name, *val); } - MaterialParameter::Matrix(val) => { - sk.material_set_matrix(material, parameter_name, Mat4::from(*val)); + MaterialParameter::Color(val) => { + sk.material_set_color( + material, + parameter_name, + Color128::new(val.c.r, val.c.g, val.c.b, val.a), + ); } MaterialParameter::Texture(resource) => { - let Some(texture_path) = resource.get_file( - &client.base_resource_prefixes.lock().clone(), - &[OsStr::new("png"), OsStr::new("jpg")], - ) else { + let Some(texture_path) = + get_resource_file(&resource, &client, &[OsStr::new("png"), OsStr::new("jpg")]) + else { return; }; if let Ok(tex) = sk.tex_create_file(texture_path, true, 0) { @@ -230,50 +185,12 @@ impl ModelPart { pending_material_parameters: Mutex::new(FxHashMap::default()), pending_material_replacement: Mutex::new(None), }); - node.add_local_signal( - "set_material_parameter", - ModelPart::set_material_parameter_flex, - ); - node.add_local_signal( - "apply_holdout_material", - ModelPart::apply_holdout_material_flex, - ); + ::add_node_members(&node); let _ = node.drawable.set(Drawable::ModelPart(model_part.clone())); model.parts.add(id, &node); Some(model_part) } - fn apply_holdout_material_flex( - node: Arc, - _calling_client: Arc, - _message: Message, - ) -> Result<()> { - let Some(Drawable::ModelPart(model_part)) = node.drawable.get() else { - bail!("Not a drawable??") - }; - model_part.replace_material(HOLDOUT_MATERIAL.get().unwrap().clone()); - Ok(()) - } - - fn set_material_parameter_flex( - node: Arc, - _calling_client: Arc, - message: Message, - ) -> Result<()> { - let Some(Drawable::ModelPart(model_part)) = node.drawable.get() else { - bail!("Not a drawable??") - }; - - let (name, value): (String, MaterialParameter) = deserialize(message.as_ref())?; - - model_part - .pending_material_parameters - .lock() - .insert(name, value); - - Ok(()) - } - pub fn replace_material(&self, replacement: Arc) { self.pending_material_replacement .lock() @@ -314,6 +231,35 @@ impl ModelPart { ); } } +impl ModelPartAspect for ModelPart { + #[doc = "Set this model part's material to one that cuts a hole in the world. Often used for overlays/passthrough where you want to show the background through an object."] + fn apply_holdout_material(node: Arc, _calling_client: Arc) -> Result<()> { + let Some(Drawable::ModelPart(model_part)) = node.drawable.get() else { + bail!("Not a drawable??") + }; + model_part.replace_material(HOLDOUT_MATERIAL.get().unwrap().clone()); + Ok(()) + } + + #[doc = "Set the material parameter with `parameter_name` to `value`"] + fn set_material_parameter( + node: Arc, + _calling_client: Arc, + parameter_name: String, + value: MaterialParameter, + ) -> Result<()> { + let Some(Drawable::ModelPart(model_part)) = node.drawable.get() else { + bail!("Not a drawable??") + }; + + model_part + .pending_material_parameters + .lock() + .insert(parameter_name, value); + + Ok(()) + } +} pub struct Model { self_ref: Weak, @@ -337,17 +283,12 @@ impl Model { "Internal: Node already has a drawable attached!" ); - let pending_model_path = resource_id - .get_file( - &node - .get_client() - .ok_or_else(|| eyre!("Client not found"))? - .base_resource_prefixes - .lock() - .clone(), - &[OsStr::new("glb"), OsStr::new("gltf")], - ) - .ok_or_else(|| eyre!("Resource not found"))?; + let pending_model_path = get_resource_file( + &resource_id, + &*node.get_client().ok_or_else(|| eyre!("Client not found"))?, + &[OsStr::new("glb"), OsStr::new("gltf")], + ) + .ok_or_else(|| eyre!("Resource not found"))?; let model = Arc::new_cyclic(|self_ref| Model { self_ref: self_ref.clone(), @@ -388,7 +329,7 @@ impl Model { ); } } - +impl ModelAspect for Model {} impl Drop for Model { fn drop(&mut self) { MODEL_REGISTRY.remove(self); @@ -402,21 +343,3 @@ pub fn draw_all(sk: &impl StereoKitDraw) { } } } - -pub fn create_flex(_node: Arc, calling_client: Arc, message: Message) -> Result<()> { - #[derive(Deserialize)] - struct CreateModelInfo<'a> { - name: &'a str, - parent_path: &'a str, - transform: Transform, - resource: ResourceID, - } - let info: CreateModelInfo = deserialize(message.as_ref())?; - let node = Node::create_parent_name(&calling_client, "/drawable/model", info.name, true); - let parent = find_spatial_parent(&calling_client, info.parent_path)?; - let transform = parse_transform(info.transform, true, true, true); - let node = node.add_to_scenegraph()?; - Spatial::add_to(&node, Some(parent), transform, false)?; - Model::add_to(&node, info.resource)?; - Ok(()) -} diff --git a/src/nodes/drawable/text.rs b/src/nodes/drawable/text.rs index e904581..df8adeb 100644 --- a/src/nodes/drawable/text.rs +++ b/src/nodes/drawable/text.rs @@ -1,56 +1,47 @@ use crate::{ - core::{client::Client, destroy_queue, registry::Registry, resource::ResourceID}, - nodes::{ - drawable::Drawable, - spatial::{find_spatial_parent, parse_transform, Spatial, Transform}, - Message, Node, - }, + core::{client::Client, destroy_queue, registry::Registry, resource::get_resource_file}, + nodes::{drawable::Drawable, spatial::Spatial, Node}, }; use color_eyre::eyre::{bail, ensure, eyre, Result}; use glam::{vec3, Mat4, Vec2}; -use mint::Vector2; use once_cell::sync::OnceCell; use parking_lot::Mutex; use portable_atomic::{AtomicBool, Ordering}; -use prisma::{Flatten, Rgba}; -use serde::Deserialize; -use stardust_xr::schemas::flex::deserialize; use std::{ffi::OsStr, path::PathBuf, sync::Arc}; -use stereokit::{named_colors::WHITE, Color128, StereoKitDraw, TextAlign, TextFit, TextStyle}; +use stereokit::{ + named_colors::WHITE, Color128, StereoKitDraw, TextAlign, TextFit, TextStyle as SkTextStyle, +}; + +use super::{TextAspect, TextStyle}; static TEXT_REGISTRY: Registry = Registry::new(); -struct TextData { - text: String, - character_height: f32, - text_align: TextAlign, - bounds: Option, - fit: TextFit, - bounds_align: TextAlign, - color: Rgba, +fn convert_align(x_align: super::XAlign, y_align: super::YAlign) -> TextAlign { + let x_align = match x_align { + super::XAlign::Left => TextAlign::XLeft, + super::XAlign::Center => TextAlign::XCenter, + super::XAlign::Right => TextAlign::XRight, + } as u32; + let y_align = match y_align { + super::YAlign::Top => TextAlign::YTop, + super::YAlign::Center => TextAlign::YCenter, + super::YAlign::Bottom => TextAlign::YBottom, + } as u32; + + unsafe { std::mem::transmute(x_align | y_align) } } pub struct Text { enabled: Arc, space: Arc, font_path: Option, - style: OnceCell, + style: OnceCell, - data: Mutex, + text: Mutex, + data: Mutex, } impl Text { - #[allow(clippy::too_many_arguments)] - pub fn add_to( - node: &Arc, - font_resource_id: Option, - text: String, - character_height: f32, - text_align: TextAlign, - bounds: Option>, - fit: TextFit, - bounds_align: TextAlign, - color: Rgba, - ) -> Result> { + pub fn add_to(node: &Arc, text: String, style: TextStyle) -> Result> { ensure!( node.spatial.get().is_some(), "Internal: Node does not have a spatial attached!" @@ -64,44 +55,34 @@ impl Text { let text = TEXT_REGISTRY.add(Text { enabled: node.enabled.clone(), space: node.spatial.get().unwrap().clone(), - font_path: font_resource_id.and_then(|res| { - res.get_file( - &client.base_resource_prefixes.lock().clone(), - &[OsStr::new("ttf"), OsStr::new("otf")], - ) + font_path: style.font.as_ref().and_then(|res| { + get_resource_file(&res, &client, &[OsStr::new("ttf"), OsStr::new("otf")]) }), style: OnceCell::new(), - data: Mutex::new(TextData { - text, - character_height, - text_align, - bounds: bounds.map(|b| b.into()), - fit, - bounds_align, - color, - }), + text: Mutex::new(text), + data: Mutex::new(style), }); - node.add_local_signal("set_character_height", Text::set_character_height_flex); - node.add_local_signal("set_text", Text::set_text_flex); + ::add_node_members(node); let _ = node.drawable.set(Drawable::Text(text.clone())); Ok(text) } fn draw(&self, sk: &impl StereoKitDraw) { - let style = self - .style - .get_or_try_init(|| -> Result { - let font = self - .font_path - .as_deref() - .and_then(|path| sk.font_create(path).ok()) - .unwrap_or_else(|| sk.font_find("default/font").unwrap()); - Ok(unsafe { sk.text_make_style(font, 1.0, WHITE) }) - }); + let style = + self.style + .get_or_try_init(|| -> Result { + let font = self + .font_path + .as_deref() + .and_then(|path| sk.font_create(path).ok()) + .unwrap_or_else(|| sk.font_find("default/font").unwrap()); + Ok(unsafe { sk.text_make_style(font, 1.0, WHITE) }) + }); if let Ok(style) = style { + let text = self.text.lock(); let data = self.data.lock(); let transform = self.space.global_transform() * Mat4::from_scale(vec3( @@ -109,65 +90,58 @@ impl Text { data.character_height, data.character_height, )); - if let Some(bounds) = data.bounds { + if let Some(bounds) = &data.bounds { sk.text_add_in( - &data.text, + &*text, transform, - bounds / data.character_height, - data.fit, + Vec2::from(bounds.bounds) / data.character_height, + match bounds.fit { + super::TextFit::Wrap => TextFit::Wrap, + super::TextFit::Clip => TextFit::Clip, + super::TextFit::Squeeze => TextFit::Squeeze, + super::TextFit::Exact => TextFit::Exact, + super::TextFit::Overflow => TextFit::Overflow, + }, *style, - data.bounds_align, - data.text_align, + convert_align(bounds.anchor_align_x.clone(), bounds.anchor_align_y.clone()), + convert_align(data.text_align_x.clone(), data.text_align_y.clone()), vec3(0.0, 0.0, 0.0), - Color128::from([ - data.color.red(), - data.color.green(), - data.color.blue(), - data.color.alpha(), - ]), + Color128::from([data.color.c.r, data.color.c.g, data.color.c.b, data.color.a]), ); } else { sk.text_add_at( - &data.text, + &*text, transform, *style, - data.bounds_align, - data.text_align, + TextAlign::Center, + convert_align(data.text_align_x.clone(), data.text_align_y.clone()), vec3(0.0, 0.0, 0.0), - Color128::from([ - data.color.red(), - data.color.green(), - data.color.blue(), - data.color.alpha(), - ]), + Color128::from([data.color.c.r, data.color.c.g, data.color.c.b, data.color.a]), ); } } } - - pub fn set_character_height_flex( +} +impl TextAspect for Text { + fn set_character_height( node: Arc, _calling_client: Arc, - message: Message, + height: f32, ) -> Result<()> { let Some(Drawable::Text(text)) = node.drawable.get() else { bail!("Not a drawable??") }; - text.data.lock().character_height = deserialize(message.as_ref())?; + text.data.lock().character_height = height; Ok(()) } - pub fn set_text_flex( - node: Arc, - _calling_client: Arc, - message: Message, - ) -> Result<()> { - let Some(Drawable::Text(text)) = node.drawable.get() else { + fn set_text(node: Arc, _calling_client: Arc, text: String) -> Result<()> { + let Some(Drawable::Text(text_aspect)) = node.drawable.get() else { bail!("Not a drawable??") }; - text.data.lock().text = deserialize(message.as_ref())?; + *text_aspect.text.lock() = text; Ok(()) } } @@ -187,40 +161,3 @@ pub fn draw_all(sk: &impl StereoKitDraw) { } } } - -pub fn create_flex(_node: Arc, calling_client: Arc, message: Message) -> Result<()> { - #[derive(Deserialize)] - struct CreateTextInfo<'a> { - name: &'a str, - parent_path: &'a str, - transform: Transform, - text: String, - font_resource: Option, - character_height: f32, - text_align: TextAlign, - bounds: Option>, - fit: TextFit, - bounds_align: TextAlign, - color: [f32; 4], - } - let info: CreateTextInfo = deserialize(message.as_ref())?; - let node = Node::create_parent_name(&calling_client, "/drawable/text", info.name, true); - let parent = find_spatial_parent(&calling_client, info.parent_path)?; - let transform = parse_transform(info.transform, true, true, true); - let color = Rgba::from_slice(&info.color); - - let node = node.add_to_scenegraph()?; - Spatial::add_to(&node, Some(parent), transform, false)?; - Text::add_to( - &node, - info.font_resource, - info.text, - info.character_height, - info.text_align, - info.bounds, - info.fit, - info.bounds_align, - color, - )?; - Ok(()) -}