diff --git a/src/main.rs b/src/main.rs index 1b52fe3..c35999b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,9 @@ use bevy::app::{App, TerminalCtrlCHandlerPlugin}; use bevy::asset::{AssetMetaCheck, UnapprovedPathMode}; use bevy::audio::AudioPlugin; use bevy::core_pipeline::CorePipelinePlugin; +use bevy::core_pipeline::oit::{ + OrderIndependentTransparencyPlugin, OrderIndependentTransparencySettings, +}; use bevy::diagnostic::DiagnosticsPlugin; use bevy::ecs::schedule::{ExecutorKind, ScheduleLabel}; use bevy::gizmos::GizmoPlugin; @@ -38,6 +41,7 @@ use bevy_mod_openxr::reference_space::OxrReferenceSpacePlugin; use bevy_mod_openxr::render::{OxrRenderPlugin, OxrWaitFrameSystem}; use bevy_mod_openxr::resources::{OxrFrameState, OxrFrameWaiter, OxrSessionConfig}; use bevy_mod_openxr::types::AppInfo; +use bevy_mod_xr::camera::XrProjection; use bevy_mod_xr::hand_debug_gizmos::HandGizmosPlugin; use bevy_mod_xr::session::{XrFirst, XrHandleEvents}; use clap::Parser; @@ -56,6 +60,7 @@ use openxr::{EnvironmentBlendMode, ReferenceSpaceType}; use session::{launch_start, save_session}; use stardust_xr::schemas::dbus::object_registry::ObjectRegistry; use stardust_xr::server; +use std::ops::DerefMut as _; use std::path::PathBuf; use std::sync::{Arc, OnceLock}; use std::time::Duration; @@ -359,7 +364,7 @@ fn bevy_loop( .disable::() .disable::(), ); - // font size is in meters + app.add_plugins(( bevy_sk::hand::HandPlugin, bevy_sk::vr_materials::SkMaterialPlugin { @@ -403,6 +408,7 @@ fn bevy_loop( app.add_systems(PostStartup, move || { ready_notifier.notify_waiters(); }); + app.add_systems(Update, update_cameras); app.add_systems( XrFirst, xr_step @@ -412,6 +418,30 @@ fn bevy_loop( app.run(); } +fn update_cameras( + mut camera: Query< + &mut Projection, + ( + With, + ), + >, +) { + for mut projection in &mut camera { + match projection.deref_mut() { + Projection::Perspective(perspective_projection) => perspective_projection.near = 0.003, + Projection::Orthographic(orthographic_projection) => { + orthographic_projection.near = 0.003 + } + Projection::Custom(custom_projection) => { + if let Some(xr) = custom_projection.get_mut::() { + xr.near = 0.003 + } else { + error_once!("unknown custom camera projection"); + } + } + } + } +} fn xr_step(world: &mut World) { // camera::update(token); diff --git a/src/nodes/drawable/lines.rs b/src/nodes/drawable/lines.rs index ebc5e12..1ce6677 100644 --- a/src/nodes/drawable/lines.rs +++ b/src/nodes/drawable/lines.rs @@ -9,20 +9,17 @@ use crate::{ use bevy::{ asset::RenderAssetUsages, prelude::*, - render::mesh::{Indices, PrimitiveTopology}, -}; -use bevy_sk::vr_materials::PbrMaterial; -use glam::{FloatExt, Vec3}; -use parking_lot::Mutex; -use std::{ - collections::VecDeque, - sync::{ - Arc, OnceLock, - atomic::{AtomicBool, Ordering}, + render::{ + mesh::{Indices, MeshAabb, PrimitiveTopology}, + primitives::Aabb, }, }; -use stereokit_rust::{ - maths::Bounds, sk::MainThreadToken, system::LinePoint as SkLinePoint, util::Color128, +use bevy_sk::vr_materials::PbrMaterial; +use glam::Vec3; +use parking_lot::Mutex; +use std::sync::{ + Arc, OnceLock, + atomic::{AtomicBool, Ordering}, }; pub struct LinesNodePlugin; @@ -32,16 +29,6 @@ impl Plugin for LinesNodePlugin { app.add_systems(Update, build_line_mesh); } } -const POINTS: [Vec3; 8] = [ - Vec3::X, - Vec3::new(1., 0., 1.), - Vec3::Z, - Vec3::new(-1., 0., 1.), - Vec3::NEG_X, - Vec3::new(-1., 0., -1.), - Vec3::NEG_Z, - Vec3::new(1., 0., -1.), -]; fn build_line_mesh( mut meshes: ResMut>, @@ -62,25 +49,30 @@ fn build_line_mesh( if lines_data.is_empty() { continue; } - - let mut idk = 0; + + let mut indecies_set = 0; for line in lines_data.iter() { + let start_set = indecies_set; let optional_points = { let mut out = Vec::new(); let mut last = line.cyclic.then(|| line.points.last()).flatten(); let mut peekable = line.points.iter().peekable(); while let Some(curr) = peekable.next() { + let mut end = false; let next = match peekable.peek() { Some(v) => Some(*v), - None => line.cyclic.then(|| line.points.first()).flatten(), + None => { + end = true; + line.cyclic.then(|| line.points.first()).flatten() + } }; - out.push((last, curr, next)); + out.push((last, curr, next, end)); last = Some(curr); } out }; - for (last, curr, next) in optional_points { + for (last, curr, next, end) in optional_points { let last_quat = last.map(|v| { Quat::from_rotation_arc( Vec3::Y, @@ -115,10 +107,18 @@ fn build_line_mesh( vertex_normals.extend(normals); vertex_positions.extend(points); vertex_colors.extend([curr.color.to_bevy().to_srgba().to_f32_array(); 8]); - idk += 1; + if !end { + vertex_indecies.extend(indecies(indecies_set)); + } + indecies_set += 1; + } + if line.cyclic { + vertex_indecies.extend(cyclic_indecies(start_set, indecies_set - 1)); + } else { + vertex_indecies.extend(cap_indecies(start_set, false)); + vertex_indecies.extend(cap_indecies(indecies_set - 1, true)); } } - let vertex_indecies = (0..idk - 1).flat_map(indecies).collect::>(); let mut mesh = Mesh::new( PrimitiveTopology::TriangleList, RenderAssetUsages::RENDER_WORLD, @@ -126,7 +126,10 @@ fn build_line_mesh( mesh.insert_indices(Indices::U32(vertex_indecies)); mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_colors); mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_normals); - mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertex_positions); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertex_positions.clone()); + if let Some(aabb) = mesh.compute_aabb() { + *lines.bounds.lock() = aabb; + } match lines.entity.get() { Some(e) => cmds.entity(*e), @@ -137,23 +140,38 @@ fn build_line_mesh( MeshMaterial3d(materials.add(PbrMaterial { color: Color::WHITE, roughness: 1.0, - alpha_mode: AlphaMode::Blend, + alpha_mode: AlphaMode::Opaque, ..default() })), )); } } +const END_CAP_INDECIES: [u32; 18] = [0, 1, 7, 7, 1, 2, 7, 2, 6, 6, 2, 3, 6, 3, 5, 5, 3, 4]; +fn cap_indecies(set: u32, flip: bool) -> [u32; END_CAP_INDECIES.len()] { + let mut out = END_CAP_INDECIES.map(|v| v + (set * 8)); + if flip { + out.reverse(); + } + out +} + // const BASE: [u16; 6] = [0, 8, 1, 8, 9, 1]; const INDECIES: [u32; 48] = [ 0, 8, 1, 8, 9, 1, 1, 9, 2, 9, 10, 2, 2, 10, 3, 10, 11, 3, 3, 11, 4, 11, 12, 4, 4, 12, 5, 12, 13, 5, 5, 13, 6, 13, 14, 6, 6, 14, 7, 14, 15, 7, 7, 15, 0, 15, 8, 0, ]; -fn indecies(base: u32) -> [u32; INDECIES.len()] { - INDECIES.map(|v| v + (base * 8)) +fn indecies(set: u32) -> [u32; INDECIES.len()] { + INDECIES.map(|v| v + (set * 8)) } -fn cyclic_indecies(base: u32) -> [u32; INDECIES.len()] { - let mut out = INDECIES.map(|v| if v >= 8 { v + (base * 8) } else { v }); +fn cyclic_indecies(start_set: u32, end_set: u32) -> [u32; INDECIES.len()] { + let mut out = INDECIES.map(|v| { + if v < 8 { + v + ((start_set) * 8) + } else { + v + ((end_set - 1) * 8) + } + }); out.reverse(); out } @@ -165,6 +183,7 @@ pub struct Lines { data: Mutex>, gen_mesh: AtomicBool, entity: OnceLock, + bounds: Mutex, } impl Lines { pub fn add_to(node: &Arc, lines: Vec) -> Result> { @@ -173,15 +192,10 @@ impl Lines { .unwrap() .bounding_box_calc .set(|node| { - let mut bounds = Bounds::default(); - if let Ok(lines) = node.get_aspect::() { - for line in &*lines.data.lock() { - for point in &line.points { - bounds.grown_point(Vec3::from(point.point)); - } - } - } - bounds + node.get_aspect::() + .ok() + .map(|v| v.bounds.lock().clone()) + .unwrap_or_default() }); info!("line::add_to"); @@ -190,48 +204,12 @@ impl Lines { data: Mutex::new(lines), gen_mesh: AtomicBool::new(true), entity: OnceLock::new(), + bounds: Mutex::new(Aabb::default()), }); node.add_aspect_raw(lines.clone()); Ok(lines) } - - fn draw(&self, token: &MainThreadToken) { - let transform_mat = self.spatial.global_transform(); - let data = self.data.lock().clone(); - for line in &data { - let mut points: VecDeque = line - .points - .iter() - .map(|p| SkLinePoint { - pt: transform_mat.transform_point3(Vec3::from(p.point)).into(), - thickness: p.thickness, - color: Color128::new(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 = 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_point3(Vec3::from(first.point).lerp(Vec3::from(last.point), 0.5)) - .into(), - thickness: (first.thickness + last.thickness) * 0.5, - color: color.into(), - }; - points.push_front(connect_point); - points.push_back(connect_point); - } - stereokit_rust::system::Lines::add_list(token, points.make_contiguous()); - } - } } impl LinesAspect for Lines { fn set_lines(node: Arc, _calling_client: Arc, lines: Vec) -> Result<()> { @@ -247,13 +225,3 @@ impl Drop for Lines { LINES_REGISTRY.remove(self); } } - -pub fn draw_all(token: &MainThreadToken) { - for lines in LINES_REGISTRY.get_valid_contents() { - if let Some(node) = lines.spatial.node() { - if node.enabled() { - lines.draw(token); - } - } - } -} diff --git a/src/nodes/drawable/mod.rs b/src/nodes/drawable/mod.rs index a1ec88e..2c35494 100644 --- a/src/nodes/drawable/mod.rs +++ b/src/nodes/drawable/mod.rs @@ -22,7 +22,6 @@ use stereokit_rust::{sk::MainThreadToken, system::Renderer, tex::SHCubemap}; // #[instrument(level = "debug", skip(sk))] pub fn draw(token: &MainThreadToken) { - lines::draw_all(token); match QUEUED_SKYTEX.lock().take() { Some(Some(skytex)) => { if let Ok(skytex) = SHCubemap::from_cubemap(skytex, true, 100) { diff --git a/src/nodes/drawable/model.rs b/src/nodes/drawable/model.rs index c042157..480dcae 100644 --- a/src/nodes/drawable/model.rs +++ b/src/nodes/drawable/model.rs @@ -9,6 +9,7 @@ use crate::nodes::Node; use crate::nodes::alias::{Alias, AliasList}; use crate::nodes::spatial::{Spatial, SpatialNode}; use bevy::prelude::*; +use bevy::render::primitives::Aabb; use bevy_sk::vr_materials::PbrMaterial; use color_eyre::eyre::eyre; use parking_lot::Mutex; @@ -18,7 +19,6 @@ use std::ffi::OsStr; use std::hash::{Hash, Hasher}; use std::path::PathBuf; use std::sync::{Arc, OnceLock, Weak}; -use stereokit_rust::maths::Bounds; use tokio::sync::mpsc; static LOAD_MODEL: OnceLock, PathBuf)>> = OnceLock::new(); @@ -121,6 +121,7 @@ fn gen_model_parts( query: Query<(Entity, &SceneRoot, &ModelNode, &Children)>, children_query: Query<&Children>, part_query: Query<(&Name, Option<&Children>, &Transform), Without>, + part_mesh_query: Query<(&Transform, &Aabb), With>, has_mesh: Query>, mut cmds: Commands, ) { @@ -145,7 +146,7 @@ fn gen_model_parts( entity, &part_query, None, - &mut |entity, name, transform, parent| { + &mut |entity, name, transform, parent, children| { let path = parent .as_ref() .map(|p| format!("{}/{}", &p.path, name.as_str())) @@ -175,6 +176,7 @@ fn gen_model_parts( pending_material_parameters: Mutex::default(), pending_material_replacement: Mutex::default(), aliases: AliasList::default(), + bounds: OnceLock::new(), }); (spatial, model_part) } @@ -185,9 +187,24 @@ fn gen_model_parts( (part.space.clone(), part.clone()) } }; - _ = spatial.bounding_box_calc.set(|_| { - // TODO: actually impl aabb - Bounds::default() + let aabb = Aabb::enclosing( + children + .iter() + .flat_map(|v| v.iter()) + .filter_map(|e| part_mesh_query.get(e).ok()) + .flat_map(|(transform, aabb)| { + [ + transform.transform_point(aabb.min().into()), + transform.transform_point(aabb.max().into()), + ] + }), + ) + .unwrap_or_default(); + _ = spatial.bounding_box_calc.set(move |n| { + n.get_aspect::() + .ok() + .and_then(|v| v.bounds.get().copied()) + .unwrap_or_default() }); cmds.entity(entity) .insert(SpatialNode(Arc::downgrade(&spatial))); @@ -196,6 +213,7 @@ fn gen_model_parts( .iter() .flat_map(|v| v.iter()) .find(|e| has_mesh.get(*e).unwrap_or(false))?; + _ = model_part.bounds.set(aabb); _ = model_part.entity.set(entity); _ = model_part.mesh_entity.set(mesh_entity); parts.push(model_part.clone()); @@ -216,12 +234,13 @@ fn gen_path( &Name, &Transform, Option>, + Option<&Children>, ) -> Option>, ) { let Ok((name, children, transform)) = part_query.get(current_entity) else { return; }; - let Some(parent) = func(current_entity, name, transform, parent) else { + let Some(parent) = func(current_entity, name, transform, parent, children) else { return; }; for e in children.iter().flat_map(|c| c.iter()) { @@ -449,6 +468,7 @@ pub struct ModelPart { pending_material_parameters: Mutex>, pending_material_replacement: Mutex>, aliases: AliasList, + bounds: OnceLock, } impl ModelPart { pub fn replace_material(&self, replacement: Material) { @@ -589,6 +609,7 @@ impl Model { pending_material_parameters: Mutex::default(), pending_material_replacement: Mutex::default(), aliases: AliasList::default(), + bounds: OnceLock::new(), }); self.pre_bound_parts.lock().push(part.clone()); part diff --git a/src/nodes/spatial/mod.rs b/src/nodes/spatial/mod.rs index 343ad21..6eb08b4 100644 --- a/src/nodes/spatial/mod.rs +++ b/src/nodes/spatial/mod.rs @@ -11,6 +11,7 @@ use crate::core::registry::Registry; use crate::nodes::{Node, OWNED_ASPECT_ALIAS_INFO}; use bevy::prelude::Transform as BevyTransform; use bevy::prelude::*; +use bevy::render::primitives::Aabb; use color_eyre::eyre::OptionExt; use glam::{Mat4, Quat, Vec3, vec3a}; use mint::Vector3; @@ -19,7 +20,6 @@ use rustc_hash::FxHashMap; use std::fmt::Debug; use std::sync::{Arc, OnceLock, Weak}; use std::{f32, ptr}; -use stereokit_rust::maths::Bounds; pub struct SpatialNodePlugin; impl Plugin for SpatialNodePlugin { @@ -102,7 +102,7 @@ pub struct Spatial { transform: Mutex, zone: Mutex>, children: Registry, - pub bounding_box_calc: OnceLock Bounds>, + pub bounding_box_calc: OnceLock Aabb>, } impl Spatial { @@ -146,9 +146,9 @@ impl Spatial { } // the output bounds are probably way bigger than they need to be - pub fn get_bounding_box(&self) -> Bounds { + pub fn get_bounding_box(&self) -> Aabb { let Some(node) = self.node() else { - return Bounds::default(); + return Aabb::default(); }; let mut bounds = self .bounding_box_calc @@ -156,7 +156,15 @@ impl Spatial { .map(|b| (b)(&node)) .unwrap_or_default(); for child in self.children.get_valid_contents() { - bounds.grown_box(child.get_bounding_box(), child.local_transform()); + let mat = child.local_transform(); + let child_aabb = child.get_bounding_box(); + bounds = Aabb::enclosing([ + bounds.min().into(), + bounds.max().into(), + mat.transform_point3(child_aabb.min().into()), + mat.transform_point3(child_aabb.max().into()), + ]) + .unwrap(); } bounds } @@ -382,7 +390,7 @@ impl SpatialRefAspect for SpatialRef { Ok(BoundingBox { center: Vec3::from(bounds.center).into(), - size: Vec3::from(bounds.dimensions).into(), + size: Vec3::from(bounds.half_extents * 2.0).into(), }) } @@ -393,20 +401,17 @@ impl SpatialRefAspect for SpatialRef { ) -> Result { let this_spatial = node.get_aspect::()?; let relative_spatial = relative_to.get_aspect::()?; - let center = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial)) - .transform_point3([0.0; 3].into()); - let mut bounds = Bounds { - center: center.into(), - dimensions: [0.0; 3].into(), - }; - bounds.grown_box( - this_spatial.get_bounding_box(), - Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial)), - ); + let mat = Spatial::space_to_space_matrix(Some(&this_spatial), Some(&relative_spatial)); + let bb = this_spatial.get_bounding_box(); + let bounds = Aabb::enclosing([ + mat.transform_point3(bb.min().into()), + mat.transform_point3(bb.max().into()), + ]) + .unwrap(); Ok(BoundingBox { center: Vec3::from(bounds.center).into(), - size: Vec3::from(bounds.dimensions).into(), + size: Vec3::from(bounds.half_extents * 2.0).into(), }) } diff --git a/src/objects/input/sk_controller.rs b/src/objects/input/sk_controller.rs index 31da994..cbc9b6c 100644 --- a/src/objects/input/sk_controller.rs +++ b/src/objects/input/sk_controller.rs @@ -307,6 +307,9 @@ impl SkController { }) } pub fn set_enabled(&self, enabled: bool) { + if let Some(node) = self.input.spatial.node() { + node.set_enabled(enabled); + } tokio::spawn({ // this is suboptimal since it probably allocates a fresh string every frame let handle = self.tracked.clone(); @@ -342,8 +345,6 @@ impl SkController { let world_transform = Mat4::from(Affine3A::from(location.pose.to_xr_pose())); self.model_part .set_material_parameter("roughness".to_string(), MaterialParameter::Float(1.0)); - // self.model_part - // .set_material_parameter("metallic".to_string(), MaterialParameter::Float(0.0)); self.model_part.set_material_parameter( "color".to_string(), MaterialParameter::Color(stardust_xr::values::Color::new(