From 8b34ccb932aac495906ae7e12bc2a406fa633e8f Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Tue, 30 Jul 2024 17:47:37 +0200 Subject: [PATCH 1/5] add an example for intersection plane --- crates/parry3d/Cargo.toml | 17 ++- crates/parry3d/examples/plane_intersection.rs | 142 ++++++++++++++++++ 2 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 crates/parry3d/examples/plane_intersection.rs diff --git a/crates/parry3d/Cargo.toml b/crates/parry3d/Cargo.toml index 62545af3..8a9e398e 100644 --- a/crates/parry3d/Cargo.toml +++ b/crates/parry3d/Cargo.toml @@ -22,11 +22,23 @@ workspace = true [features] default = ["required-features", "std"] required-features = ["dim3", "f32"] -std = ["nalgebra/std", "slab", "rustc-hash", "simba/std", "arrayvec/std", "spade", "thiserror"] +std = [ + "nalgebra/std", + "slab", + "rustc-hash", + "simba/std", + "arrayvec/std", + "spade", + "thiserror", +] dim3 = [] f32 = [] serde-serialize = ["serde", "nalgebra/serde-serialize", "bitflags/serde"] -rkyv-serialize = ["rkyv/validation", "nalgebra/rkyv-serialize", "simba/rkyv-serialize"] +rkyv-serialize = [ + "rkyv/validation", + "nalgebra/rkyv-serialize", + "simba/rkyv-serialize", +] bytemuck-serialize = ["bytemuck", "nalgebra/convert-bytemuck"] simd-stable = ["simba/wide", "simd-is-enabled"] @@ -76,3 +88,4 @@ obj = { version = "0.10.2", optional = true } oorandom = "11" ptree = "0.4.0" rand = { version = "0.8" } +macroquad = "0.4" diff --git a/crates/parry3d/examples/plane_intersection.rs b/crates/parry3d/examples/plane_intersection.rs new file mode 100644 index 00000000..a796e1b9 --- /dev/null +++ b/crates/parry3d/examples/plane_intersection.rs @@ -0,0 +1,142 @@ +use macroquad::models::Vertex; +use macroquad::prelude::*; +use nalgebra::{Point3, UnitVector3, Vector3}; +use parry3d::math::{Isometry, Real}; +use parry3d::query::IntersectResult; +use parry3d::shape::TriMesh; + +fn build_diamond(position: &Isometry) -> (Vec>, Vec<[u32; 3]>) { + // Two tetrahedrons sharing a face + let points = vec![ + position * Point3::new(0.0, 2.0, 0.0), + position * Point3::new(-2.0, -1.0, 0.0), + position * Point3::new(0.0, 0.0, 2.0), + position * Point3::new(2.0, -1.0, 0.0), + position * Point3::new(0.0, 0.0, -2.0), + ]; + + let indices = vec![ + [0u32, 1, 2], + [0, 2, 3], + [1, 2, 3], + [0, 1, 4], + [0, 4, 3], + [1, 4, 3], + ]; + + (points, indices) +} + +#[macroquad::main("parry3d::query::PlaneIntersection")] +async fn main() { + // + // This is useful to test for https://github.com/dimforge/parry/pull/248 + let _points = vec![ + Point3::from([0.0, 0.0, 0.0]), + Point3::from([0.0, 0.0, 1.0]), + Point3::from([1.0, 0.0, 0.0]), + Point3::from([1.0, 0.0, 1.0]), + ]; + let _indices: Vec<[u32; 3]> = vec![[0, 1, 2], [1, 3, 2]]; + // + // + + let (points, indices) = build_diamond(&Isometry::identity()); + + let mesh = Mesh { + vertices: points + .iter() + .map(|p| Vertex { + position: Vec3::new(p.x, p.y, p.z), + uv: Vec2::new(p.x, p.y), + color: WHITE, + }) + .collect(), + indices: indices.as_flattened().iter().map(|v| *v as u16).collect(), + texture: None, + }; + let trimesh = TriMesh::new(points, indices); + + for _i in 1.. { + clear_background(BLACK); + + let elapsed_time = get_time(); + + let bias = -2.0 * (elapsed_time as f32 / 3f32).sin(); + let rotation = Quat::from_axis_angle(Vec3::Z, (elapsed_time as f32 * 50f32).to_radians()); + let up_plane_vector = rotation * Vec3::Y; + let intersection_result = trimesh.intersection_with_local_plane( + &UnitVector3::new_normalize(Vector3::::new( + up_plane_vector.x, + up_plane_vector.y, + up_plane_vector.z, + )), + bias, + 0.0005, + ); + + // Going 3d! + set_camera(&Camera3D { + position: Vec3::new(0f32, 3f32, -3f32), + up: Vec3::new(0f32, 1f32, 0f32), + target: Vec3::new(0.5f32, 0f32, 0.5f32), + ..Default::default() + }); + + let plane_center = up_plane_vector * bias; + draw_line_3d(plane_center, plane_center + up_plane_vector, GREEN); + draw_mesh(&mesh); + draw_grid_ex(10, 0.333, BLUE, RED, plane_center, rotation); + + /* + * + * Render the intersection + * + */ + match intersection_result { + IntersectResult::Intersect(points) => { + draw_polyline( + points + .segments() + .map(|s| { + ( + Vec3::new(s.a.x, s.a.y, s.a.z), + Vec3::new(s.b.x, s.b.y, s.b.z), + ) + }) + .collect(), + Color::new(0f32, 1f32, 0f32, 1f32), + ); + } + IntersectResult::Negative => { + set_default_camera(); + draw_text( + format!("No intersection found, the plane goes below the shape.").as_str(), + 10.0, + 48.0 + 18.0, + 30.0, + WHITE, + ); + } + IntersectResult::Positive => { + set_default_camera(); + draw_text( + format!("No intersection found, the plane goes above the shape.").as_str(), + 10.0, + 48.0 + 18.0, + 30.0, + WHITE, + ); + } + } + next_frame().await + } +} + +fn draw_polyline(polygon: Vec<(Vec3, Vec3)>, color: Color) { + for i in 0..polygon.len() { + let a = polygon[i].0; + let b = polygon[i].1; + draw_line_3d(Vec3::new(a.x, a.y, a.z), Vec3::new(b.x, b.y, b.z), color); + } +} From ec139832fdce6d23ce5181420b9b024272a4206e Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Tue, 30 Jul 2024 18:00:48 +0200 Subject: [PATCH 2/5] fix flattened --- crates/parry3d/examples/plane_intersection.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/parry3d/examples/plane_intersection.rs b/crates/parry3d/examples/plane_intersection.rs index a796e1b9..bc413815 100644 --- a/crates/parry3d/examples/plane_intersection.rs +++ b/crates/parry3d/examples/plane_intersection.rs @@ -52,7 +52,7 @@ async fn main() { color: WHITE, }) .collect(), - indices: indices.as_flattened().iter().map(|v| *v as u16).collect(), + indices: indices.iter().flatten().map(|v| *v as u16).collect(), texture: None, }; let trimesh = TriMesh::new(points, indices); @@ -111,7 +111,7 @@ async fn main() { IntersectResult::Negative => { set_default_camera(); draw_text( - format!("No intersection found, the plane goes below the shape.").as_str(), + format!("No intersection found, the shape is below the plane.").as_str(), 10.0, 48.0 + 18.0, 30.0, @@ -121,7 +121,7 @@ async fn main() { IntersectResult::Positive => { set_default_camera(); draw_text( - format!("No intersection found, the plane goes above the shape.").as_str(), + format!("No intersection found, the shape is above the plane.").as_str(), 10.0, 48.0 + 18.0, 30.0, From 358e6b14f3b4bceb6fd5bac52b47d9e90ece9dd1 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Wed, 31 Jul 2024 11:33:09 +0200 Subject: [PATCH 3/5] helper function + removed an indirection --- crates/parry3d/examples/plane_intersection.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/parry3d/examples/plane_intersection.rs b/crates/parry3d/examples/plane_intersection.rs index bc413815..58554c0a 100644 --- a/crates/parry3d/examples/plane_intersection.rs +++ b/crates/parry3d/examples/plane_intersection.rs @@ -47,7 +47,7 @@ async fn main() { vertices: points .iter() .map(|p| Vertex { - position: Vec3::new(p.x, p.y, p.z), + position: mquad_from_na(*p), uv: Vec2::new(p.x, p.y), color: WHITE, }) @@ -98,12 +98,7 @@ async fn main() { draw_polyline( points .segments() - .map(|s| { - ( - Vec3::new(s.a.x, s.a.y, s.a.z), - Vec3::new(s.b.x, s.b.y, s.b.z), - ) - }) + .map(|s| (mquad_from_na(s.a), mquad_from_na(s.b))) .collect(), Color::new(0f32, 1f32, 0f32, 1f32), ); @@ -137,6 +132,10 @@ fn draw_polyline(polygon: Vec<(Vec3, Vec3)>, color: Color) { for i in 0..polygon.len() { let a = polygon[i].0; let b = polygon[i].1; - draw_line_3d(Vec3::new(a.x, a.y, a.z), Vec3::new(b.x, b.y, b.z), color); + draw_line_3d(a, b, color); } } + +fn mquad_from_na(a: Point3) -> Vec3 { + Vec3::new(a.x, a.y, a.z) +} From d06491b9fe7696d527d37bfa8675c7b7d9bcd108 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Mon, 5 Aug 2024 16:01:12 +0200 Subject: [PATCH 4/5] better looking shape (shadows), simpler geometry. --- crates/parry3d/examples/plane_intersection.rs | 155 ++++++++++-------- 1 file changed, 85 insertions(+), 70 deletions(-) diff --git a/crates/parry3d/examples/plane_intersection.rs b/crates/parry3d/examples/plane_intersection.rs index 58554c0a..638c7818 100644 --- a/crates/parry3d/examples/plane_intersection.rs +++ b/crates/parry3d/examples/plane_intersection.rs @@ -1,70 +1,30 @@ use macroquad::models::Vertex; use macroquad::prelude::*; use nalgebra::{Point3, UnitVector3, Vector3}; -use parry3d::math::{Isometry, Real}; +use parry3d::math::Real; use parry3d::query::IntersectResult; -use parry3d::shape::TriMesh; - -fn build_diamond(position: &Isometry) -> (Vec>, Vec<[u32; 3]>) { - // Two tetrahedrons sharing a face - let points = vec![ - position * Point3::new(0.0, 2.0, 0.0), - position * Point3::new(-2.0, -1.0, 0.0), - position * Point3::new(0.0, 0.0, 2.0), - position * Point3::new(2.0, -1.0, 0.0), - position * Point3::new(0.0, 0.0, -2.0), - ]; - - let indices = vec![ - [0u32, 1, 2], - [0, 2, 3], - [1, 2, 3], - [0, 1, 4], - [0, 4, 3], - [1, 4, 3], - ]; - - (points, indices) -} +use parry3d::shape::{Cuboid, TriMesh}; #[macroquad::main("parry3d::query::PlaneIntersection")] async fn main() { - // - // This is useful to test for https://github.com/dimforge/parry/pull/248 - let _points = vec![ - Point3::from([0.0, 0.0, 0.0]), - Point3::from([0.0, 0.0, 1.0]), - Point3::from([1.0, 0.0, 0.0]), - Point3::from([1.0, 0.0, 1.0]), - ]; - let _indices: Vec<[u32; 3]> = vec![[0, 1, 2], [1, 3, 2]]; - // - // - - let (points, indices) = build_diamond(&Isometry::identity()); + let trimesh = Cuboid::new(Vector3::new(1.0, 1.0, 1.0)).to_trimesh(); - let mesh = Mesh { - vertices: points - .iter() - .map(|p| Vertex { - position: mquad_from_na(*p), - uv: Vec2::new(p.x, p.y), - color: WHITE, - }) - .collect(), - indices: indices.iter().flatten().map(|v| *v as u16).collect(), - texture: None, - }; - let trimesh = TriMesh::new(points, indices); + let camera_pos = Vec3::new(-1.5f32, 2.5f32, -3f32); + + let mesh = mquad_mesh_from_points(&trimesh, camera_pos); + let trimesh = TriMesh::new(trimesh.0, trimesh.1); - for _i in 1.. { + for _ in 1.. { clear_background(BLACK); let elapsed_time = get_time(); - let bias = -2.0 * (elapsed_time as f32 / 3f32).sin(); - let rotation = Quat::from_axis_angle(Vec3::Z, (elapsed_time as f32 * 50f32).to_radians()); + // Animated rotation for the intersection plane. + let bias = -1.2 * (elapsed_time as f32 / 3f32).sin(); + let rotation = Quat::from_axis_angle(Vec3::Z, (elapsed_time as f32 * 40f32).to_radians()); let up_plane_vector = rotation * Vec3::Y; + + // Get the intersection polyline. let intersection_result = trimesh.intersection_with_local_plane( &UnitVector3::new_normalize(Vector3::::new( up_plane_vector.x, @@ -75,14 +35,15 @@ async fn main() { 0.0005, ); - // Going 3d! + // Initialize 3D camera. set_camera(&Camera3D { - position: Vec3::new(0f32, 3f32, -3f32), + position: camera_pos, up: Vec3::new(0f32, 1f32, 0f32), target: Vec3::new(0.5f32, 0f32, 0.5f32), ..Default::default() }); + // Draw involved shapes. let plane_center = up_plane_vector * bias; draw_line_3d(plane_center, plane_center + up_plane_vector, GREEN); draw_mesh(&mesh); @@ -90,7 +51,7 @@ async fn main() { /* * - * Render the intersection + * Render the intersection. * */ match intersection_result { @@ -102,32 +63,82 @@ async fn main() { .collect(), Color::new(0f32, 1f32, 0f32, 1f32), ); + set_default_camera(); + draw_text("Intersection found!"); } IntersectResult::Negative => { set_default_camera(); - draw_text( - format!("No intersection found, the shape is below the plane.").as_str(), - 10.0, - 48.0 + 18.0, - 30.0, - WHITE, - ); + draw_text("No intersection found, the shape is below the plane."); } IntersectResult::Positive => { set_default_camera(); - draw_text( - format!("No intersection found, the shape is above the plane.").as_str(), - 10.0, - 48.0 + 18.0, - 30.0, - WHITE, - ); + draw_text("No intersection found, the shape is above the plane."); } } next_frame().await } } +fn mquad_mesh_from_points(trimesh: &(Vec>, Vec<[u32; 3]>), camera_pos: Vec3) -> Mesh { + let (points, indices) = trimesh; + // Transform the parry mesh into a mquad Mesh + let (mquad_points, mquad_indices) = ( + points + .iter() + .map(|p| Vertex { + position: mquad_from_na(*p), + uv: Vec2::new(p.x, p.y), + color: DARKGRAY, + }) + .collect(), + indices.iter().flatten().map(|v| *v as u16).collect(), + ); + + // Macroquad doesn´t support adding normals to vertices, so we'll bake a color into these vertices. + // See https://github.com/not-fl3/macroquad/issues/321. + + // Compute the normal of each vertex, making them unique + let vertices: Vec = mquad_compute_normals(&mquad_points, &mquad_indices, camera_pos); + // Regenerate the index for each vertex. + let indices: Vec = (0..vertices.len() * 3) + .into_iter() + .map(|i| i as u16) + .collect(); + let mesh = Mesh { + vertices, + indices, + texture: None, + }; + mesh +} + +fn mquad_compute_normals(points: &Vec, indices: &Vec, cam_pos: Vec3) -> Vec { + let mut vertices: Vec = Vec::::new(); + for indices in indices.chunks(3) { + let v0 = &points[indices[0] as usize]; + let v1 = &points[indices[1] as usize]; + let v2 = &points[indices[2] as usize]; + let normal = (v0.position - v2.position) + .cross(v1.position - v2.position) + .normalize(); + let brightness_mod = 0.2 + (0.8 / 2.) * (normal.dot(cam_pos) + 1.); + + for &i in indices.iter() { + let mut color = points[i as usize].color; + color.r *= brightness_mod; + color.g *= brightness_mod; + color.b *= brightness_mod; + + vertices.push(Vertex { + position: points[i as usize].position, + uv: Vec2::ZERO, + color: color, + }); + } + } + vertices +} + fn draw_polyline(polygon: Vec<(Vec3, Vec3)>, color: Color) { for i in 0..polygon.len() { let a = polygon[i].0; @@ -139,3 +150,7 @@ fn draw_polyline(polygon: Vec<(Vec3, Vec3)>, color: Color) { fn mquad_from_na(a: Point3) -> Vec3 { Vec3::new(a.x, a.y, a.z) } + +fn draw_text(text: &str) { + macroquad::text::draw_text(text, 10.0, 48.0 + 18.0, 30.0, WHITE); +} From b7128e5bc626cbc3ea4bca9c71fc517704973cbc Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Fri, 9 Aug 2024 11:52:13 +0200 Subject: [PATCH 5/5] pr feedbacks --- crates/parry3d/examples/plane_intersection.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/parry3d/examples/plane_intersection.rs b/crates/parry3d/examples/plane_intersection.rs index 638c7818..cc73fc8b 100644 --- a/crates/parry3d/examples/plane_intersection.rs +++ b/crates/parry3d/examples/plane_intersection.rs @@ -7,7 +7,7 @@ use parry3d::shape::{Cuboid, TriMesh}; #[macroquad::main("parry3d::query::PlaneIntersection")] async fn main() { - let trimesh = Cuboid::new(Vector3::new(1.0, 1.0, 1.0)).to_trimesh(); + let trimesh = Cuboid::new(Vector3::repeat(1.0)).to_trimesh(); let camera_pos = Vec3::new(-1.5f32, 2.5f32, -3f32); @@ -26,7 +26,7 @@ async fn main() { // Get the intersection polyline. let intersection_result = trimesh.intersection_with_local_plane( - &UnitVector3::new_normalize(Vector3::::new( + &UnitVector3::new_normalize(Vector3::new( up_plane_vector.x, up_plane_vector.y, up_plane_vector.z,