use super::backend::{DebugLine, DebugPoint, LineCollector, hsla_to_rgba, push_box};
use super::mpm::{MpmDebugRenderMode, render_grid, render_particles};
use crate::rapier::pipeline::{DebugRenderMode, DebugRenderPipeline, DebugRenderStyle};
use crate::rapier::prelude::{ColliderSet, RigidBodyHandle, RigidBodySet};
use khal::backend::{Backend, GpuBackend};
use nexus::rbd::math::Pose;
use nexus::state::NexusState;
#[derive(Clone, Debug, PartialEq)]
pub struct DebugRenderSettings {
pub enabled: bool,
pub mode: DebugRenderMode,
pub mpm_mode: MpmDebugRenderMode,
pub line_width: f32,
pub point_size: f32,
pub contact_normal_length: f32,
pub rigid_body_axes_length: f32,
pub mpm_velocity_scale: f32,
pub hide_regular_rendering: bool,
pub lbvh: bool,
pub lbvh_depth: u32,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum LbvhStatus {
#[default]
Off,
BruteForce,
Tree {
max_depth: u32,
},
}
impl Default for DebugRenderSettings {
fn default() -> Self {
let style = DebugRenderStyle::default();
Self {
enabled: false,
mode: DebugRenderMode::CONTACTS
| DebugRenderMode::JOINTS
| DebugRenderMode::RIGID_BODY_AXES,
mpm_mode: MpmDebugRenderMode::GRID_BLOCKS,
line_width: 2.0,
point_size: 6.0,
contact_normal_length: style.contact_normal_length,
rigid_body_axes_length: style.rigid_body_axes_length,
mpm_velocity_scale: 0.1,
hide_regular_rendering: false,
lbvh: false,
lbvh_depth: 0,
}
}
}
fn light_background_style() -> DebugRenderStyle {
DebugRenderStyle {
collider_dynamic_color: [340.0, 1.0, 0.35, 1.0],
collider_kinematic_color: [20.0, 1.0, 0.35, 1.0],
collider_fixed_color: [30.0, 0.9, 0.3, 1.0],
collider_parentless_color: [30.0, 0.9, 0.3, 1.0],
impulse_joint_anchor_color: [240.0, 0.9, 0.4, 1.0],
impulse_joint_separation_color: [0.0, 0.9, 0.4, 1.0],
multibody_joint_anchor_color: [290.0, 0.9, 0.4, 1.0],
multibody_joint_separation_color: [0.0, 0.9, 0.4, 1.0],
contact_depth_color: [140.0, 1.0, 0.3, 1.0],
contact_normal_color: [210.0, 1.0, 0.4, 1.0],
collider_aabb_color: [270.0, 0.8, 0.45, 1.0],
..DebugRenderStyle::default()
}
}
struct WorldMirror {
bodies: RigidBodySet,
colliders: ColliderSet,
}
pub struct DebugRenderer {
pipeline: DebugRenderPipeline,
mirrors: Vec<WorldMirror>,
mirrored_counts: Vec<(usize, usize)>,
lines: Vec<DebugLine>,
points: Vec<DebugPoint>,
lbvh_status: LbvhStatus,
}
const LBVH_NODE_COLOR: [f32; 4] = [0.0, 0.45, 0.45, 1.0];
const LBVH_LEAF_COLOR: [f32; 4] = [0.75, 0.4, 0.0, 1.0];
impl Default for DebugRenderer {
fn default() -> Self {
Self {
pipeline: DebugRenderPipeline::new(
light_background_style(),
DebugRenderMode::default(),
),
mirrors: Vec::new(),
mirrored_counts: Vec::new(),
lines: Vec::new(),
points: Vec::new(),
lbvh_status: LbvhStatus::Off,
}
}
}
impl DebugRenderer {
pub fn lines(&self) -> &[DebugLine] {
&self.lines
}
pub fn points(&self) -> &[DebugPoint] {
&self.points
}
pub fn lbvh_status(&self) -> LbvhStatus {
self.lbvh_status
}
pub fn clear_scene(&mut self) {
self.mirrors.clear();
self.mirrored_counts.clear();
self.lines.clear();
self.points.clear();
}
pub async fn sync(
&mut self,
state: &NexusState,
backend: &GpuBackend,
settings: &DebugRenderSettings,
) {
self.lines.clear();
self.points.clear();
self.lbvh_status = LbvhStatus::Off;
if !settings.enabled {
return;
}
if let Some(rbd) = state.rbd.as_ref()
&& settings.lbvh
{
self.render_lbvh(rbd, backend, settings).await;
}
if let Some(rbd) = state.rbd.as_ref()
&& !settings.mode.is_empty()
{
self.pipeline.mode = settings.mode;
self.pipeline.style.contact_normal_length = settings.contact_normal_length;
self.pipeline.style.rigid_body_axes_length = settings.rigid_body_axes_length;
let wireframe = DebugRenderMode::COLLIDER_SHAPES
| DebugRenderMode::COLLIDER_AABBS
| DebugRenderMode::RIGID_BODY_AXES
| DebugRenderMode::JOINTS;
if settings.mode.intersects(wireframe) {
let poses = backend
.slow_read_vec::<Pose>(rbd.body_poses().buffer())
.await
.unwrap_or_default();
self.sync_mirrors(state, &poses);
self.render_wireframes(state);
}
let contact_modes = DebugRenderMode::CONTACTS | DebugRenderMode::SOLVER_CONTACTS;
if settings.mode.intersects(contact_modes) {
self.render_contacts(rbd, backend, settings).await;
}
}
if let Some(mpm) = state.mpm.as_ref()
&& !settings.mpm_mode.is_empty()
{
self.render_mpm(mpm, backend, settings).await;
}
}
async fn render_lbvh(
&mut self,
rbd: &nexus::rbd::pipeline::RbdState,
backend: &GpuBackend,
settings: &DebugRenderSettings,
) {
let Some(nodes) = rbd.debug_lbvh(backend).await else {
self.lbvh_status = LbvhStatus::BruteForce;
return;
};
let max_depth = nodes.iter().map(|n| n.depth).max().unwrap_or(0);
let depth = settings.lbvh_depth.min(max_depth);
for node in nodes
.iter()
.filter(|n| n.depth == depth || (n.leaf && n.depth < depth))
{
let color = if node.leaf {
LBVH_LEAF_COLOR
} else {
LBVH_NODE_COLOR
};
push_box(node.mins, node.maxs, color, &mut self.lines);
}
self.lbvh_status = LbvhStatus::Tree { max_depth };
}
async fn render_mpm(
&mut self,
mpm: &nexus::mpm::pipeline::MpmState,
backend: &GpuBackend,
settings: &DebugRenderSettings,
) {
if settings.mpm_mode.needs_particles() {
let particles = mpm.debug_particles(backend).await;
render_particles(
&particles,
settings.mpm_mode,
settings.mpm_velocity_scale,
&mut self.lines,
&mut self.points,
);
}
if settings.mpm_mode.needs_grid() {
let grid = mpm.debug_grid(backend).await;
render_grid(
&grid,
settings.mpm_mode,
settings.mpm_velocity_scale,
&mut self.lines,
&mut self.points,
);
}
}
fn sync_mirrors(&mut self, state: &NexusState, poses: &[Pose]) {
let counts: Vec<(usize, usize)> = (0..state.num_environments())
.map(|env| {
let world = state.rbd_world(env);
(world.bodies.len(), world.colliders.len())
})
.collect();
if counts != self.mirrored_counts {
self.mirrors = (0..counts.len())
.map(|env| {
let world = state.rbd_world(env);
WorldMirror {
bodies: world.bodies.clone(),
colliders: world.colliders.clone(),
}
})
.collect();
self.mirrored_counts = counts;
}
for (env, mirror) in self.mirrors.iter_mut().enumerate() {
let Some(map) = state.rbd2gpu.get(env) else {
continue;
};
let pose_of = |handle: RigidBodyHandle| {
map.get(handle.0)
.map(|r| r.gpu_id)
.filter(|id| *id != u32::MAX)
.and_then(|id| poses.get(id as usize))
.copied()
};
for (handle, rb) in mirror.bodies.iter_mut() {
if let Some(pose) = pose_of(handle) {
rb.set_position(pose, false);
}
}
for (_, co) in mirror.colliders.iter_mut() {
let Some(parent) = co.parent() else { continue };
let local = co.position_wrt_parent().copied().unwrap_or_default();
if let Some(pose) = pose_of(parent) {
co.set_position(pose * local);
}
}
}
}
fn render_wireframes(&mut self, state: &NexusState) {
let mut collector = LineCollector::default();
for (env, mirror) in self.mirrors.iter().enumerate() {
self.pipeline
.render_rigid_bodies(&mut collector, &mirror.bodies);
self.pipeline
.render_colliders(&mut collector, &mirror.bodies, &mirror.colliders);
let world = state.rbd_world(env);
self.pipeline.render_joints(
&mut collector,
&mirror.bodies,
&world.impulse_joints,
&world.multibody_joints,
);
}
self.lines.append(&mut collector.lines);
}
async fn render_contacts(
&mut self,
rbd: &nexus::rbd::pipeline::RbdState,
backend: &GpuBackend,
settings: &DebugRenderSettings,
) {
let contacts = rbd.debug_contacts(backend).await;
let depth_color = hsla_to_rgba(self.pipeline.style.contact_depth_color);
let normal_color = hsla_to_rgba(self.pipeline.style.contact_normal_color);
let normal_len = settings.contact_normal_length;
for contact in &contacts {
if settings.mode.contains(DebugRenderMode::CONTACTS) {
self.lines.push(DebugLine {
a: contact.point,
b: contact.point_b(),
color: depth_color,
});
self.lines.push(DebugLine {
a: contact.point,
b: contact.point + contact.normal * normal_len,
color: normal_color,
});
self.points.push(DebugPoint {
point: contact.point,
color: depth_color,
});
}
if settings.mode.contains(DebugRenderMode::SOLVER_CONTACTS) {
let point = contact.solver_point();
self.lines.push(DebugLine {
a: point,
b: point + contact.normal * normal_len,
color: normal_color,
});
self.points.push(DebugPoint {
point,
color: normal_color,
});
}
}
}
}