use crate::plugin::context::{
RapierContextColliders, RapierContextJoints, RapierContextSimulation, RapierRigidBodySet,
};
use bevy::prelude::*;
use bevy::transform::TransformSystems;
use rapier::math::{Point, Real};
use rapier::pipeline::{DebugRenderBackend, DebugRenderObject, DebugRenderPipeline};
pub use rapier::pipeline::{DebugRenderMode, DebugRenderStyle};
use std::fmt::Debug;
#[cfg(doc)]
use crate::prelude::Collider;
#[derive(Copy, Clone, Component, PartialEq, Debug, Reflect)]
pub struct ColliderDebugColor(pub Hsla);
#[derive(Copy, Clone, Reflect, Component, Eq, PartialEq, Default, Debug)]
pub enum ColliderDebug {
#[default]
AlwaysRender,
NeverRender,
}
pub struct RapierDebugRenderPlugin {
pub default_collider_debug: ColliderDebug,
pub enabled: bool,
pub style: DebugRenderStyle,
pub mode: DebugRenderMode,
}
#[allow(clippy::derivable_impls)] impl Default for RapierDebugRenderPlugin {
#[cfg(feature = "dim2")]
fn default() -> Self {
Self {
enabled: true,
default_collider_debug: ColliderDebug::AlwaysRender,
style: DebugRenderStyle {
rigid_body_axes_length: 20.0,
..Default::default()
},
mode: DebugRenderMode::default(),
}
}
#[cfg(feature = "dim3")]
fn default() -> Self {
Self {
enabled: true,
default_collider_debug: ColliderDebug::AlwaysRender,
style: DebugRenderStyle::default(),
mode: DebugRenderMode::default(),
}
}
}
impl RapierDebugRenderPlugin {
pub fn disabled(mut self) -> Self {
self.enabled = false;
self
}
}
#[derive(Resource, Reflect)]
#[reflect(Resource)]
pub struct DebugRenderContext {
pub enabled: bool,
pub default_collider_debug: ColliderDebug,
#[reflect(ignore)]
pub pipeline: DebugRenderPipeline,
}
impl Default for DebugRenderContext {
fn default() -> Self {
Self {
enabled: true,
default_collider_debug: ColliderDebug::AlwaysRender,
pipeline: DebugRenderPipeline::default(),
}
}
}
impl Plugin for RapierDebugRenderPlugin {
fn build(&self, app: &mut App) {
app.register_type::<DebugRenderContext>();
app.register_type::<ColliderDebug>();
app.register_type::<ColliderDebugColor>();
app.insert_resource(DebugRenderContext {
enabled: self.enabled,
default_collider_debug: self.default_collider_debug,
pipeline: DebugRenderPipeline::new(self.style, self.mode),
})
.add_systems(
PostUpdate,
debug_render_scene.after(TransformSystems::Propagate),
);
}
}
struct BevyLinesRenderBackend<'world, 'state, 'world2, 'state2, 'a, 'c, 'd, 'v, 'p> {
custom_colors: &'c Query<'world, 'state, &'a ColliderDebugColor>,
default_collider_debug: ColliderDebug,
override_visibility: &'v Query<'world, 'state, &'a ColliderDebug>,
context_colliders: &'d RapierContextColliders,
gizmos: &'p mut Gizmos<'world2, 'state2>,
}
impl<'world, 'state, 'world2, 'state2, 'a, 'c, 'd, 'v, 'p>
BevyLinesRenderBackend<'world, 'state, 'world2, 'state2, 'a, 'c, 'd, 'v, 'p>
{
fn object_color(&self, object: DebugRenderObject, default: [f32; 4]) -> [f32; 4] {
let color = match object {
DebugRenderObject::Collider(h, ..) => {
self.context_colliders.colliders.get(h).and_then(|co| {
self.custom_colors
.get(Entity::from_bits(co.user_data as u64))
.map(|co| co.0)
.ok()
})
}
_ => None,
};
color.map(|co: Hsla| co.to_f32_array()).unwrap_or(default)
}
fn drawing_enabled(&self, object: DebugRenderObject) -> bool {
match object {
DebugRenderObject::Collider(h, ..) => {
let Some(collider) = self.context_colliders.colliders.get(h) else {
return false;
};
let entity = Entity::from_bits(collider.user_data as u64);
let collider_debug =
if let Ok(collider_override) = self.override_visibility.get(entity) {
*collider_override
} else {
self.default_collider_debug
};
collider_debug == ColliderDebug::AlwaysRender
}
_ => true,
}
}
}
impl<'world, 'state, 'world2, 'state2, 'a, 'c, 'd, 'v, 'p> DebugRenderBackend
for BevyLinesRenderBackend<'world, 'state, 'world2, 'state2, 'a, 'c, 'd, 'v, 'p>
{
#[cfg(feature = "dim2")]
fn draw_line(
&mut self,
object: DebugRenderObject,
a: Point<Real>,
b: Point<Real>,
color: [f32; 4],
) {
if !self.drawing_enabled(object) {
return;
}
let color = self.object_color(object, color);
self.gizmos.line(
[a.x, a.y, 0.0].into(),
[b.x, b.y, 0.0].into(),
Color::hsla(color[0], color[1], color[2], color[3]),
)
}
#[cfg(feature = "dim3")]
fn draw_line(
&mut self,
object: DebugRenderObject,
a: Point<Real>,
b: Point<Real>,
color: [f32; 4],
) {
if !self.drawing_enabled(object) {
return;
}
let color = self.object_color(object, color);
self.gizmos.line(
[a.x, a.y, a.z].into(),
[b.x, b.y, b.z].into(),
Color::hsla(color[0], color[1], color[2], color[3]),
)
}
}
fn debug_render_scene<'a>(
rapier_context: Query<(
&RapierContextSimulation,
&RapierContextColliders,
&RapierContextJoints,
&RapierRigidBodySet,
)>,
mut render_context: ResMut<DebugRenderContext>,
mut gizmos: Gizmos,
custom_colors: Query<&'a ColliderDebugColor>,
override_visibility: Query<&'a ColliderDebug>,
) {
if !render_context.enabled {
return;
}
for (rapier_context, rapier_context_colliders, joints, rigidbody_set) in rapier_context.iter() {
let mut backend = BevyLinesRenderBackend {
custom_colors: &custom_colors,
default_collider_debug: render_context.default_collider_debug,
override_visibility: &override_visibility,
context_colliders: rapier_context_colliders,
gizmos: &mut gizmos,
};
let unscaled_style = render_context.pipeline.style;
render_context.pipeline.render(
&mut backend,
&rigidbody_set.bodies,
&rapier_context_colliders.colliders,
&joints.impulse_joints,
&joints.multibody_joints,
&rapier_context.narrow_phase,
);
render_context.pipeline.style = unscaled_style;
}
}