Bevy Rewrite, attempt 2 #46

Merged
Schmarni-Dev merged 18 commits from bevy_for_real_this_time into dev 2025-06-30 05:04:00 -04:00
6 changed files with 142 additions and 118 deletions
Showing only changes of commit 880e187dc6 - Show all commits

View File

@@ -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::<OxrActionAttachingPlugin>()
.disable::<OxrActionSyncingPlugin>(),
);
// 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<Camera3d>,
),
>,
) {
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::<XrProjection>() {
xr.near = 0.003
} else {
error_once!("unknown custom camera projection");
}
}
}
}
}
fn xr_step(world: &mut World) {
// camera::update(token);

View File

@@ -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<Assets<Mesh>>,
@@ -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::<Vec<_>>();
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<Vec<Line>>,
gen_mesh: AtomicBool,
entity: OnceLock<Entity>,
bounds: Mutex<Aabb>,
}
impl Lines {
pub fn add_to(node: &Arc<Node>, lines: Vec<Line>) -> Result<Arc<Lines>> {
@@ -173,15 +192,10 @@ impl Lines {
.unwrap()
.bounding_box_calc
.set(|node| {
let mut bounds = Bounds::default();
if let Ok(lines) = node.get_aspect::<Lines>() {
for line in &*lines.data.lock() {
for point in &line.points {
bounds.grown_point(Vec3::from(point.point));
}
}
}
bounds
node.get_aspect::<Lines>()
.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<SkLinePoint> = 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<Node>, _calling_client: Arc<Client>, lines: Vec<Line>) -> 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);
}
}
}
}

View File

@@ -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) {

View File

@@ -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<mpsc::UnboundedSender<(Arc<Model>, 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<Mesh3d>>,
part_mesh_query: Query<(&Transform, &Aabb), With<Mesh3d>>,
has_mesh: Query<Has<Mesh3d>>,
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::<ModelPart>()
.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<Arc<ModelPart>>,
Option<&Children>,
) -> Option<Arc<ModelPart>>,
) {
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<FxHashMap<String, MaterialParameter>>,
pending_material_replacement: Mutex<Option<Material>>,
aliases: AliasList,
bounds: OnceLock<Aabb>,
}
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

View File

@@ -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;
coderabbitai[bot] commented 2025-06-28 14:53:15 -04:00 (Migrated from github.com)
Review

🛠️ Refactor suggestion

Consider implementing entity cleanup for dead spatial references.

The comment raises a valid concern. When the spatial weak reference can't be upgraded, the entity is orphaned and should be despawned to prevent resource leaks.

 let Some(spatial) = spatial_node.0.upgrade() else {
-    // should we despawn the entity?
+    cmds.entity(entity).despawn_recursive();
     return;
 };

Note: This would require adding Commands to the system parameters.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/nodes/spatial/mod.rs around lines 41 to 44, the code currently returns
early when the spatial weak reference cannot be upgraded, potentially leaving
orphaned entities. To fix this, modify the system to accept Commands as a
parameter and use it to despawn the entity when the spatial reference is dead,
ensuring proper cleanup and preventing resource leaks.
_🛠️ Refactor suggestion_ **Consider implementing entity cleanup for dead spatial references.** The comment raises a valid concern. When the spatial weak reference can't be upgraded, the entity is orphaned and should be despawned to prevent resource leaks. ```diff let Some(spatial) = spatial_node.0.upgrade() else { - // should we despawn the entity? + cmds.entity(entity).despawn_recursive(); return; }; ``` Note: This would require adding `Commands` to the system parameters. > Committable suggestion skipped: line range outside the PR's diff. <details> <summary>🤖 Prompt for AI Agents</summary> ``` In src/nodes/spatial/mod.rs around lines 41 to 44, the code currently returns early when the spatial weak reference cannot be upgraded, potentially leaving orphaned entities. To fix this, modify the system to accept Commands as a parameter and use it to despawn the entity when the spatial reference is dead, ensuring proper cleanup and preventing resource leaks. ``` </details> <!-- This is an auto-generated comment by CodeRabbit --> <!-- fingerprinting:phantom:medusa:lion -->
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<Mat4>,
zone: Mutex<Weak<Zone>>,
children: Registry<Spatial>,
pub bounding_box_calc: OnceLock<fn(&Node) -> Bounds>,
pub bounding_box_calc: OnceLock<fn(&Node) -> 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
}
@@ -398,7 +406,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(),
})
}
@@ -409,20 +417,17 @@ impl SpatialRefAspect for SpatialRef {
) -> 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 = 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(),
})
}

View File

@@ -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(