refactor: probably get models mostly working
Signed-off-by: Schmarni <marnistromer@gmail.com>
This commit is contained in:
@@ -82,6 +82,9 @@ zbus = { version = "5.0.0", default-features = false, features = ["tokio"] }
|
|||||||
directories = "5.0.1"
|
directories = "5.0.1"
|
||||||
xkbcommon-rs = "0.1.0"
|
xkbcommon-rs = "0.1.0"
|
||||||
thiserror = "2.0.9"
|
thiserror = "2.0.9"
|
||||||
|
crossbeam-channel = "0.5.14"
|
||||||
|
|
||||||
|
# wayland
|
||||||
# wayland-scanner = "0.31.2"
|
# wayland-scanner = "0.31.2"
|
||||||
# wayland-backend = "0.3.4"
|
# wayland-backend = "0.3.4"
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::ops::Deref as _;
|
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
app::MainScheduleOrder, asset::embedded_asset, ecs::schedule::ScheduleLabel, prelude::*,
|
app::MainScheduleOrder, asset::embedded_asset, ecs::schedule::ScheduleLabel,
|
||||||
|
math::bounding::Aabb3d, prelude::*,
|
||||||
};
|
};
|
||||||
use bevy_mod_openxr::session::OxrSession;
|
use bevy_mod_openxr::session::OxrSession;
|
||||||
use bevy_mod_xr::session::{session_available, XrSessionCreated};
|
use bevy_mod_xr::session::{session_available, XrSessionCreated};
|
||||||
@@ -47,6 +46,49 @@ pub enum BevyToStardustEvent {
|
|||||||
MainSessionVisible(bool),
|
MainSessionVisible(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait StardustAabb3dExt {
|
||||||
|
fn grown_box(&self, aabb: &Self, opt_box_transform: Option<impl Into<Mat4>>) -> Self;
|
||||||
|
fn grown_point(&self, pt: impl Into<Vec3>) -> Self;
|
||||||
|
}
|
||||||
|
impl StardustAabb3dExt for Aabb3d {
|
||||||
|
fn grown_box(&self, other: &Self, opt_box_transform: Option<impl Into<Mat4>>) -> Self {
|
||||||
|
let mat = opt_box_transform.map(|m| m.into());
|
||||||
|
let other_min = mat
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.transform_point3a(other.min))
|
||||||
|
.unwrap_or(other.min);
|
||||||
|
let other_max = mat
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.transform_point3a(other.max))
|
||||||
|
.unwrap_or(other.max);
|
||||||
|
let tmp = self.grown_point(other_min);
|
||||||
|
tmp.grown_point(other_max)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn grown_point(&self, pt: impl Into<Vec3>) -> Self {
|
||||||
|
let pt = pt.into();
|
||||||
|
let mut min = self.min;
|
||||||
|
let mut max = self.max;
|
||||||
|
if pt.x > max.x {
|
||||||
|
max.x = pt.x;
|
||||||
|
} else if pt.x < min.x {
|
||||||
|
min.x = pt.x;
|
||||||
|
}
|
||||||
|
if pt.y > max.y {
|
||||||
|
max.y = pt.y;
|
||||||
|
} else if pt.y < min.y {
|
||||||
|
min.y = pt.y;
|
||||||
|
}
|
||||||
|
if pt.z > max.z {
|
||||||
|
max.z = pt.z;
|
||||||
|
} else if pt.z < min.z {
|
||||||
|
min.z = pt.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
Aabb3d { min, max }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(ScheduleLabel, Hash, Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(ScheduleLabel, Hash, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct StardustExtract;
|
pub struct StardustExtract;
|
||||||
#[derive(Component, Hash, Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Component, Hash, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -55,4 +97,4 @@ pub struct TemporaryEntity;
|
|||||||
#[require(GlobalTransform)]
|
#[require(GlobalTransform)]
|
||||||
pub struct ViewLocation;
|
pub struct ViewLocation;
|
||||||
#[derive(Hash, Debug, Clone, Copy, PartialEq, Eq, Deref)]
|
#[derive(Hash, Debug, Clone, Copy, PartialEq, Eq, Deref)]
|
||||||
pub struct MainWorldEntity(Entity);
|
pub struct MainWorldEntity(pub Entity);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::{MaterialParameter, ModelAspect, ModelPartAspect, MODEL_PART_ASPECT_ALIAS_INFO};
|
|
||||||
use crate::bail;
|
use crate::bail;
|
||||||
|
use crate::bevy_plugin::MainWorldEntity;
|
||||||
use crate::core::client::Client;
|
use crate::core::client::Client;
|
||||||
use crate::core::error::Result;
|
use crate::core::error::Result;
|
||||||
use crate::core::registry::Registry;
|
use crate::core::registry::Registry;
|
||||||
@@ -8,24 +8,35 @@ use crate::nodes::alias::{Alias, AliasList};
|
|||||||
use crate::nodes::spatial::Spatial;
|
use crate::nodes::spatial::Spatial;
|
||||||
use crate::nodes::{Aspect, Node};
|
use crate::nodes::{Aspect, Node};
|
||||||
use crate::DefaultMaterial;
|
use crate::DefaultMaterial;
|
||||||
use bevy::app::Plugin;
|
use bevy::app::{Plugin, PostUpdate};
|
||||||
use bevy::asset::Handle;
|
use bevy::asset::Handle;
|
||||||
|
use bevy::asset::{AssetServer, Assets};
|
||||||
use bevy::color::{Alpha, Color, LinearRgba, Srgba};
|
use bevy::color::{Alpha, Color, LinearRgba, Srgba};
|
||||||
|
use bevy::core::Name;
|
||||||
|
use bevy::gltf::GltfAssetLabel;
|
||||||
|
use bevy::math::bounding::Aabb3d;
|
||||||
|
use bevy::pbr::MeshMaterial3d;
|
||||||
use bevy::prelude::AlphaMode;
|
use bevy::prelude::AlphaMode;
|
||||||
|
use bevy::prelude::{
|
||||||
|
Children, Commands, Component, Deref, Entity, HierarchyQueryExt, Mesh3d, Parent, Query, Res,
|
||||||
|
ResMut, Resource, Transform, Visibility, With, Without,
|
||||||
|
};
|
||||||
use bevy::reflect::{GetField, PartialReflect, Reflect};
|
use bevy::reflect::{GetField, PartialReflect, Reflect};
|
||||||
|
use bevy::render::primitives::Aabb;
|
||||||
|
use bevy::scene::SceneRoot;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use glam::{Mat4, Vec2, Vec3};
|
use glam::{Vec2, Vec3};
|
||||||
use once_cell::sync::{Lazy, OnceCell};
|
use once_cell::sync::OnceCell;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use stardust_xr::values::ResourceID;
|
use stardust_xr::values::ResourceID;
|
||||||
use tracing::warn;
|
use tracing::{error, warn};
|
||||||
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
|
|
||||||
static MODEL_REGISTRY: Registry<Model> = Registry::new();
|
static MODEL_REGISTRY: Registry<Model> = Registry::new();
|
||||||
static HOLDOUT_MATERIAL: OnceCell<Arc<DefaultMaterial>> = OnceCell::new();
|
static HOLDOUT_MATERIAL: OnceCell<Arc<DefaultMaterial>> = OnceCell::new();
|
||||||
|
|
||||||
@@ -114,167 +125,110 @@ impl MaterialParameter {
|
|||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// if let Ok(tex) = Tex::from_file(texture_path, true, None) {
|
error!("TODO: implement texture changing");
|
||||||
// params.set_texture(parameter_name, &tex);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ModelPart {
|
pub struct ModelPart {
|
||||||
id: i32,
|
entity: MainWorldEntity,
|
||||||
path: String,
|
path: String,
|
||||||
space: Arc<Spatial>,
|
space: Arc<Spatial>,
|
||||||
model: Weak<Model>,
|
model: Weak<Model>,
|
||||||
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
|
pending_material_parameters: Mutex<FxHashMap<String, MaterialParameter>>,
|
||||||
pending_material_replacement: Mutex<Option<Arc<Handle<DefaultMaterial>>>>,
|
pending_material_replacement: Mutex<Option<Arc<DefaultMaterial>>>,
|
||||||
aliases: AliasList,
|
aliases: AliasList,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Clone)]
|
||||||
|
pub struct StardustModel(Arc<Model>);
|
||||||
|
#[derive(Component, Clone)]
|
||||||
|
pub struct UnprocessedModel;
|
||||||
pub struct StardustModelPlugin;
|
pub struct StardustModelPlugin;
|
||||||
impl Plugin for StardustModelPlugin {
|
impl Plugin for StardustModelPlugin {
|
||||||
fn build(&self, app: &mut bevy::prelude::App) {}
|
fn build(&self, app: &mut bevy::prelude::App) {
|
||||||
|
let (tx, rx) = crossbeam_channel::unbounded();
|
||||||
|
LOAD_MODEL_SENDER.set(tx);
|
||||||
|
app.insert_resource(LoadModelReader(rx));
|
||||||
|
app.add_systems(PostUpdate, create_model_parts_for_loaded_models);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static LOAD_MODEL_SENDER: OnceCell<crossbeam_channel::Sender<(PathBuf, Arc<Model>)>> =
|
||||||
|
OnceCell::new();
|
||||||
|
#[derive(Resource, Deref)]
|
||||||
|
struct LoadModelReader(crossbeam_channel::Receiver<(PathBuf, Arc<Model>)>);
|
||||||
|
|
||||||
|
fn update_models(mut query: Query<(&StardustModel, &mut Visibility, &mut Transform)>) {
|
||||||
|
for (model, mut vis, mut transform) in query.iter_mut() {
|
||||||
|
*transform = Transform::from_matrix(model.0.space.global_transform());
|
||||||
|
if let Some(node) = model.0.space.node() {
|
||||||
|
*vis = match node.enabled() {
|
||||||
|
true => Visibility::Inherited,
|
||||||
|
false => Visibility::Hidden,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModelPart {
|
fn load_models(rx: Res<LoadModelReader>, mut cmds: Commands, asset_server: Res<AssetServer>) {
|
||||||
fn create_for_model(model: &Arc<Model>, sk_model: &SKModel) {
|
for (path, model) in rx.try_iter() {
|
||||||
// The value isn't even used?!
|
let handle = asset_server.load(GltfAssetLabel::Scene(0).from_asset(path));
|
||||||
HOLDOUT_MATERIAL.get_or_init(|| {
|
let entity = cmds
|
||||||
let mut mat = DefaultMaterial {
|
.spawn((
|
||||||
unlit: true,
|
SceneRoot(handle),
|
||||||
alpha_mode: AlphaMode::Opaque,
|
StardustModel(model.clone()),
|
||||||
base_color: Srgba::BLACK.with_alpha(0.0).into(),
|
UnprocessedModel,
|
||||||
..Default::default()
|
))
|
||||||
};
|
.id();
|
||||||
Arc::new(mat)
|
model.entity.set(entity);
|
||||||
});
|
|
||||||
|
|
||||||
let nodes = sk_model.get_nodes();
|
|
||||||
for part in nodes.all() {
|
|
||||||
ModelPart::create(model, &part);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn create(model: &Arc<Model>, part: &stereokit_rust::model::ModelNode) -> Option<Arc<Self>> {
|
fn update_model_parts(
|
||||||
let mut parts = model.parts.lock();
|
models: Query<&StardustModel>,
|
||||||
let parent_part = part
|
mut mats: ResMut<Assets<DefaultMaterial>>,
|
||||||
.get_parent()
|
mut part_query: Query<(
|
||||||
.and_then(|part| parts.iter().find(|p| p.id == *part.get_id()));
|
&mut Transform,
|
||||||
|
&mut MeshMaterial3d<DefaultMaterial>,
|
||||||
let stardust_model_part = model.space.node()?;
|
&mut Mesh3d,
|
||||||
let client = stardust_model_part.get_client()?;
|
)>,
|
||||||
let mut part_path = parent_part
|
) {
|
||||||
.map(|n| n.path.clone() + "/")
|
for model in &models {
|
||||||
.unwrap_or_default();
|
let model = &model.0;
|
||||||
part_path += part.get_name().unwrap();
|
for part in model.parts.lock().iter() {
|
||||||
|
let Ok((mut transform, mut mat, mut _mesh)) = part_query.get_mut(*part.entity) else {
|
||||||
let node = client.scenegraph.add_node(Node::generate(&client, false));
|
continue;
|
||||||
let spatial_parent = parent_part
|
|
||||||
.map(|n| n.space.clone())
|
|
||||||
.unwrap_or_else(|| model.space.clone());
|
|
||||||
|
|
||||||
let local_transform = unsafe { part.get_local_transform().m };
|
|
||||||
let space = Spatial::add_to(
|
|
||||||
&node,
|
|
||||||
Some(spatial_parent),
|
|
||||||
Mat4::from_cols_array(&local_transform),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
let _ = space.bounding_box_calc.set(|node| {
|
|
||||||
let Ok(model_part) = node.get_aspect::<ModelPart>() else {
|
|
||||||
return Bounds::default();
|
|
||||||
};
|
};
|
||||||
let Some(model) = model_part.model.upgrade() else {
|
*transform = Transform::from_matrix(Spatial::space_to_space_matrix(
|
||||||
return Bounds::default();
|
Some(&part.space),
|
||||||
};
|
Some(&model.space),
|
||||||
let Some(sk_model) = model.sk_model.get() else {
|
));
|
||||||
return Bounds::default();
|
if let Some(material_replacement) = part.pending_material_replacement.lock().take() {
|
||||||
};
|
let material = material_replacement.deref().clone();
|
||||||
let model_nodes = sk_model.get_nodes();
|
let mat_handle = mats.add(material);
|
||||||
let Some(model_node) = model_nodes.get_index(model_part.id) else {
|
mat.0 = mat_handle;
|
||||||
return Bounds::default();
|
}
|
||||||
};
|
// todo: find all materials with identical parameters and batch them into 1 material again
|
||||||
let Some(sk_mesh) = model_node.get_mesh() else {
|
'mat_params: {
|
||||||
return Bounds::default();
|
let mut material_parameters = part.pending_material_parameters.lock();
|
||||||
};
|
if !material_parameters.is_empty() {
|
||||||
sk_mesh.get_bounds()
|
let Some(material) = mats.get(&mat.0) else {
|
||||||
});
|
break 'mat_params;
|
||||||
|
};
|
||||||
|
|
||||||
let model_part = Arc::new(ModelPart {
|
let mut new_material = material.clone();
|
||||||
id: *part.get_id(),
|
let Some(client) = part.space.node().and_then(|v| v.get_client()) else {
|
||||||
path: part_path,
|
return;
|
||||||
space,
|
};
|
||||||
model: Arc::downgrade(model),
|
for (parameter_name, parameter_value) in material_parameters.drain() {
|
||||||
pending_material_parameters: Mutex::new(FxHashMap::default()),
|
parameter_value.apply_to_material(
|
||||||
pending_material_replacement: Mutex::new(None),
|
&client,
|
||||||
aliases: AliasList::default(),
|
&mut new_material,
|
||||||
});
|
¶meter_name,
|
||||||
node.add_aspect_raw(model_part.clone());
|
);
|
||||||
parts.push(model_part.clone());
|
}
|
||||||
Some(model_part)
|
mat.0 = mats.add(new_material);
|
||||||
}
|
|
||||||
|
|
||||||
pub fn replace_material(&self, replacement: Arc<MaterialWrapper>) {
|
|
||||||
let shared_material = MATERIAL_REGISTRY.add_or_get(replacement);
|
|
||||||
self.pending_material_replacement
|
|
||||||
.lock()
|
|
||||||
.replace(shared_material);
|
|
||||||
}
|
|
||||||
/// only to be run on the main thread
|
|
||||||
pub fn replace_material_now(&self, replacement: &Material) {
|
|
||||||
let Some(model) = self.model.upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(sk_model) = model.sk_model.get() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let nodes = sk_model.get_nodes();
|
|
||||||
let Some(mut part) = nodes.get_index(self.id) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let shared_material =
|
|
||||||
MATERIAL_REGISTRY.add_or_get(Arc::new(MaterialWrapper(replacement.copy())));
|
|
||||||
part.material(&shared_material.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&self) {
|
|
||||||
let Some(model) = self.model.upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(sk_model) = model.sk_model.get() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(node) = model.space.node() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let nodes = sk_model.get_nodes();
|
|
||||||
let Some(mut part) = nodes.get_index(self.id) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
part.model_transform(Spatial::space_to_space_matrix(
|
|
||||||
Some(&self.space),
|
|
||||||
Some(&model.space),
|
|
||||||
));
|
|
||||||
|
|
||||||
let Some(client) = node.get_client() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(material_replacement) = self.pending_material_replacement.lock().take() {
|
|
||||||
part.material(&material_replacement.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
'mat_params: {
|
|
||||||
let mut material_parameters = self.pending_material_parameters.lock();
|
|
||||||
if !material_parameters.is_empty() {
|
|
||||||
let Some(material) = part.get_material() else {
|
|
||||||
break 'mat_params;
|
|
||||||
};
|
|
||||||
let new_material = material.copy();
|
|
||||||
for (parameter_name, parameter_value) in material_parameters.drain() {
|
|
||||||
parameter_value.apply_to_material(&client, &new_material, ¶meter_name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let shared_material =
|
let shared_material =
|
||||||
@@ -284,6 +238,85 @@ impl ModelPart {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_path(
|
||||||
|
entity: Entity,
|
||||||
|
query: &Query<(Entity, Option<&Parent>, &Transform, &Name, &Aabb), Without<SceneRoot>>,
|
||||||
|
) -> Option<String> {
|
||||||
|
let (_, parent, _, name, _) = query.get(entity).ok()?;
|
||||||
|
let next = parent.and_then(|p| get_path(p.get(), query));
|
||||||
|
match next {
|
||||||
|
Some(next) => Some(format!("{name}/{next}")),
|
||||||
|
None => Some(name.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_model_parts_for_loaded_models(
|
||||||
|
query: Query<(Entity, &StardustModel), With<UnprocessedModel>>,
|
||||||
|
children: Query<&Children>,
|
||||||
|
gltf_model_parts: Query<
|
||||||
|
(Entity, Option<&Parent>, &Transform, &Name, &Aabb),
|
||||||
|
Without<SceneRoot>,
|
||||||
|
>,
|
||||||
|
mut cmds: Commands,
|
||||||
|
) {
|
||||||
|
for (entity, model) in &query {
|
||||||
|
let model = &model.0;
|
||||||
|
let mut parts = model.parts.lock();
|
||||||
|
cmds.entity(entity).remove::<UnprocessedModel>();
|
||||||
|
for (entity, parent, transform, name, aabb) in children
|
||||||
|
.iter_descendants(entity)
|
||||||
|
.filter_map(|e| gltf_model_parts.get(e).ok())
|
||||||
|
{
|
||||||
|
let parent_part = parent
|
||||||
|
.and_then(|e| gltf_model_parts.get(e.get()).ok())
|
||||||
|
.and_then(|(e, _, _, _, _)| parts.iter().find(|v| v.entity.0 == e));
|
||||||
|
|
||||||
|
let Some(stardust_model_part) = model.space.node() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Some(client) = stardust_model_part.get_client() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let part_path = get_path(entity, &gltf_model_parts).unwrap_or_else(|| name.to_string());
|
||||||
|
|
||||||
|
let node = client.scenegraph.add_node(Node::generate(&client, false));
|
||||||
|
let spatial_parent = parent_part
|
||||||
|
.map(|n| n.space.clone())
|
||||||
|
.unwrap_or_else(|| model.space.clone());
|
||||||
|
|
||||||
|
let space = Spatial::add_to(
|
||||||
|
&node,
|
||||||
|
Some(spatial_parent),
|
||||||
|
transform.compute_matrix(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
*space.bounding_box_calc.lock() = Aabb3d::new(aabb.center, aabb.half_extents);
|
||||||
|
|
||||||
|
let model_part = Arc::new(ModelPart {
|
||||||
|
entity: MainWorldEntity(entity),
|
||||||
|
path: part_path,
|
||||||
|
space,
|
||||||
|
model: Arc::downgrade(model),
|
||||||
|
pending_material_parameters: Mutex::new(FxHashMap::default()),
|
||||||
|
pending_material_replacement: Mutex::new(None),
|
||||||
|
aliases: AliasList::default(),
|
||||||
|
});
|
||||||
|
<ModelPart as ModelPartAspect>::add_node_members(&node);
|
||||||
|
node.add_aspect_raw(model_part.clone());
|
||||||
|
parts.push(model_part.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModelPart {
|
||||||
|
pub fn replace_material(&self, replacement: Arc<DefaultMaterial>) {
|
||||||
|
self.pending_material_replacement
|
||||||
|
.lock()
|
||||||
|
.replace(replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
impl ModelPartAspect for 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."]
|
#[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<()> {
|
fn apply_holdout_material(node: Arc<Node>, _calling_client: Arc<Client>) -> Result<()> {
|
||||||
@@ -312,7 +345,7 @@ impl ModelPartAspect for ModelPart {
|
|||||||
pub struct Model {
|
pub struct Model {
|
||||||
space: Arc<Spatial>,
|
space: Arc<Spatial>,
|
||||||
_resource_id: ResourceID,
|
_resource_id: ResourceID,
|
||||||
sk_model: OnceCell<()>,
|
entity: OnceCell<Entity>,
|
||||||
parts: Mutex<Vec<Arc<ModelPart>>>,
|
parts: Mutex<Vec<Arc<ModelPart>>>,
|
||||||
}
|
}
|
||||||
impl Model {
|
impl Model {
|
||||||
@@ -327,42 +360,17 @@ impl Model {
|
|||||||
let model = Arc::new(Model {
|
let model = Arc::new(Model {
|
||||||
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
space: node.get_aspect::<Spatial>().unwrap().clone(),
|
||||||
_resource_id: resource_id,
|
_resource_id: resource_id,
|
||||||
sk_model: OnceCell::new(),
|
entity: OnceCell::new(),
|
||||||
parts: Mutex::new(Vec::default()),
|
parts: Mutex::new(Vec::default()),
|
||||||
});
|
});
|
||||||
MODEL_REGISTRY.add_raw(&model);
|
MODEL_REGISTRY.add_raw(&model);
|
||||||
|
if let Some(sender) = LOAD_MODEL_SENDER.get() {
|
||||||
// technically doing this in anything but the main thread isn't a good idea but dangit we need those model nodes ASAP
|
sender.send((pending_model_path, model.clone()));
|
||||||
let sk_model = SKModel::copy(SKModel::from_file(
|
}
|
||||||
pending_model_path.to_str().unwrap(),
|
|
||||||
None,
|
|
||||||
)?);
|
|
||||||
ModelPart::create_for_model(&model, &sk_model);
|
|
||||||
let _ = model.sk_model.set(sk_model);
|
|
||||||
node.add_aspect_raw(model.clone());
|
node.add_aspect_raw(model.clone());
|
||||||
Ok(model)
|
Ok(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, token: &MainThreadToken) {
|
|
||||||
let Some(sk_model) = self.sk_model.get() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let parts = self.parts.lock();
|
|
||||||
for model_node in &*parts {
|
|
||||||
model_node.update();
|
|
||||||
}
|
|
||||||
drop(parts);
|
|
||||||
|
|
||||||
if let Some(node) = self.space.node() {
|
|
||||||
if node.enabled() {
|
|
||||||
sk_model.draw(token, self.space.global_transform(), None, None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// TODO: proper hread safety in stereokit_rust (probably just bind stereokit directly)
|
|
||||||
unsafe impl Send for Model {}
|
|
||||||
unsafe impl Sync for Model {}
|
|
||||||
impl ModelAspect for Model {
|
impl ModelAspect for Model {
|
||||||
#[doc = "Bind a model part to the node with the ID input."]
|
#[doc = "Bind a model part to the node with the ID input."]
|
||||||
fn bind_model_part(
|
fn bind_model_part(
|
||||||
@@ -392,9 +400,3 @@ impl Drop for Model {
|
|||||||
MODEL_REGISTRY.remove(self);
|
MODEL_REGISTRY.remove(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_all(token: &MainThreadToken) {
|
|
||||||
for model in MODEL_REGISTRY.get_valid_contents() {
|
|
||||||
model.draw(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ use super::alias::Alias;
|
|||||||
use super::fields::{Field, FieldTrait};
|
use super::fields::{Field, FieldTrait};
|
||||||
use super::{Aspect, AspectIdentifier};
|
use super::{Aspect, AspectIdentifier};
|
||||||
use crate::bail;
|
use crate::bail;
|
||||||
|
use crate::bevy_plugin::StardustAabb3dExt;
|
||||||
use crate::core::client::Client;
|
use crate::core::client::Client;
|
||||||
use crate::core::error::Result;
|
use crate::core::error::Result;
|
||||||
use crate::core::registry::Registry;
|
use crate::core::registry::Registry;
|
||||||
use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO};
|
use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO};
|
||||||
use bevy::math::bounding::{Aabb3d, BoundingVolume};
|
use bevy::math::bounding::{Aabb3d, BoundingVolume};
|
||||||
use color_eyre::eyre::OptionExt;
|
use color_eyre::eyre::OptionExt;
|
||||||
use glam::{vec3a, Mat4, Quat, Vec3};
|
use glam::{vec3a, Mat4, Quat, Vec3, Vec3A};
|
||||||
use mint::Vector3;
|
use mint::Vector3;
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
@@ -59,7 +59,7 @@ pub struct Spatial {
|
|||||||
transform: Mutex<Mat4>,
|
transform: Mutex<Mat4>,
|
||||||
zone: Mutex<Weak<Zone>>,
|
zone: Mutex<Weak<Zone>>,
|
||||||
children: Registry<Spatial>,
|
children: Registry<Spatial>,
|
||||||
pub bounding_box_calc: OnceCell<fn(&Node) -> Aabb3d>,
|
pub bounding_box_calc: Mutex<Aabb3d>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spatial {
|
impl Spatial {
|
||||||
@@ -71,7 +71,7 @@ impl Spatial {
|
|||||||
transform: Mutex::new(transform),
|
transform: Mutex::new(transform),
|
||||||
zone: Mutex::new(Weak::new()),
|
zone: Mutex::new(Weak::new()),
|
||||||
children: Registry::new(),
|
children: Registry::new(),
|
||||||
bounding_box_calc: OnceCell::default(),
|
bounding_box_calc: Mutex::new(Aabb3d::new(Vec3A::ZERO, Vec3A::ZERO)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn add_to(
|
pub fn add_to(
|
||||||
@@ -247,6 +247,65 @@ impl AspectIdentifier for Spatial {
|
|||||||
impl Aspect for Spatial {
|
impl Aspect for Spatial {
|
||||||
impl_aspect_for_spatial_aspect! {}
|
impl_aspect_for_spatial_aspect! {}
|
||||||
}
|
}
|
||||||
|
impl SpatialRefAspect for Spatial {
|
||||||
|
async fn get_local_bounding_box(
|
||||||
|
node: Arc<Node>,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
) -> Result<BoundingBox> {
|
||||||
|
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||||
|
let bounds = this_spatial.get_bounding_box();
|
||||||
|
|
||||||
|
Ok(BoundingBox {
|
||||||
|
center: Vec3::from(bounds.center()).into(),
|
||||||
|
size: Vec3::from(bounds.half_size() * 2.0).into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_relative_bounding_box(
|
||||||
|
node: Arc<Node>,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
relative_to: Arc<Node>,
|
||||||
|
) -> Result<BoundingBox> {
|
||||||
|
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||||
|
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
|
||||||
|
let center = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial))
|
||||||
|
.transform_point3([0.0; 3].into());
|
||||||
|
let mut bounds = Aabb3d::new(center, Vec3A::ZERO);
|
||||||
|
bounds = bounds.grown_box(
|
||||||
|
&this_spatial.get_bounding_box(),
|
||||||
|
Some(Spatial::space_to_space_matrix(
|
||||||
|
Some(&this_spatial),
|
||||||
|
Some(&relative_spatial),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(BoundingBox {
|
||||||
|
center: Vec3::from(bounds.center()).into(),
|
||||||
|
size: Vec3::from(bounds.half_size() * 2.0).into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_transform(
|
||||||
|
node: Arc<Node>,
|
||||||
|
_calling_client: Arc<Client>,
|
||||||
|
relative_to: Arc<Node>,
|
||||||
|
) -> Result<Transform> {
|
||||||
|
let this_spatial = node.get_aspect::<Spatial>()?;
|
||||||
|
let relative_spatial = relative_to.get_aspect::<Spatial>()?;
|
||||||
|
|
||||||
|
let (scale, rotation, position) = Spatial::space_to_space_matrix(
|
||||||
|
Some(this_spatial.as_ref()),
|
||||||
|
Some(relative_spatial.as_ref()),
|
||||||
|
)
|
||||||
|
.to_scale_rotation_translation();
|
||||||
|
|
||||||
|
Ok(Transform {
|
||||||
|
translation: Some(position.into()),
|
||||||
|
rotation: Some(rotation.into()),
|
||||||
|
scale: Some(scale.into()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
impl SpatialAspect for Spatial {
|
impl SpatialAspect for Spatial {
|
||||||
fn set_local_transform(
|
fn set_local_transform(
|
||||||
node: Arc<Node>,
|
node: Arc<Node>,
|
||||||
|
|||||||
Reference in New Issue
Block a user