diff options
| author | Sébastien Crozet <developer@crozet.re> | 2022-04-22 15:45:53 +0200 |
|---|---|---|
| committer | Sébastien Crozet <developer@crozet.re> | 2022-04-22 16:11:23 +0200 |
| commit | bc2ae4b512b8bc7a2b61dd24d9685289453681c5 (patch) | |
| tree | 576d950ee18b0a3a2e69a48db5fd2ba14f7bcd39 /src | |
| parent | 21a31bc1026d17d30b3a5ac35e6bb716dc66be6e (diff) | |
| download | rapier-bc2ae4b512b8bc7a2b61dd24d9685289453681c5.tar.gz rapier-bc2ae4b512b8bc7a2b61dd24d9685289453681c5.tar.bz2 rapier-bc2ae4b512b8bc7a2b61dd24d9685289453681c5.zip | |
Add a basic lines-based debug-renderer
Diffstat (limited to 'src')
| -rw-r--r-- | src/pipeline/debug_render_pipeline/debug_render_backend.rs | 66 | ||||
| -rw-r--r-- | src/pipeline/debug_render_pipeline/debug_render_pipeline.rs | 450 | ||||
| -rw-r--r-- | src/pipeline/debug_render_pipeline/debug_render_style.rs | 37 | ||||
| -rw-r--r-- | src/pipeline/debug_render_pipeline/mod.rs | 8 | ||||
| -rw-r--r-- | src/pipeline/debug_render_pipeline/outlines.rs | 36 | ||||
| -rw-r--r-- | src/pipeline/mod.rs | 9 | ||||
| -rw-r--r-- | src/pipeline/physics_pipeline.rs | 11 |
7 files changed, 617 insertions, 0 deletions
diff --git a/src/pipeline/debug_render_pipeline/debug_render_backend.rs b/src/pipeline/debug_render_pipeline/debug_render_backend.rs new file mode 100644 index 0000000..27a67e4 --- /dev/null +++ b/src/pipeline/debug_render_pipeline/debug_render_backend.rs @@ -0,0 +1,66 @@ +use crate::dynamics::{ + ImpulseJoint, ImpulseJointHandle, Multibody, MultibodyLink, RigidBody, RigidBodyHandle, +}; +use crate::geometry::Collider; +use crate::math::{Isometry, Point, Real, Vector}; +use crate::prelude::{ColliderHandle, MultibodyJointHandle}; +use na::Scale; + +#[derive(Copy, Clone)] +pub enum DebugRenderObject<'a> { + RigidBody(RigidBodyHandle, &'a RigidBody), + Collider(ColliderHandle, &'a Collider), + ImpulseJoint(ImpulseJointHandle, &'a ImpulseJoint), + MultibodyJoint(MultibodyJointHandle, &'a Multibody, &'a MultibodyLink), + Other, +} + +pub trait DebugRenderBackend { + fn draw_line( + &mut self, + object: DebugRenderObject, + a: Point<Real>, + b: Point<Real>, + color: [f32; 4], + ); + + fn draw_polyline( + &mut self, + object: DebugRenderObject, + vertices: &[Point<Real>], + indices: &[[u32; 2]], + transform: &Isometry<Real>, + scale: &Vector<Real>, + color: [f32; 4], + ) { + for idx in indices { + let a = transform * (Scale::from(*scale) * vertices[idx[0] as usize]); + let b = transform * (Scale::from(*scale) * vertices[idx[1] as usize]); + self.draw_line(object, a, b, color); + } + } + + fn draw_line_strip( + &mut self, + object: DebugRenderObject, + vertices: &[Point<Real>], + transform: &Isometry<Real>, + scale: &Vector<Real>, + color: [f32; 4], + closed: bool, + ) { + for vtx in vertices.windows(2) { + let a = transform * (Scale::from(*scale) * vtx[0]); + let b = transform * (Scale::from(*scale) * vtx[1]); + self.draw_line(object, a, b, color); + } + + if closed { + if vertices.len() > 2 { + let a = transform * (Scale::from(*scale) * vertices[0]); + let b = transform * (Scale::from(*scale) * vertices.last().unwrap()); + self.draw_line(object, a, b, color); + } + } + } +} diff --git a/src/pipeline/debug_render_pipeline/debug_render_pipeline.rs b/src/pipeline/debug_render_pipeline/debug_render_pipeline.rs new file mode 100644 index 0000000..36593ce --- /dev/null +++ b/src/pipeline/debug_render_pipeline/debug_render_pipeline.rs @@ -0,0 +1,450 @@ +use super::{outlines, DebugRenderBackend}; +use crate::dynamics::{ + GenericJoint, ImpulseJointSet, MultibodyJointSet, RigidBodySet, RigidBodyType, +}; +use crate::geometry::{Ball, ColliderSet, Cuboid, Shape, TypedShape}; +#[cfg(feature = "dim3")] +use crate::geometry::{Cone, Cylinder}; +use crate::math::{Isometry, Point, Real, Vector, DIM}; +use crate::pipeline::debug_render_pipeline::debug_render_backend::DebugRenderObject; +use crate::pipeline::debug_render_pipeline::DebugRenderStyle; +use crate::utils::WBasis; +use std::any::TypeId; +use std::collections::HashMap; + +bitflags::bitflags! { + pub struct DebugRenderMode: u32 { + const COLLIDER_SHAPES = 1 << 0; + const RIGID_BODY_AXES = 1 << 1; + const MULTIBODY_JOINTS = 1 << 2; + const IMPULSE_JOINTS = 1 << 3; + } +} + +pub struct DebugRenderPipeline { + #[cfg(feature = "dim2")] + instances: HashMap<TypeId, Vec<Point<Real>>>, + #[cfg(feature = "dim3")] + instances: HashMap<TypeId, (Vec<Point<Real>>, Vec<[u32; 2]>)>, + pub style: DebugRenderStyle, + pub mode: DebugRenderMode, +} + +impl Default for DebugRenderPipeline { + fn default() -> Self { + Self::render_all(DebugRenderStyle::default()) + } +} + +impl DebugRenderPipeline { + pub fn new(style: DebugRenderStyle, mode: DebugRenderMode) -> Self { + Self { + instances: outlines::instances(style.subdivisions), + style, + mode, + } + } + + pub fn render_all(style: DebugRenderStyle) -> Self { + Self::new(style, DebugRenderMode::all()) + } + + pub fn render( + &mut self, + backend: &mut impl DebugRenderBackend, + bodies: &RigidBodySet, + colliders: &ColliderSet, + impulse_joints: &ImpulseJointSet, + multibody_joints: &MultibodyJointSet, + ) { + self.render_bodies(backend, bodies); + self.render_colliders(backend, bodies, colliders); + self.render_joints(backend, bodies, impulse_joints, multibody_joints); + } + + pub fn render_joints( + &mut self, + backend: &mut impl DebugRenderBackend, + bodies: &RigidBodySet, + impulse_joints: &ImpulseJointSet, + multibody_joints: &MultibodyJointSet, + ) { + let mut render_joint = |body1, + body2, + data: &GenericJoint, + mut anchor_color: [f32; 4], + mut separation_color: [f32; 4], + object| { + if let (Some(rb1), Some(rb2)) = (bodies.get(body1), bodies.get(body2)) { + let coeff = if (rb1.is_fixed() || rb1.is_sleeping()) + && (rb2.is_fixed() || rb2.is_sleeping()) + { + self.style.sleep_color_multiplier + } else { + [1.0; 4] + }; + + let frame1 = rb1.position() * data.local_frame1; + let frame2 = rb2.position() * data.local_frame2; + + let a = *rb1.translation(); + let b = frame1.translation.vector; + let c = frame2.translation.vector; + let d = *rb2.translation(); + + for k in 0..4 { + anchor_color[k] *= coeff[k]; + separation_color[k] *= coeff[k]; + } + + backend.draw_line(object, a.into(), b.into(), anchor_color); + backend.draw_line(object, b.into(), c.into(), separation_color); + backend.draw_line(object, c.into(), d.into(), anchor_color); + } + }; + + if self.mode.contains(DebugRenderMode::IMPULSE_JOINTS) { + for (handle, joint) in impulse_joints.iter() { + let anc_color = self.style.impulse_joint_anchor_color; + let sep_color = self.style.impulse_joint_separation_color; + let object = DebugRenderObject::ImpulseJoint(handle, joint); + render_joint( + joint.body1, + joint.body2, + &joint.data, + anc_color, + sep_color, + object, + ); + } + } + + if self.mode.contains(DebugRenderMode::MULTIBODY_JOINTS) { + for (handle, multibody, link) in multibody_joints.iter() { + let anc_color = self.style.multibody_joint_anchor_color; + let sep_color = self.style.multibody_joint_separation_color; + let parent = multibody.link(link.parent_id().unwrap()).unwrap(); + let object = DebugRenderObject::MultibodyJoint(handle, multibody, link); + render_joint( + parent.rigid_body_handle(), + link.rigid_body_handle(), + &link.joint.data, + anc_color, + sep_color, + object, + ); + } + } + } + + pub fn render_bodies(&mut self, backend: &mut impl DebugRenderBackend, bodies: &RigidBodySet) { + for (handle, rb) in bodies.iter() { + let object = DebugRenderObject::RigidBody(handle, rb); + + if self.style.rigid_body_axes_length != 0.0 + && self.mode.contains(DebugRenderMode::RIGID_BODY_AXES) + { + let basis = rb.rotation().to_rotation_matrix().into_inner(); + let coeff = if rb.is_sleeping() { + self.style.sleep_color_multiplier + } else { + [1.0; 4] + }; + let colors = [ + [0.0 * coeff[0], 1.0 * coeff[1], 0.25 * coeff[2], coeff[3]], + [120.0 * coeff[0], 1.0 * coeff[1], 0.1 * coeff[2], coeff[3]], + [240.0 * coeff[0], 1.0 * coeff[1], 0.2 * coeff[2], coeff[3]], + ]; + let com = rb.mprops.world_com; + + for k in 0..DIM { + let axis = basis.column(k) * self.style.rigid_body_axes_length; + backend.draw_line(object, com, com + axis, colors[k]); + } + } + } + } + + pub fn render_colliders( + &mut self, + backend: &mut impl DebugRenderBackend, + bodies: &RigidBodySet, + colliders: &ColliderSet, + ) { + if self.mode.contains(DebugRenderMode::COLLIDER_SHAPES) { + for (h, co) in colliders.iter() { + let object = DebugRenderObject::Collider(h, co); + let color = if let Some(parent) = co.parent().and_then(|p| bodies.get(p)) { + let coeff = if parent.is_sleeping() { + self.style.sleep_color_multiplier + } else { + [1.0; 4] + }; + let c = match parent.body_type { + RigidBodyType::Fixed => self.style.collider_fixed_color, + RigidBodyType::Dynamic => self.style.collider_dynamic_color, + RigidBodyType::KinematicPositionBased + | RigidBodyType::KinematicVelocityBased => { + self.style.collider_kinematic_color + } + }; + + [ + c[0] * coeff[0], + c[1] * coeff[1], + c[2] * coeff[2], + c[3] * coeff[3], + ] + } else { + self.style.collider_parentless_color + }; + + self.render_shape(object, backend, co.shape(), co.position(), color) + } + } + } + + #[cfg(feature = "dim2")] + fn render_shape( + &mut self, + object: DebugRenderObject, + backend: &mut impl DebugRenderBackend, + shape: &dyn Shape, + pos: &Isometry<Real>, + color: [f32; 4], + ) { + match shape.as_typed_shape() { + TypedShape::Ball(s) => { + let vtx = &self.instances[&TypeId::of::<Ball>()]; + backend.draw_line_strip( + object, + vtx, + pos, + &Vector::repeat(s.radius * 2.0), + color, + true, + ) + } + TypedShape::Cuboid(s) => { + let vtx = &self.instances[&TypeId::of::<Cuboid>()]; + backend.draw_line_strip(object, vtx, pos, &(s.half_extents * 2.0), color, true) + } + TypedShape::Capsule(s) => { + let vtx = s.to_polyline(self.style.subdivisions); + backend.draw_line_strip(object, &vtx, pos, &Vector::repeat(1.0), color, true) + } + TypedShape::Segment(s) => backend.draw_line_strip( + object, + &[s.a, s.b], + pos, + &Vector::repeat(1.0), + color, + false, + ), + TypedShape::Triangle(s) => backend.draw_line_strip( + object, + &[s.a, s.b, s.c], + pos, + &Vector::repeat(1.0), + color, + true, + ), + TypedShape::TriMesh(s) => { + for tri in s.triangles() { + self.render_shape(object, backend, &tri, pos, color) + } + } + TypedShape::Polyline(s) => backend.draw_polyline( + object, + s.vertices(), + s.indices(), + pos, + &Vector::repeat(1.0), + color, + ), + TypedShape::HalfSpace(s) => { + let basis = s.normal.orthonormal_basis()[0]; + let a = Point::from(basis) * 10_000.0; + let b = Point::from(basis) * -10_000.0; + backend.draw_line_strip(object, &[a, b], pos, &Vector::repeat(1.0), color, false) + } + TypedShape::HeightField(s) => { + for seg in s.segments() { + self.render_shape(object, backend, &seg, pos, color) + } + } + TypedShape::Compound(s) => { + for (sub_pos, shape) in s.shapes() { + self.render_shape(object, backend, &**shape, &(pos * sub_pos), color) + } + } + TypedShape::ConvexPolygon(s) => { + backend.draw_line_strip(object, s.points(), pos, &Vector::repeat(1.0), color, true) + } + /* + * Round shapes. + */ + TypedShape::RoundCuboid(s) => { + self.render_shape(object, backend, &s.base_shape, pos, color) + } + TypedShape::RoundTriangle(s) => { + self.render_shape(object, backend, &s.base_shape, pos, color) + } + // TypedShape::RoundTriMesh(s) => self.render_shape(backend, &s.base_shape, pos, color), + // TypedShape::RoundHeightField(s) => { + // self.render_shape(backend, &s.base_shape, pos, color) + // } + TypedShape::RoundConvexPolygon(s) => { + self.render_shape(object, backend, &s.base_shape, pos, color) + } + TypedShape::Custom(_) => {} + } + } + + #[cfg(feature = "dim3")] + fn render_shape( + &mut self, + object: DebugRenderObject, + backend: &mut impl DebugRenderBackend, + shape: &dyn Shape, + pos: &Isometry<Real>, + color: [f32; 4], + ) { + match shape.as_typed_shape() { + TypedShape::Ball(s) => { + let (vtx, idx) = &self.instances[&TypeId::of::<Ball>()]; + backend.draw_polyline( + object, + vtx, + idx, + pos, + &Vector::repeat(s.radius * 2.0), + color, + ) + } + TypedShape::Cuboid(s) => { + let (vtx, idx) = &self.instances[&TypeId::of::<Cuboid>()]; + backend.draw_polyline(object, vtx, idx, pos, &(s.half_extents * 2.0), color) + } + #[cfg(feature = "dim3")] + TypedShape::Capsule(s) => { + let (vtx, idx) = s.to_outline(self.style.subdivisions); + backend.draw_polyline(object, &vtx, &idx, pos, &Vector::repeat(1.0), color) + } + TypedShape::Segment(s) => backend.draw_polyline( + object, + &[s.a, s.b], + &[[0, 1]], + pos, + &Vector::repeat(1.0), + color, + ), + TypedShape::Triangle(s) => backend.draw_line_strip( + object, + &[s.a, s.b, s.c], + pos, + &Vector::repeat(1.0), + color, + true, + ), + TypedShape::TriMesh(s) => { + for tri in s.triangles() { + self.render_shape(object, backend, &tri, pos, color) + } + } + TypedShape::Polyline(s) => backend.draw_polyline( + object, + s.vertices(), + s.indices(), + pos, + &Vector::repeat(1.0), + color, + ), + TypedShape::HalfSpace(s) => { + let basis = s.normal.orthonormal_basis(); + let a = Point::from(basis[0]) * 10_000.0; + let b = Point::from(basis[0]) * -10_000.0; + let c = Point::from(basis[1]) * 10_000.0; + let d = Point::from(basis[1]) * -10_000.0; + backend.draw_polyline( + object, + &[a, b, c, d], + &[[0, 1], [2, 3]], + pos, + &Vector::repeat(1.0), + color, + ) + } + TypedShape::HeightField(s) => { + for tri in s.triangles() { + self.render_shape(object, backend, &tri, pos, color) + } + } + TypedShape::Compound(s) => { + for (sub_pos, shape) in s.shapes() { + self.render_shape(object, backend, &**shape, &(pos * sub_pos), color) + } + } + TypedShape::ConvexPolyhedron(s) => { + let indices: Vec<_> = s + .edges() + .iter() + .map(|e| [e.vertices.x, e.vertices.y]) + .collect(); + backend.draw_polyline( + object, + s.points(), + &indices, + pos, + &Vector::repeat(1.0), + color, + ) + } + TypedShape::Cylinder(s) => { + let (vtx, idx) = &self.instances[&TypeId::of::<Cylinder>()]; + backend.draw_polyline( + object, + vtx, + idx, + pos, + &(Vector::new(s.radius, s.half_height, s.radius) * 2.0), + color, + ) + } + TypedShape::Cone(s) => { + let (vtx, idx) = &self.instances[&TypeId::of::<Cone>()]; + backend.draw_polyline( + object, + vtx, + idx, + pos, + &(Vector::new(s.radius, s.half_height, s.radius) * 2.0), + color, + ) + } + /* + * Round shapes. + */ + TypedShape::RoundCuboid(s) => { + self.render_shape(object, backend, &s.base_shape, pos, color) + } + TypedShape::RoundTriangle(s) => { + self.render_shape(object, backend, &s.base_shape, pos, color) + } + // TypedShape::RoundTriMesh(s) => self.render_shape(object, backend, &s.base_shape, pos, color), + // TypedShape::RoundHeightField(s) => { + // self.render_shape(object, backend, &s.base_shape, pos, color) + // } + TypedShape::RoundCylinder(s) => { + self.render_shape(object, backend, &s.base_shape, pos, color) + } + TypedShape::RoundCone(s) => { + self.render_shape(object, backend, &s.base_shape, pos, color) + } + TypedShape::RoundConvexPolyhedron(s) => { + self.render_shape(object, backend, &s.base_shape, pos, color) + } + TypedShape::Custom(_) => {} + } + } +} diff --git a/src/pipeline/debug_render_pipeline/debug_render_style.rs b/src/pipeline/debug_render_pipeline/debug_render_style.rs new file mode 100644 index 0000000..9d4b6cc --- /dev/null +++ b/src/pipeline/debug_render_pipeline/debug_render_style.rs @@ -0,0 +1,37 @@ +/// A color for debug-rendering. +/// +/// The default colors are provided in HSLA (Hue Saturation Lightness Alpha) format. +pub type DebugColor = [f32; 4]; + +#[derive(Clone, Debug, PartialEq)] +pub struct DebugRenderStyle { + pub subdivisions: u32, + pub collider_dynamic_color: DebugColor, + pub collider_fixed_color: DebugColor, + pub collider_kinematic_color: DebugColor, + pub collider_parentless_color: DebugColor, + pub impulse_joint_anchor_color: DebugColor, + pub impulse_joint_separation_color: DebugColor, + pub multibody_joint_anchor_color: DebugColor, + pub multibody_joint_separation_color: DebugColor, + pub sleep_color_multiplier: [f32; 4], + pub rigid_body_axes_length: f32, +} + +impl Default for DebugRenderStyle { + fn default() -> Self { + Self { + subdivisions: 20, + collider_dynamic_color: [340.0, 1.0, 0.3, 1.0], + collider_kinematic_color: [20.0, 1.0, 0.3, 1.0], + collider_fixed_color: [30.0, 1.0, 0.4, 1.0], + collider_parentless_color: [30.0, 1.0, 0.4, 1.0], + impulse_joint_anchor_color: [240.0, 0.5, 0.4, 1.0], + impulse_joint_separation_color: [0.0, 0.5, 0.4, 1.0], + multibody_joint_anchor_color: [300.0, 1.0, 0.4, 1.0], + multibody_joint_separation_color: [0.0, 1.0, 0.4, 1.0], + sleep_color_multiplier: [1.0, 1.0, 0.2, 1.0], + rigid_body_axes_length: 0.5, + } + } +} diff --git a/src/pipeline/debug_render_pipeline/mod.rs b/src/pipeline/debug_render_pipeline/mod.rs new file mode 100644 index 0000000..104fd9f --- /dev/null +++ b/src/pipeline/debug_render_pipeline/mod.rs @@ -0,0 +1,8 @@ +pub use self::debug_render_backend::{DebugRenderBackend, DebugRenderObject}; +pub use self::debug_render_pipeline::{DebugRenderMode, DebugRenderPipeline}; +pub use self::debug_render_style::{DebugColor, DebugRenderStyle}; + +mod debug_render_backend; +mod debug_render_pipeline; +mod debug_render_style; +pub(self) mod outlines; diff --git a/src/pipeline/debug_render_pipeline/outlines.rs b/src/pipeline/debug_render_pipeline/outlines.rs new file mode 100644 index 0000000..01a441e --- /dev/null +++ b/src/pipeline/debug_render_pipeline/outlines.rs @@ -0,0 +1,36 @@ +use crate::geometry::{Ball, Cuboid}; +#[cfg(feature = "dim3")] +use crate::geometry::{Cone, Cylinder}; +use crate::math::{Point, Real, Vector}; +use std::any::TypeId; +use std::collections::HashMap; + +#[cfg(feature = "dim2")] +pub fn instances(nsubdivs: u32) -> HashMap<TypeId, Vec<Point<Real>>> { + let mut result = HashMap::new(); + result.insert( + TypeId::of::<Cuboid>(), + Cuboid::new(Vector::repeat(0.5)).to_polyline(), + ); + result.insert(TypeId::of::<Ball>(), Ball::new(0.5).to_polyline(nsubdivs)); + result +} + +#[cfg(feature = "dim3")] +pub fn instances(nsubdivs: u32) -> HashMap<TypeId, (Vec<Point<Real>>, Vec<[u32; 2]>)> { + let mut result = HashMap::new(); + result.insert( + TypeId::of::<Cuboid>(), + Cuboid::new(Vector::repeat(0.5)).to_outline(), + ); + result.insert(TypeId::of::<Ball>(), Ball::new(0.5).to_outline(nsubdivs)); + result.insert( + TypeId::of::<Cone>(), + Cone::new(0.5, 0.5).to_outline(nsubdivs), + ); + result.insert( + TypeId::of::<Cylinder>(), + Cylinder::new(0.5, 0.5).to_outline(nsubdivs), + ); + result +} diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs index 2c0393e..fb14e4c 100644 --- a/src/pipeline/mod.rs +++ b/src/pipeline/mod.rs @@ -6,9 +6,18 @@ pub use physics_hooks::{ActiveHooks, ContactModificationContext, PairFilterConte pub use physics_pipeline::PhysicsPipeline; pub use query_pipeline::{QueryPipeline, QueryPipelineMode}; +#[cfg(feature = "debug-render")] +pub use self::debug_render_pipeline::{ + DebugColor, DebugRenderBackend, DebugRenderMode, DebugRenderObject, DebugRenderPipeline, + DebugRenderStyle, +}; + mod collision_pipeline; mod event_handler; mod physics_hooks; mod physics_pipeline; mod query_pipeline; mod user_changes; + +#[cfg(feature = "debug-render")] +mod debug_render_pipeline; diff --git a/src/pipeline/physics_pipeline.rs b/src/pipeline/physics_pipeline.rs index 5727592..012f40f 100644 --- a/src/pipeline/physics_pipeline.rs +++ b/src/pipeline/physics_pipeline.rs @@ -541,6 +541,17 @@ impl PhysicsPipeline { self.clear_modified_colliders(colliders, &mut modified_colliders); } + // Finally, make sure we update the world mass-properties of the rigid-bodies + // that moved. Otherwise, users may end up applying forces wrt. an outdated + // center of mass. + // TODO: avoid updating the world mass properties twice (here, and + // at the beginning of the next timestep) for bodies that were + // not modified by the user in the mean time. + for handle in islands.active_dynamic_bodies() { + let rb = bodies.index_mut_internal(*handle); + rb.mprops.update_world_mass_properties(&rb.pos.position); + } + self.counters.step_completed(); } } |
