refactor(drawable): use idl

This commit is contained in:
Nova
2024-02-04 17:36:30 -05:00
parent dfaaa2a3a9
commit 43b3499ed7
6 changed files with 285 additions and 467 deletions

View File

@@ -1,88 +1,46 @@
use color_eyre::eyre::eyre; use stardust_xr::values::ResourceID;
use serde::{de::Visitor, Deserialize};
use std::{ffi::OsStr, path::PathBuf}; use std::{ffi::OsStr, path::PathBuf};
use super::client::Client;
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref THEMES: Vec<PathBuf> = std::env::var("STARDUST_THEMES").map(|s| s.split(":").map(PathBuf::from).collect()).unwrap_or_default(); static ref THEMES: Vec<PathBuf> = std::env::var("STARDUST_THEMES").map(|s| s.split(":").map(PathBuf::from).collect()).unwrap_or_default();
} }
#[derive(Debug)] fn has_extension(path: &PathBuf, extensions: &[&OsStr]) -> bool {
pub enum ResourceID { if let Some(path_extension) = path.extension() {
File(PathBuf), extensions.contains(&path_extension)
Namespaced { namespace: String, path: PathBuf }, } else {
} false
impl ResourceID {
pub fn get_file(&self, prefixes: &[PathBuf], extensions: &[&OsStr]) -> Option<PathBuf> {
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 { pub fn get_resource_file(
if let Some(path_extension) = path.extension() { resource: &ResourceID,
extensions.contains(&path_extension) client: &Client,
} else { extensions: &[&OsStr],
false ) -> Option<PathBuf> {
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<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, v: &str) -> Result<Self::Value, E>
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<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_str(&v)
}
}

View File

@@ -2,7 +2,7 @@ use super::{Message, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::destroy_queue; use crate::core::destroy_queue;
use crate::core::registry::Registry; 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 crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial, Transform};
use color_eyre::eyre::{ensure, eyre, Result}; use color_eyre::eyre::{ensure, eyre, Result};
use glam::{vec3, Vec4Swizzles}; use glam::{vec3, Vec4Swizzles};
@@ -11,6 +11,7 @@ use parking_lot::Mutex;
use send_wrapper::SendWrapper; use send_wrapper::SendWrapper;
use serde::Deserialize; use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize; use stardust_xr::schemas::flex::deserialize;
use stardust_xr::values::ResourceID;
use std::ops::DerefMut; use std::ops::DerefMut;
use std::{ffi::OsStr, path::PathBuf, sync::Arc}; use std::{ffi::OsStr, path::PathBuf, sync::Arc};
@@ -35,17 +36,12 @@ impl Sound {
node.spatial.get().is_some(), node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!" "Internal: Node does not have a spatial attached!"
); );
let pending_audio_path = resource_id let pending_audio_path = get_resource_file(
.get_file( &resource_id,
&node &*node.get_client().ok_or_else(|| eyre!("Client not found"))?,
.get_client() &[OsStr::new("wav"), OsStr::new("mp3")],
.ok_or_else(|| eyre!("Client not found"))? )
.base_resource_prefixes .ok_or_else(|| eyre!("Resource not found"))?;
.lock()
.clone(),
&[OsStr::new("wav"), OsStr::new("mp3")],
)
.ok_or_else(|| eyre!("Resource not found"))?;
let sound = Sound { let sound = Sound {
space: node.spatial.get().unwrap().clone(), space: node.spatial.get().unwrap().clone(),
volume: 1.0, volume: 1.0,

View File

@@ -1,53 +1,25 @@
use super::{Drawable, Line, LinesAspect};
use crate::{ use crate::{
core::{client::Client, registry::Registry}, core::{client::Client, registry::Registry},
nodes::{ nodes::{spatial::Spatial, Node},
spatial::{find_spatial_parent, parse_transform, Spatial, Transform},
Message, Node,
},
}; };
use color_eyre::eyre::{bail, ensure, Result}; use color_eyre::eyre::{bail, ensure, Result};
use glam::Vec3A; use glam::Vec3A;
use mint::Vector3;
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering}; use portable_atomic::{AtomicBool, Ordering};
use prisma::{Flatten, Lerp, Rgba}; use prisma::Lerp;
use serde::Deserialize;
use stardust_xr::schemas::flex::deserialize;
use std::{collections::VecDeque, sync::Arc}; use std::{collections::VecDeque, sync::Arc};
use stereokit::{bounds_grow_to_fit_pt, Bounds, Color128, LinePoint as SkLinePoint, StereoKitDraw}; use stereokit::{bounds_grow_to_fit_pt, Bounds, Color128, LinePoint as SkLinePoint, StereoKitDraw};
use super::Drawable;
static LINES_REGISTRY: Registry<Lines> = Registry::new(); static LINES_REGISTRY: Registry<Lines> = Registry::new();
#[derive(Debug, Clone, Deserialize)]
struct LinePointRaw {
point: Vector3<f32>,
thickness: f32,
color: [f32; 4],
}
#[derive(Debug, Clone, Deserialize)]
struct Line {
points: Vec<LinePointRaw>,
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 { pub struct Lines {
enabled: Arc<AtomicBool>, enabled: Arc<AtomicBool>,
space: Arc<Spatial>, space: Arc<Spatial>,
data: Mutex<Vec<Line>>, data: Mutex<Vec<Line>>,
} }
impl Lines { impl Lines {
fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> { pub fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> {
ensure!( ensure!(
node.drawable.get().is_none(), node.drawable.get().is_none(),
"Internal: Node already has a drawable attached!" "Internal: Node already has a drawable attached!"
@@ -63,7 +35,6 @@ impl Lines {
bounds = bounds_grow_to_fit_pt(bounds, point.point); bounds = bounds_grow_to_fit_pt(bounds, point.point);
} }
} }
bounds bounds
}); });
@@ -72,7 +43,7 @@ impl Lines {
space: node.get_aspect("Lines", "spatial", |n| &n.spatial)?.clone(), space: node.get_aspect("Lines", "spatial", |n| &n.spatial)?.clone(),
data: Mutex::new(lines), data: Mutex::new(lines),
}); });
node.add_local_signal("set_lines", Lines::set_lines_flex); <Lines as LinesAspect>::add_node_members(node);
let _ = node.drawable.set(Drawable::Lines(lines.clone())); let _ = node.drawable.set(Drawable::Lines(lines.clone()));
Ok(lines) Ok(lines)
@@ -88,14 +59,25 @@ impl Lines {
.map(|p| SkLinePoint { .map(|p| SkLinePoint {
pt: transform_mat.transform_point3a(Vec3A::from(p.point)).into(), pt: transform_mat.transform_point3a(Vec3A::from(p.point)).into(),
thickness: p.thickness, 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(); .collect();
if line.cyclic && !points.is_empty() { if line.cyclic && !points.is_empty() {
let first = line.points.first().unwrap(); let first = line.points.first().unwrap();
let last = line.points.last().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 { let connect_point = SkLinePoint {
pt: transform_mat pt: transform_mat
.transform_point3a( .transform_point3a(
@@ -103,13 +85,7 @@ impl Lines {
) )
.into(), .into(),
thickness: (first.thickness + last.thickness) * 0.5, thickness: (first.thickness + last.thickness) * 0.5,
color: Color128::from([ color: color.into(),
color.red(),
color.green(),
color.blue(),
color.alpha(),
])
.into(),
}; };
points.push_front(connect_point.clone()); points.push_front(connect_point.clone());
points.push_back(connect_point); points.push_back(connect_point);
@@ -117,21 +93,14 @@ impl Lines {
draw_ctx.line_add_listv(points.make_contiguous()); draw_ctx.line_add_listv(points.make_contiguous());
} }
} }
}
pub fn set_lines_flex( impl LinesAspect for Lines {
node: Arc<Node>, fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> {
_calling_client: Arc<Client>, let Some(Drawable::Lines(lines_aspect)) = node.drawable.get() else {
message: Message,
) -> Result<()> {
let Some(Drawable::Lines(lines)) = node.drawable.get() else {
bail!("Not a drawable??") bail!("Not a drawable??")
}; };
let mut new_lines: Vec<Line> = deserialize(message.as_ref())?; *lines_aspect.data.lock() = lines;
for l in &mut new_lines {
l.degamma();
}
*lines.data.lock() = new_lines;
Ok(()) Ok(())
} }
} }
@@ -148,26 +117,3 @@ pub fn draw_all(draw_ctx: &impl StereoKitDraw) {
} }
} }
} }
pub fn create_flex(_node: Arc<Node>, calling_client: Arc<Client>, message: Message) -> Result<()> {
#[derive(Deserialize)]
struct CreateLinesInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
lines: Vec<Line>,
}
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(())
}

View File

@@ -9,24 +9,19 @@ use self::{
text::Text, text::Text,
}; };
use super::{Message, Node}; use super::{
use crate::core::client::Client; spatial::{get_spatial, Spatial, Transform},
use color_eyre::eyre::Result; Node,
};
use crate::{
core::{client::Client, resource::get_resource_file},
create_interface,
};
use color_eyre::eyre::{self, Result};
use parking_lot::Mutex; use parking_lot::Mutex;
use serde::Deserialize; use stardust_xr::values::ResourceID;
use stardust_xr::schemas::flex::deserialize; use std::{ffi::OsStr, path::PathBuf, sync::Arc};
use std::{path::PathBuf, sync::Arc};
use stereokit::StereoKitDraw; use stereokit::StereoKitDraw;
use tracing::instrument;
pub fn create_interface(client: &Arc<Client>) -> 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 { pub enum Drawable {
Lines(Arc<Lines>), Lines(Arc<Lines>),
@@ -35,7 +30,7 @@ pub enum Drawable {
Text(Arc<Text>), Text(Arc<Text>),
} }
#[instrument(level = "debug", skip(sk))] // #[instrument(level = "debug", skip(sk))]
pub fn draw(sk: &impl StereoKitDraw) { pub fn draw(sk: &impl StereoKitDraw) {
lines::draw_all(sk); lines::draw_all(sk);
model::draw_all(sk); model::draw_all(sk);
@@ -56,25 +51,88 @@ pub fn draw(sk: &impl StereoKitDraw) {
static QUEUED_SKYLIGHT: Mutex<Option<PathBuf>> = Mutex::new(None); static QUEUED_SKYLIGHT: Mutex<Option<PathBuf>> = Mutex::new(None);
static QUEUED_SKYTEX: Mutex<Option<PathBuf>> = Mutex::new(None); static QUEUED_SKYTEX: Mutex<Option<PathBuf>> = Mutex::new(None);
fn set_sky_file_flex( stardust_xr_server_codegen::codegen_drawable_protocol!();
_node: Arc<Node>, create_interface!(DrawableInterface, DrawableInterfaceAspect, "/drawable");
_calling_client: Arc<Client>,
message: Message, pub struct DrawableInterface;
) -> Result<()> { impl DrawableInterfaceAspect for DrawableInterface {
#[derive(Deserialize)] #[doc = "Set the sky lignt/texture to a given HDRI file."]
struct SkyFileInfo { fn set_sky(
path: PathBuf, _node: Arc<Node>,
skytex: Option<bool>, calling_client: Arc<Client>,
skylight: Option<bool>, tex: Option<ResourceID>,
} light: Option<ResourceID>,
let info: SkyFileInfo = deserialize(message.as_ref())?; ) -> Result<()> {
info.path.metadata()?; if let Some(tex) = tex {
if info.skytex.unwrap_or_default() { let resource_path = get_resource_file(&tex, &calling_client, &[OsStr::new("hdr")])
QUEUED_SKYTEX.lock().replace(info.path.clone()); .ok_or(eyre::eyre!("Could not find resource"))?;
} QUEUED_SKYTEX.lock().replace(resource_path);
if info.skylight.unwrap_or_default() { }
QUEUED_SKYLIGHT.lock().replace(info.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<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
transform: Transform,
lines: Vec<Line>,
) -> 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<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
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<Node>,
calling_client: Arc<Client>,
name: String,
parent: Arc<Node>,
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(())
}
} }

View File

@@ -1,21 +1,17 @@
use super::Node; use super::{MaterialParameter, ModelAspect, ModelPartAspect, Node};
use crate::core::client::Client; use crate::core::client::Client;
use crate::core::node_collections::LifeLinkedNodeMap; use crate::core::node_collections::LifeLinkedNodeMap;
use crate::core::registry::Registry; 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::drawable::Drawable;
use crate::nodes::spatial::{find_spatial_parent, parse_transform, Spatial, Transform}; use crate::nodes::spatial::Spatial;
use crate::nodes::Message;
use crate::SK_MULTITHREAD; use crate::SK_MULTITHREAD;
use color_eyre::eyre::{bail, ensure, eyre, Result}; use color_eyre::eyre::{bail, ensure, eyre, Result};
use glam::Mat4;
use mint::{ColumnMatrix4, Vector2, Vector3, Vector4};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering}; use portable_atomic::{AtomicBool, Ordering};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use serde::Deserialize; use stardust_xr::values::ResourceID;
use stardust_xr::schemas::flex::deserialize;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::path::PathBuf; use std::path::PathBuf;
@@ -29,26 +25,6 @@ use stereokit::{
static MODEL_REGISTRY: Registry<Model> = Registry::new(); static MODEL_REGISTRY: Registry<Model> = Registry::new();
static HOLDOUT_MATERIAL: OnceCell<Arc<Material>> = OnceCell::new(); static HOLDOUT_MATERIAL: OnceCell<Arc<Material>> = OnceCell::new();
#[derive(Deserialize, Debug)]
#[serde(tag = "t", content = "c")]
pub enum MaterialParameter {
Float(f32),
Vector2(Vector2<f32>),
Vector3(Vector3<f32>),
Vector4(Vector4<f32>),
Color([f32; 4]),
Int(i32),
Int2(Vector2<i32>),
Int3(Vector3<i32>),
Int4(Vector4<i32>),
Bool(bool),
UInt(u32),
UInt2(Vector2<u32>),
UInt3(Vector3<u32>),
UInt4(Vector4<u32>),
Matrix(ColumnMatrix4<f32>),
Texture(ResourceID),
}
impl MaterialParameter { impl MaterialParameter {
fn apply_to_material( fn apply_to_material(
&self, &self,
@@ -58,56 +34,35 @@ impl MaterialParameter {
parameter_name: &str, parameter_name: &str,
) { ) {
match self { match self {
MaterialParameter::Float(val) => { MaterialParameter::Bool(val) => {
sk.material_set_float(material, parameter_name, *val); sk.material_set_bool(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::Int(val) => { MaterialParameter::Int(val) => {
sk.material_set_int(material, parameter_name, *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) => { MaterialParameter::UInt(val) => {
sk.material_set_uint(material, parameter_name, *val); sk.material_set_uint(material, parameter_name, *val);
} }
MaterialParameter::UInt2(val) => { MaterialParameter::Float(val) => {
sk.material_set_uint2(material, parameter_name, val.x, val.y); sk.material_set_float(material, parameter_name, *val);
} }
MaterialParameter::UInt3(val) => { MaterialParameter::Vec2(val) => {
sk.material_set_uint3(material, parameter_name, val.x, val.y, val.z); sk.material_set_vector2(material, parameter_name, *val);
} }
MaterialParameter::UInt4(val) => { MaterialParameter::Vec3(val) => {
sk.material_set_uint4(material, parameter_name, val.w, val.x, val.y, val.z); sk.material_set_vector3(material, parameter_name, *val);
} }
MaterialParameter::Matrix(val) => { MaterialParameter::Color(val) => {
sk.material_set_matrix(material, parameter_name, Mat4::from(*val)); sk.material_set_color(
material,
parameter_name,
Color128::new(val.c.r, val.c.g, val.c.b, val.a),
);
} }
MaterialParameter::Texture(resource) => { MaterialParameter::Texture(resource) => {
let Some(texture_path) = resource.get_file( let Some(texture_path) =
&client.base_resource_prefixes.lock().clone(), get_resource_file(&resource, &client, &[OsStr::new("png"), OsStr::new("jpg")])
&[OsStr::new("png"), OsStr::new("jpg")], else {
) else {
return; return;
}; };
if let Ok(tex) = sk.tex_create_file(texture_path, true, 0) { 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_parameters: Mutex::new(FxHashMap::default()),
pending_material_replacement: Mutex::new(None), pending_material_replacement: Mutex::new(None),
}); });
node.add_local_signal( <ModelPart as ModelPartAspect>::add_node_members(&node);
"set_material_parameter",
ModelPart::set_material_parameter_flex,
);
node.add_local_signal(
"apply_holdout_material",
ModelPart::apply_holdout_material_flex,
);
let _ = node.drawable.set(Drawable::ModelPart(model_part.clone())); let _ = node.drawable.set(Drawable::ModelPart(model_part.clone()));
model.parts.add(id, &node); model.parts.add(id, &node);
Some(model_part) Some(model_part)
} }
fn apply_holdout_material_flex(
node: Arc<Node>,
_calling_client: Arc<Client>,
_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<Node>,
_calling_client: Arc<Client>,
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<Material>) { pub fn replace_material(&self, replacement: Arc<Material>) {
self.pending_material_replacement self.pending_material_replacement
.lock() .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<Node>, _calling_client: Arc<Client>) -> 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<Node>,
_calling_client: Arc<Client>,
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 { pub struct Model {
self_ref: Weak<Model>, self_ref: Weak<Model>,
@@ -337,17 +283,12 @@ impl Model {
"Internal: Node already has a drawable attached!" "Internal: Node already has a drawable attached!"
); );
let pending_model_path = resource_id let pending_model_path = get_resource_file(
.get_file( &resource_id,
&node &*node.get_client().ok_or_else(|| eyre!("Client not found"))?,
.get_client() &[OsStr::new("glb"), OsStr::new("gltf")],
.ok_or_else(|| eyre!("Client not found"))? )
.base_resource_prefixes .ok_or_else(|| eyre!("Resource not found"))?;
.lock()
.clone(),
&[OsStr::new("glb"), OsStr::new("gltf")],
)
.ok_or_else(|| eyre!("Resource not found"))?;
let model = Arc::new_cyclic(|self_ref| Model { let model = Arc::new_cyclic(|self_ref| Model {
self_ref: self_ref.clone(), self_ref: self_ref.clone(),
@@ -388,7 +329,7 @@ impl Model {
); );
} }
} }
impl ModelAspect for Model {}
impl Drop for Model { impl Drop for Model {
fn drop(&mut self) { fn drop(&mut self) {
MODEL_REGISTRY.remove(self); MODEL_REGISTRY.remove(self);
@@ -402,21 +343,3 @@ pub fn draw_all(sk: &impl StereoKitDraw) {
} }
} }
} }
pub fn create_flex(_node: Arc<Node>, calling_client: Arc<Client>, 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(())
}

View File

@@ -1,56 +1,47 @@
use crate::{ use crate::{
core::{client::Client, destroy_queue, registry::Registry, resource::ResourceID}, core::{client::Client, destroy_queue, registry::Registry, resource::get_resource_file},
nodes::{ nodes::{drawable::Drawable, spatial::Spatial, Node},
drawable::Drawable,
spatial::{find_spatial_parent, parse_transform, Spatial, Transform},
Message, Node,
},
}; };
use color_eyre::eyre::{bail, ensure, eyre, Result}; use color_eyre::eyre::{bail, ensure, eyre, Result};
use glam::{vec3, Mat4, Vec2}; use glam::{vec3, Mat4, Vec2};
use mint::Vector2;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use portable_atomic::{AtomicBool, Ordering}; 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 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<Text> = Registry::new(); static TEXT_REGISTRY: Registry<Text> = Registry::new();
struct TextData { fn convert_align(x_align: super::XAlign, y_align: super::YAlign) -> TextAlign {
text: String, let x_align = match x_align {
character_height: f32, super::XAlign::Left => TextAlign::XLeft,
text_align: TextAlign, super::XAlign::Center => TextAlign::XCenter,
bounds: Option<Vec2>, super::XAlign::Right => TextAlign::XRight,
fit: TextFit, } as u32;
bounds_align: TextAlign, let y_align = match y_align {
color: Rgba<f32>, 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 { pub struct Text {
enabled: Arc<AtomicBool>, enabled: Arc<AtomicBool>,
space: Arc<Spatial>, space: Arc<Spatial>,
font_path: Option<PathBuf>, font_path: Option<PathBuf>,
style: OnceCell<TextStyle>, style: OnceCell<SkTextStyle>,
data: Mutex<TextData>, text: Mutex<String>,
data: Mutex<TextStyle>,
} }
impl Text { impl Text {
#[allow(clippy::too_many_arguments)] pub fn add_to(node: &Arc<Node>, text: String, style: TextStyle) -> Result<Arc<Text>> {
pub fn add_to(
node: &Arc<Node>,
font_resource_id: Option<ResourceID>,
text: String,
character_height: f32,
text_align: TextAlign,
bounds: Option<Vector2<f32>>,
fit: TextFit,
bounds_align: TextAlign,
color: Rgba<f32>,
) -> Result<Arc<Text>> {
ensure!( ensure!(
node.spatial.get().is_some(), node.spatial.get().is_some(),
"Internal: Node does not have a spatial attached!" "Internal: Node does not have a spatial attached!"
@@ -64,44 +55,34 @@ impl Text {
let text = TEXT_REGISTRY.add(Text { let text = TEXT_REGISTRY.add(Text {
enabled: node.enabled.clone(), enabled: node.enabled.clone(),
space: node.spatial.get().unwrap().clone(), space: node.spatial.get().unwrap().clone(),
font_path: font_resource_id.and_then(|res| { font_path: style.font.as_ref().and_then(|res| {
res.get_file( get_resource_file(&res, &client, &[OsStr::new("ttf"), OsStr::new("otf")])
&client.base_resource_prefixes.lock().clone(),
&[OsStr::new("ttf"), OsStr::new("otf")],
)
}), }),
style: OnceCell::new(), style: OnceCell::new(),
data: Mutex::new(TextData { text: Mutex::new(text),
text, data: Mutex::new(style),
character_height,
text_align,
bounds: bounds.map(|b| b.into()),
fit,
bounds_align,
color,
}),
}); });
node.add_local_signal("set_character_height", Text::set_character_height_flex); <Text as TextAspect>::add_node_members(node);
node.add_local_signal("set_text", Text::set_text_flex);
let _ = node.drawable.set(Drawable::Text(text.clone())); let _ = node.drawable.set(Drawable::Text(text.clone()));
Ok(text) Ok(text)
} }
fn draw(&self, sk: &impl StereoKitDraw) { fn draw(&self, sk: &impl StereoKitDraw) {
let style = self let style =
.style self.style
.get_or_try_init(|| -> Result<TextStyle, color_eyre::eyre::Error> { .get_or_try_init(|| -> Result<SkTextStyle, color_eyre::eyre::Error> {
let font = self let font = self
.font_path .font_path
.as_deref() .as_deref()
.and_then(|path| sk.font_create(path).ok()) .and_then(|path| sk.font_create(path).ok())
.unwrap_or_else(|| sk.font_find("default/font").unwrap()); .unwrap_or_else(|| sk.font_find("default/font").unwrap());
Ok(unsafe { sk.text_make_style(font, 1.0, WHITE) }) Ok(unsafe { sk.text_make_style(font, 1.0, WHITE) })
}); });
if let Ok(style) = style { if let Ok(style) = style {
let text = self.text.lock();
let data = self.data.lock(); let data = self.data.lock();
let transform = self.space.global_transform() let transform = self.space.global_transform()
* Mat4::from_scale(vec3( * Mat4::from_scale(vec3(
@@ -109,65 +90,58 @@ impl Text {
data.character_height, data.character_height,
data.character_height, data.character_height,
)); ));
if let Some(bounds) = data.bounds { if let Some(bounds) = &data.bounds {
sk.text_add_in( sk.text_add_in(
&data.text, &*text,
transform, transform,
bounds / data.character_height, Vec2::from(bounds.bounds) / data.character_height,
data.fit, 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, *style,
data.bounds_align, convert_align(bounds.anchor_align_x.clone(), bounds.anchor_align_y.clone()),
data.text_align, convert_align(data.text_align_x.clone(), data.text_align_y.clone()),
vec3(0.0, 0.0, 0.0), vec3(0.0, 0.0, 0.0),
Color128::from([ Color128::from([data.color.c.r, data.color.c.g, data.color.c.b, data.color.a]),
data.color.red(),
data.color.green(),
data.color.blue(),
data.color.alpha(),
]),
); );
} else { } else {
sk.text_add_at( sk.text_add_at(
&data.text, &*text,
transform, transform,
*style, *style,
data.bounds_align, TextAlign::Center,
data.text_align, convert_align(data.text_align_x.clone(), data.text_align_y.clone()),
vec3(0.0, 0.0, 0.0), vec3(0.0, 0.0, 0.0),
Color128::from([ Color128::from([data.color.c.r, data.color.c.g, data.color.c.b, data.color.a]),
data.color.red(),
data.color.green(),
data.color.blue(),
data.color.alpha(),
]),
); );
} }
} }
} }
}
pub fn set_character_height_flex( impl TextAspect for Text {
fn set_character_height(
node: Arc<Node>, node: Arc<Node>,
_calling_client: Arc<Client>, _calling_client: Arc<Client>,
message: Message, height: f32,
) -> Result<()> { ) -> Result<()> {
let Some(Drawable::Text(text)) = node.drawable.get() else { let Some(Drawable::Text(text)) = node.drawable.get() else {
bail!("Not a drawable??") bail!("Not a drawable??")
}; };
text.data.lock().character_height = deserialize(message.as_ref())?; text.data.lock().character_height = height;
Ok(()) Ok(())
} }
pub fn set_text_flex( fn set_text(node: Arc<Node>, _calling_client: Arc<Client>, text: String) -> Result<()> {
node: Arc<Node>, let Some(Drawable::Text(text_aspect)) = node.drawable.get() else {
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(Drawable::Text(text)) = node.drawable.get() else {
bail!("Not a drawable??") bail!("Not a drawable??")
}; };
text.data.lock().text = deserialize(message.as_ref())?; *text_aspect.text.lock() = text;
Ok(()) Ok(())
} }
} }
@@ -187,40 +161,3 @@ pub fn draw_all(sk: &impl StereoKitDraw) {
} }
} }
} }
pub fn create_flex(_node: Arc<Node>, calling_client: Arc<Client>, message: Message) -> Result<()> {
#[derive(Deserialize)]
struct CreateTextInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
text: String,
font_resource: Option<ResourceID>,
character_height: f32,
text_align: TextAlign,
bounds: Option<Vector2<f32>>,
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(())
}