From e8e464729ec7fbc720b9ff21a3651ad5c4a2ea93 Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Fri, 15 Dec 2023 23:54:11 -0800 Subject: [PATCH 1/5] Support teleportation, objects without normals --- blade-render/code/fill-gbuf.wgsl | 5 ++++- examples/scene/main.rs | 14 ++++++++++---- src/lib.rs | 17 +++++++++++++++-- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/blade-render/code/fill-gbuf.wgsl b/blade-render/code/fill-gbuf.wgsl index b0ca69e0..edfbce43 100644 --- a/blade-render/code/fill-gbuf.wgsl +++ b/blade-render/code/fill-gbuf.wgsl @@ -125,7 +125,10 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { let n_xy = textureSampleLevel(textures[entry.normal_texture], sampler_linear, tex_coords, lod).xy; normal_local = vec3(n_xy, sqrt(max(0.0, 1.0 - dot(n_xy.xy, n_xy.xy)))); } - let normal = qrot(geo_to_world_rot, tangent_space_geo * normal_local); + var normal = qrot(geo_to_world_rot, tangent_space_geo * normal_local); + if (dot(normal, normal) == 0.0) { + normal = flat_normal; // fallback + } basis = shortest_arc_quat(vec3(0.0, 0.0, 1.0), normalize(normal)); let hit_position = camera.position + intersection.t * ray_dir; diff --git a/examples/scene/main.rs b/examples/scene/main.rs index 4d57866a..bf81abf6 100644 --- a/examples/scene/main.rs +++ b/examples/scene/main.rs @@ -678,13 +678,19 @@ impl Example { ); }); ui.horizontal(|ui| { + let tc = &selection.tex_coords; ui.label("Texture coords:"); ui.colored_label( egui::Color32::WHITE, - format!( - "{:.2} {:.2}", - selection.tex_coords.x, selection.tex_coords.y - ), + format!("{:.2} {:.2}", tc.x, tc.y), + ); + }); + ui.horizontal(|ui| { + let wp = &selection.position; + ui.label("World pos:"); + ui.colored_label( + egui::Color32::WHITE, + format!("{:.2} {:.2} {:.2}", wp.x, wp.y, wp.z), ); }); ui.horizontal(|ui| { diff --git a/src/lib.rs b/src/lib.rs index 7ca9e986..aab32d3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -646,7 +646,7 @@ impl Engine { let mut colliders = Vec::new(); for cc in config.colliders.iter() { - use rapier3d::geometry::ColliderBuilder; + use rapier3d::geometry::{ColliderBuilder, TriMeshFlags}; let isometry = nalgebra::geometry::Isometry3::from_parts( nalgebra::Vector3::from(cc.pos).into(), @@ -692,7 +692,12 @@ impl Engine { .expect("Unable to build convex mesh") } else { assert_eq!(border_radius, 0.0); - ColliderBuilder::trimesh(trimesh.points, trimesh.triangles) + let flags = TriMeshFlags::empty(); + ColliderBuilder::trimesh_with_flags( + trimesh.points, + trimesh.triangles, + flags, + ) } } }; @@ -760,6 +765,14 @@ impl Engine { body.apply_impulse(impulse, false) } + pub fn teleport_object(&mut self, handle: ObjectHandle, isometry: nalgebra::Isometry3) { + let object = &self.objects[handle.0]; + let body = &mut self.physics.rigid_bodies[object.rigid_body]; + body.set_linvel(Default::default(), false); + body.set_angvel(Default::default(), false); + body.set_position(isometry, true); + } + pub fn set_environment_map(&mut self, path: &str) { if path.is_empty() { self.environment_map = None; From db11403afc377fea6b2b2bab5674796e5857f4c1 Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Sat, 16 Dec 2023 21:45:45 -0800 Subject: [PATCH 2/5] Switch camera rotation to be local --- examples/scene/main.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/scene/main.rs b/examples/scene/main.rs index bf81abf6..eefc761a 100644 --- a/examples/scene/main.rs +++ b/examples/scene/main.rs @@ -1147,18 +1147,18 @@ fn main() { example.is_point_selected = false; } winit::event::WindowEvent::CursorMoved { position, .. } => { - last_mouse_pos = [position.x as i32, position.y as i32]; - if let Some(ref mut drag) = drag_start { - // This is rotation around the world UP, which is assumed to be Y - let qx = glam::Quat::from_rotation_y( - (drag.screen_pos.x - last_mouse_pos[0]) as f32 * rotate_speed, - ); - let qy = glam::Quat::from_rotation_x( - (drag.screen_pos.y - last_mouse_pos[1]) as f32 * rotate_speed, + if let Some(_) = drag_start { + let prev = glam::Quat::from(example.camera.rot); + let rotation = glam::Quat::from_euler( + glam::EulerRot::ZYX, + 0.0, + (last_mouse_pos[0] as f32 - position.x as f32) * rotate_speed, + (last_mouse_pos[1] as f32 - position.y as f32) * rotate_speed, ); - example.camera.rot = (qx * drag.rotation * qy).into(); + example.camera.rot = (prev * rotation).into(); example.debug.mouse_pos = None; } + last_mouse_pos = [position.x as i32, position.y as i32]; } winit::event::WindowEvent::HoveredFile(_) => { example.is_file_hovered = true; From 211725c9fa14bec90fcf1305bba0f7647624323a Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Sat, 16 Dec 2023 21:45:58 -0800 Subject: [PATCH 3/5] Handle missing normals in GLTF loader --- blade-render/code/fill-gbuf.wgsl | 3 --- blade-render/src/model/mod.rs | 21 +++++++++++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/blade-render/code/fill-gbuf.wgsl b/blade-render/code/fill-gbuf.wgsl index edfbce43..346edf51 100644 --- a/blade-render/code/fill-gbuf.wgsl +++ b/blade-render/code/fill-gbuf.wgsl @@ -126,9 +126,6 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { normal_local = vec3(n_xy, sqrt(max(0.0, 1.0 - dot(n_xy.xy, n_xy.xy)))); } var normal = qrot(geo_to_world_rot, tangent_space_geo * normal_local); - if (dot(normal, normal) == 0.0) { - normal = flat_normal; // fallback - } basis = shortest_arc_quat(vec3(0.0, 0.0, 1.0), normalize(normal)); let hit_position = camera.position + intersection.t * ray_dir; diff --git a/blade-render/src/model/mod.rs b/blade-render/src/model/mod.rs index 1c871521..8eb04a88 100644 --- a/blade-render/src/model/mod.rs +++ b/blade-render/src/model/mod.rs @@ -27,7 +27,9 @@ fn pack4x8snorm(v: [f32; 4]) -> u32 { } fn encode_normal(v: [f32; 3]) -> u32 { - pack4x8snorm([v[0], v[1], v[2], 0.0]) + let raw = pack4x8snorm([v[0], v[1], v[2], 0.0]); + assert_ne!(raw, 0, "Zero normal detected"); + raw } pub struct Geometry { @@ -84,13 +86,23 @@ struct CookedGeometry<'a> { material_index: u32, } -#[derive(Clone, Default, PartialEq)] +#[derive(Clone, PartialEq)] struct GltfVertex { position: [f32; 3], normal: [f32; 3], tangent: [f32; 4], tex_coords: [f32; 2], } +impl Default for GltfVertex { + fn default() -> Self { + Self { + position: [0.0; 3], + normal: [0.0, 1.0, 0.0], + tangent: [1.0, 0.0, 0.0, 0.0], + tex_coords: [0.0; 2], + } + } +} impl Eq for GltfVertex {} impl hash::Hash for GltfVertex { fn hash(&self, state: &mut H) { @@ -144,12 +156,13 @@ impl FlattenedGeometry { Entry::Occupied(e) => *e.get(), Entry::Vacant(e) => { let i = vertices.len() as u32; + let t = &v.tangent; vertices.push(crate::Vertex { position: v.position, - bitangent_sign: v.tangent[3], + bitangent_sign: t[3], tex_coords: v.tex_coords, normal: encode_normal(v.normal), - tangent: encode_normal([v.tangent[0], v.tangent[1], v.tangent[2]]), + tangent: encode_normal([t[0], t[1], t[2]]), }); *e.insert(i) } From 2eccdec304d3efe94ef7851d7eb370da587c369e Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Sat, 16 Dec 2023 21:56:23 -0800 Subject: [PATCH 4/5] Check against NaN positions --- blade-render/src/model/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/blade-render/src/model/mod.rs b/blade-render/src/model/mod.rs index 8eb04a88..c0138da4 100644 --- a/blade-render/src/model/mod.rs +++ b/blade-render/src/model/mod.rs @@ -238,6 +238,9 @@ impl CookedModel<'_> { .iter_mut() .zip(reader.read_positions().unwrap()) { + for component in pos { + assert!(component.is_finite()); + } v.position = pos; } if let Some(iter) = reader.read_tex_coords(0) { From 4bc99eae12b44b48d0bc4dcfaaf61fdfb3985492 Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Sat, 16 Dec 2023 22:52:10 -0800 Subject: [PATCH 5/5] Fix the timestep --- src/config.rs | 6 ++++++ src/lib.rs | 32 +++++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/config.rs b/src/config.rs index 55b964da..2753b41d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -94,8 +94,14 @@ pub struct Object { pub colliders: Vec, } +fn default_time_step() -> f32 { + 0.01 +} + #[derive(serde::Deserialize)] pub struct Engine { pub shader_path: String, pub data_path: String, + #[serde(default = "default_time_step")] + pub time_step: f32, } diff --git a/src/lib.rs b/src/lib.rs index aab32d3d..e35eecf7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,11 +116,12 @@ struct Physics { gravity: rapier3d::math::Vector, pipeline: rapier3d::pipeline::PhysicsPipeline, debug_pipeline: rapier3d::pipeline::DebugRenderPipeline, + last_time: f32, } impl Physics { - fn step(&mut self, dt: f32) { - self.integration_params.dt = dt; + fn step(&mut self) { + let query_pipeline = None; let physics_hooks = (); let event_handler = (); self.pipeline.step( @@ -134,10 +135,11 @@ impl Physics { &mut self.impulse_joints, &mut self.multibody_joints, &mut self.solver, - None, // query pipeline + query_pipeline, &physics_hooks, &event_handler, ); + self.last_time += self.integration_params.dt; } fn render_debug(&mut self) -> Vec { let mut backend = DebugPhysicsRender::default(); @@ -197,6 +199,7 @@ pub struct Engine { workers: Vec, choir: Arc, data_path: String, + time_ahead: f32, } impl ops::Index for Engine { @@ -290,6 +293,7 @@ impl Engine { let gui_painter = blade_egui::GuiPainter::new(surface_format, &gpu_context); let mut physics = Physics::default(); physics.debug_pipeline.mode = rapier3d::pipeline::DebugRenderMode::empty(); + physics.integration_params.dt = config.time_step; Self { pacer, @@ -328,6 +332,7 @@ impl Engine { workers, choir, data_path: config.data_path.clone(), + time_ahead: 0.0, } } @@ -342,7 +347,11 @@ impl Engine { #[profiling::function] pub fn update(&mut self, dt: f32) { self.choir.check_panic(); - self.physics.step(dt); + self.time_ahead += dt; + while self.time_ahead >= self.physics.integration_params.dt { + self.physics.step(); + self.time_ahead -= self.physics.integration_params.dt; + } } #[profiling::function] @@ -396,7 +405,8 @@ impl Engine { .rigid_bodies .get(object.rigid_body) .unwrap() - .position(); + .predict_position_using_velocity_and_forces(self.time_ahead); + for visual in object.visuals.iter() { let mc = (isometry * visual.similarity).to_homogeneous().transpose(); let mp = (object.prev_isometry * visual.similarity) @@ -416,7 +426,7 @@ impl Engine { model: visual.model, }); } - object.prev_isometry = *isometry; + object.prev_isometry = isometry; } // Rebuilding every frame @@ -753,7 +763,15 @@ impl Engine { } } - pub fn get_object_isometry(&self, handle: ObjectHandle) -> &nalgebra::Isometry3 { + pub fn get_object_isometry(&self, handle: ObjectHandle) -> nalgebra::Isometry3 { + let object = &self.objects[handle.0]; + let body = &self.physics.rigid_bodies[object.rigid_body]; + body.predict_position_using_velocity_and_forces(self.time_ahead) + } + + /// Returns the position of the object within the "time_step" of precision. + /// Faster than the main `get_object_isometry`. + pub fn get_object_isometry_approx(&self, handle: ObjectHandle) -> &nalgebra::Isometry3 { let object = &self.objects[handle.0]; let body = &self.physics.rigid_bodies[object.rigid_body]; body.position()