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 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<PathBuf> = 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<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 {
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<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::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,

View File

@@ -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<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 {
enabled: Arc<AtomicBool>,
space: Arc<Spatial>,
data: Mutex<Vec<Line>>,
}
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!(
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);
<Lines as LinesAspect>::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<Node>,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(Drawable::Lines(lines)) = node.drawable.get() else {
}
impl LinesAspect for Lines {
fn set_lines(node: Arc<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> Result<()> {
let Some(Drawable::Lines(lines_aspect)) = node.drawable.get() else {
bail!("Not a drawable??")
};
let mut new_lines: Vec<Line> = 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<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,
};
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<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 {
Lines(Arc<Lines>),
@@ -35,7 +30,7 @@ pub enum Drawable {
Text(Arc<Text>),
}
#[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<Option<PathBuf>> = Mutex::new(None);
static QUEUED_SKYTEX: Mutex<Option<PathBuf>> = Mutex::new(None);
fn set_sky_file_flex(
_node: Arc<Node>,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
#[derive(Deserialize)]
struct SkyFileInfo {
path: PathBuf,
skytex: Option<bool>,
skylight: Option<bool>,
}
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<Node>,
calling_client: Arc<Client>,
tex: Option<ResourceID>,
light: Option<ResourceID>,
) -> 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<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::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<Model> = Registry::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 {
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,
);
<ModelPart as ModelPartAspect>::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<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>) {
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<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 {
self_ref: Weak<Model>,
@@ -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<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::{
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<Text> = Registry::new();
struct TextData {
text: String,
character_height: f32,
text_align: TextAlign,
bounds: Option<Vec2>,
fit: TextFit,
bounds_align: TextAlign,
color: Rgba<f32>,
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<AtomicBool>,
space: Arc<Spatial>,
font_path: Option<PathBuf>,
style: OnceCell<TextStyle>,
style: OnceCell<SkTextStyle>,
data: Mutex<TextData>,
text: Mutex<String>,
data: Mutex<TextStyle>,
}
impl Text {
#[allow(clippy::too_many_arguments)]
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>> {
pub fn add_to(node: &Arc<Node>, text: String, style: TextStyle) -> Result<Arc<Text>> {
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);
<Text as TextAspect>::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<TextStyle, color_eyre::eyre::Error> {
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<SkTextStyle, color_eyre::eyre::Error> {
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<Node>,
_calling_client: Arc<Client>,
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<Node>,
_calling_client: Arc<Client>,
message: Message,
) -> Result<()> {
let Some(Drawable::Text(text)) = node.drawable.get() else {
fn set_text(node: Arc<Node>, _calling_client: Arc<Client>, 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<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(())
}