refactor(drawable): use idl
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user