1use crate::collider::AABB;
13use crate::math::{Fix128, QuatFix, Vec3Fix};
14
15#[cfg(not(feature = "std"))]
16use alloc::vec::Vec;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub struct DebugColor {
21 pub r: u8,
23 pub g: u8,
25 pub b: u8,
27 pub a: u8,
29}
30
31impl DebugColor {
32 #[must_use]
34 pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
35 Self { r, g, b, a }
36 }
37
38 pub const RED: Self = Self::new(255, 50, 50, 255);
40 pub const GREEN: Self = Self::new(50, 255, 50, 255);
42 pub const BLUE: Self = Self::new(50, 50, 255, 255);
44 pub const YELLOW: Self = Self::new(255, 255, 50, 255);
46 pub const CYAN: Self = Self::new(50, 255, 255, 255);
48 pub const MAGENTA: Self = Self::new(255, 50, 255, 255);
50 pub const WHITE: Self = Self::new(255, 255, 255, 255);
52 pub const GRAY: Self = Self::new(128, 128, 128, 255);
54 pub const ORANGE: Self = Self::new(255, 165, 0, 255);
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63#[non_exhaustive]
64pub struct DebugLine {
65 pub start: Vec3Fix,
67 pub end: Vec3Fix,
69 pub color: DebugColor,
71}
72
73impl DebugLine {
74 #[must_use]
77 pub const fn new(start: Vec3Fix, end: Vec3Fix, color: DebugColor) -> Self {
78 Self { start, end, color }
79 }
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
87#[non_exhaustive]
88pub struct DebugPoint {
89 pub position: Vec3Fix,
91 pub color: DebugColor,
93 pub size: Fix128,
95}
96
97impl DebugPoint {
98 #[must_use]
101 pub const fn new(position: Vec3Fix, color: DebugColor, size: Fix128) -> Self {
102 Self {
103 position,
104 color,
105 size,
106 }
107 }
108}
109
110#[derive(Clone, Copy, Debug, PartialEq, Eq)]
112#[allow(clippy::struct_excessive_bools)]
113pub struct DebugDrawFlags {
114 pub draw_aabbs: bool,
119 pub draw_centers: bool,
121 pub draw_velocities: bool,
123 pub draw_contacts: bool,
125 pub draw_contact_normals: bool,
127 pub draw_joints: bool,
130 pub draw_bvh: bool,
135 pub draw_axes: bool,
137}
138
139impl Default for DebugDrawFlags {
140 fn default() -> Self {
141 Self {
142 draw_aabbs: true,
143 draw_centers: true,
144 draw_velocities: false,
145 draw_contacts: true,
146 draw_contact_normals: true,
147 draw_joints: true,
148 draw_bvh: false,
149 draw_axes: false,
150 }
151 }
152}
153
154#[derive(Clone, Debug, Default)]
160#[non_exhaustive]
161pub struct DebugDrawData {
162 pub lines: Vec<DebugLine>,
164 pub points: Vec<DebugPoint>,
166}
167
168impl DebugDrawData {
169 #[must_use]
171 pub const fn new() -> Self {
172 Self {
173 lines: Vec::new(),
174 points: Vec::new(),
175 }
176 }
177
178 pub fn clear(&mut self) {
180 self.lines.clear();
181 self.points.clear();
182 }
183
184 #[inline]
186 pub fn line(&mut self, start: Vec3Fix, end: Vec3Fix, color: DebugColor) {
187 self.lines.push(DebugLine::new(start, end, color));
188 }
189
190 #[inline]
192 pub fn point(&mut self, position: Vec3Fix, color: DebugColor, size: Fix128) {
193 self.points.push(DebugPoint::new(position, color, size));
194 }
195
196 pub fn aabb(&mut self, aabb: &AABB, color: DebugColor) {
198 let min = aabb.min;
199 let max = aabb.max;
200
201 let corners = [
202 Vec3Fix::new(min.x, min.y, min.z),
203 Vec3Fix::new(max.x, min.y, min.z),
204 Vec3Fix::new(max.x, max.y, min.z),
205 Vec3Fix::new(min.x, max.y, min.z),
206 Vec3Fix::new(min.x, min.y, max.z),
207 Vec3Fix::new(max.x, min.y, max.z),
208 Vec3Fix::new(max.x, max.y, max.z),
209 Vec3Fix::new(min.x, max.y, max.z),
210 ];
211
212 self.line(corners[0], corners[1], color);
214 self.line(corners[1], corners[2], color);
215 self.line(corners[2], corners[3], color);
216 self.line(corners[3], corners[0], color);
217 self.line(corners[4], corners[5], color);
219 self.line(corners[5], corners[6], color);
220 self.line(corners[6], corners[7], color);
221 self.line(corners[7], corners[4], color);
222 self.line(corners[0], corners[4], color);
224 self.line(corners[1], corners[5], color);
225 self.line(corners[2], corners[6], color);
226 self.line(corners[3], corners[7], color);
227 }
228
229 pub fn sphere(&mut self, center: Vec3Fix, radius: Fix128, color: DebugColor) {
231 let segments = 16;
232 self.draw_circle(
233 center,
234 Vec3Fix::UNIT_X,
235 Vec3Fix::UNIT_Y,
236 radius,
237 segments,
238 color,
239 );
240 self.draw_circle(
241 center,
242 Vec3Fix::UNIT_X,
243 Vec3Fix::UNIT_Z,
244 radius,
245 segments,
246 color,
247 );
248 self.draw_circle(
249 center,
250 Vec3Fix::UNIT_Y,
251 Vec3Fix::UNIT_Z,
252 radius,
253 segments,
254 color,
255 );
256 }
257
258 fn draw_circle(
260 &mut self,
261 center: Vec3Fix,
262 axis_a: Vec3Fix,
263 axis_b: Vec3Fix,
264 radius: Fix128,
265 segments: usize,
266 color: DebugColor,
267 ) {
268 let mut prev = center + axis_a * radius;
269 for i in 1..=segments {
270 let angle = Fix128::TWO_PI * Fix128::from_ratio(i as i64, segments as i64);
271 let c = angle.cos();
272 let s = angle.sin();
273 let point = center + axis_a * (radius * c) + axis_b * (radius * s);
274 self.line(prev, point, color);
275 prev = point;
276 }
277 }
278
279 pub fn arrow(&mut self, start: Vec3Fix, end: Vec3Fix, color: DebugColor) {
289 self.line(start, end, color);
290
291 let dir = end - start;
292 let len = dir.length();
293 if len.is_zero() {
294 return;
295 }
296
297 let head_len = len * Fix128::from_ratio(2, 10);
298 let unit = dir.normalize();
299 let head_point = end - unit * head_len;
300 let mut side = unit.cross(Vec3Fix::UNIT_Y);
301 if side.length_squared() < Fix128::from_ratio(1, 1_000_000) {
302 side = unit.cross(Vec3Fix::UNIT_X);
303 }
304 let half_width = side.normalize() * head_len.half();
305 self.line(end, head_point + half_width, color);
306 self.line(end, head_point - half_width, color);
307 self.point(end, color, head_len);
308 }
309
310 pub fn axes(&mut self, position: Vec3Fix, rotation: QuatFix, scale: Fix128) {
312 let x = rotation.rotate_vec(Vec3Fix::UNIT_X) * scale;
313 let y = rotation.rotate_vec(Vec3Fix::UNIT_Y) * scale;
314 let z = rotation.rotate_vec(Vec3Fix::UNIT_Z) * scale;
315
316 self.arrow(position, position + x, DebugColor::RED);
317 self.arrow(position, position + y, DebugColor::GREEN);
318 self.arrow(position, position + z, DebugColor::BLUE);
319 }
320
321 #[must_use]
323 pub fn primitive_count(&self) -> usize {
324 self.lines.len() + self.points.len()
325 }
326}
327
328pub fn debug_draw_world(
330 world: &crate::solver::PhysicsWorld,
331 flags: &DebugDrawFlags,
332 data: &mut DebugDrawData,
333) {
334 data.clear();
335
336 for (i, body) in world.bodies.iter().enumerate() {
337 let is_static = body.is_static();
338 let body_color = if is_static {
339 DebugColor::GRAY
340 } else {
341 DebugColor::GREEN
342 };
343
344 if flags.draw_aabbs {
345 if let Some((radius, _)) = world.ray_geometry(i) {
346 let half = Vec3Fix::new(radius, radius, radius);
347 data.aabb(&AABB::from_center_half(body.position, half), body_color);
348 }
349 }
350
351 if flags.draw_centers {
352 data.point(body.position, body_color, Fix128::from_ratio(1, 10));
353 }
354
355 if flags.draw_velocities && !is_static {
356 let vel_end = body.position + body.velocity;
357 data.arrow(body.position, vel_end, DebugColor::YELLOW);
358 }
359
360 if flags.draw_axes {
361 data.axes(body.position, body.rotation, Fix128::ONE);
362 }
363 }
364
365 if flags.draw_contacts {
366 for contact in &world.contact_constraints {
367 let c = &contact.contact;
368 data.point(c.point_a, DebugColor::RED, Fix128::from_ratio(5, 100));
369 data.point(c.point_b, DebugColor::BLUE, Fix128::from_ratio(5, 100));
370
371 if flags.draw_contact_normals {
372 data.arrow(c.point_a, c.point_a + c.normal * c.depth, DebugColor::RED);
373 }
374 }
375 }
376
377 if flags.draw_joints {
378 let position = |i: usize| world.bodies.get(i).map(|b| b.position);
380 for constraint in &world.distance_constraints {
381 if let (Some(a), Some(b)) = (position(constraint.body_a), position(constraint.body_b)) {
382 data.line(a, b, DebugColor::CYAN);
383 }
384 }
385 for joint in &world.joints {
386 let (ia, ib) = joint.bodies();
387 if let (Some(a), Some(b)) = (position(ia), position(ib)) {
388 data.line(a, b, DebugColor::CYAN);
389 }
390 }
391 }
392
393 if flags.draw_bvh {
394 let primitives: Vec<crate::bvh::BvhPrimitive> = world
396 .bodies
397 .iter()
398 .enumerate()
399 .filter_map(|(i, body)| {
400 let (radius, _) = world.ray_geometry(i)?;
401 let half = Vec3Fix::new(radius, radius, radius);
402 Some(crate::bvh::BvhPrimitive {
403 aabb: AABB::from_center_half(body.position, half),
404 index: i as u32,
405 morton: 0,
406 })
407 })
408 .collect();
409 if !primitives.is_empty() {
410 let bvh = crate::bvh::LinearBvh::build(primitives);
411 let corner =
412 |c: [i32; 3]| Vec3Fix::from_int(i64::from(c[0]), i64::from(c[1]), i64::from(c[2]));
413 for node in &bvh.nodes {
414 let aabb = AABB::new(corner(node.aabb_min), corner(node.aabb_max));
415 data.aabb(&aabb, DebugColor::MAGENTA);
416 }
417 }
418 }
419}
420
421impl crate::solver::PhysicsWorld {
422 pub fn debug_draw(&self, flags: &DebugDrawFlags, data: &mut DebugDrawData) {
424 debug_draw_world(self, flags, data);
425 }
426}
427
428#[cfg(test)]
429mod tests {
430 use super::*;
431 #[cfg(not(feature = "std"))]
432 use alloc::vec;
433
434 #[test]
435 fn test_debug_draw_data() {
436 let mut data = DebugDrawData::new();
437 data.line(Vec3Fix::ZERO, Vec3Fix::UNIT_X, DebugColor::RED);
438 data.point(Vec3Fix::ZERO, DebugColor::GREEN, Fix128::ONE);
439
440 assert_eq!(data.lines.len(), 1);
441 assert_eq!(data.points.len(), 1);
442 assert_eq!(data.primitive_count(), 2);
443
444 data.clear();
445 assert_eq!(data.primitive_count(), 0);
446 }
447
448 #[test]
449 fn test_debug_aabb() {
450 let mut data = DebugDrawData::new();
451 let aabb = AABB::new(Vec3Fix::ZERO, Vec3Fix::from_int(1, 1, 1));
452 data.aabb(&aabb, DebugColor::WHITE);
453 assert_eq!(data.lines.len(), 12); }
455
456 #[test]
457 fn test_debug_sphere() {
458 let mut data = DebugDrawData::new();
459 data.sphere(Vec3Fix::ZERO, Fix128::ONE, DebugColor::GREEN);
460 assert_eq!(data.lines.len(), 48);
462 }
463
464 #[test]
465 fn test_debug_axes() {
466 let mut data = DebugDrawData::new();
467 data.axes(Vec3Fix::ZERO, QuatFix::IDENTITY, Fix128::ONE);
468 assert_eq!(data.lines.len(), 9);
470 assert_eq!(data.points.len(), 3);
471 }
472
473 #[test]
474 fn test_debug_flags_default() {
475 let flags = DebugDrawFlags::default();
476 assert!(flags.draw_aabbs);
477 assert!(flags.draw_contacts);
478 assert!(!flags.draw_bvh);
479 }
480
481 #[test]
482 fn test_debug_color() {
483 assert_eq!(DebugColor::RED, DebugColor::new(255, 50, 50, 255));
484 }
485
486 #[test]
487 fn debug_line_and_point_constructors_match_add_methods() {
488 let mut data = DebugDrawData::default();
489 data.line(Vec3Fix::ZERO, Vec3Fix::from_int(1, 0, 0), DebugColor::RED);
490 data.point(
491 Vec3Fix::from_int(0, 2, 0),
492 DebugColor::GREEN,
493 Fix128::from_ratio(1, 2),
494 );
495 assert_eq!(
496 data.lines[0],
497 DebugLine::new(Vec3Fix::ZERO, Vec3Fix::from_int(1, 0, 0), DebugColor::RED)
498 );
499 assert_eq!(
500 data.points[0],
501 DebugPoint::new(
502 Vec3Fix::from_int(0, 2, 0),
503 DebugColor::GREEN,
504 Fix128::from_ratio(1, 2)
505 )
506 );
507 }
508
509 fn only(set: impl Fn(&mut DebugDrawFlags)) -> DebugDrawFlags {
510 let mut flags = DebugDrawFlags {
511 draw_aabbs: false,
512 draw_centers: false,
513 draw_velocities: false,
514 draw_contacts: false,
515 draw_contact_normals: false,
516 draw_joints: false,
517 draw_bvh: false,
518 draw_axes: false,
519 };
520 set(&mut flags);
521 flags
522 }
523
524 fn joint_scene() -> crate::solver::PhysicsWorld {
528 use crate::joint::{BallJoint, Joint};
529 use crate::solver::{DistanceConstraint, PhysicsWorld, RigidBody, SolverConfig};
530 let mut world = PhysicsWorld::new(SolverConfig::default());
531 let half = Fix128::from_ratio(1, 2);
532 let quarter = Fix128::from_ratio(1, 4);
533 let a = world.add_body_with_radius(
534 RigidBody::new_static(Vec3Fix::new(half, half, half)),
535 quarter,
536 );
537 let b = world.add_body_with_radius(
538 RigidBody::new_dynamic(
539 Vec3Fix::new(Fix128::from_ratio(5, 2), half, half),
540 Fix128::ONE,
541 ),
542 quarter,
543 );
544 let c = world.add_body(RigidBody::new_dynamic(
545 Vec3Fix::from_int(0, 5, 0),
546 Fix128::ONE,
547 ));
548 let link =
549 |i, j| DistanceConstraint::new(i, j, Vec3Fix::ZERO, Vec3Fix::ZERO, Fix128::from_int(5));
550 world.add_distance_constraint(link(a, c));
551 world.add_distance_constraint(link(a, 99));
552 world.add_joint(Joint::Ball(BallJoint::new(
553 b,
554 c,
555 Vec3Fix::ZERO,
556 Vec3Fix::ZERO,
557 )));
558 world
559 }
560
561 #[test]
562 fn draw_joints_draws_one_cyan_line_per_resolvable_pair() {
563 let world = joint_scene();
564 let mut data = DebugDrawData::new();
565 world.debug_draw(&only(|f| f.draw_joints = true), &mut data);
566 let p = |i: usize| world.bodies[i].position;
568 assert_eq!(
569 data.lines,
570 vec![
571 DebugLine::new(p(0), p(2), DebugColor::CYAN),
572 DebugLine::new(p(1), p(2), DebugColor::CYAN),
573 ]
574 );
575 assert!(data.points.is_empty());
576 }
577
578 #[test]
579 fn draw_bvh_draws_the_single_leaf_rounded_out_to_the_lattice() {
580 let world = joint_scene();
581 let mut data = DebugDrawData::new();
582 world.debug_draw(&only(|f| f.draw_bvh = true), &mut data);
583 assert_eq!(data.lines.len(), 12);
586 assert!(data.lines.iter().all(|l| l.color == DebugColor::MAGENTA));
587 assert_eq!(data.lines[0].start, Vec3Fix::from_int(0, 0, 0));
588 assert_eq!(data.lines[0].end, Vec3Fix::from_int(3, 0, 0));
589 assert_eq!(data.lines[6].start, Vec3Fix::from_int(3, 1, 1));
590 assert_eq!(data.lines[6].end, Vec3Fix::from_int(0, 1, 1));
591
592 let mut empty = crate::solver::PhysicsWorld::new(crate::solver::SolverConfig::default());
594 empty.add_body(crate::solver::RigidBody::new_static(Vec3Fix::ZERO));
595 empty.debug_draw(&only(|f| f.draw_bvh = true), &mut data);
596 assert_eq!(data.primitive_count(), 0);
597 }
598
599 #[test]
600 fn draw_aabbs_centers_velocities_and_axes_counts() {
601 let mut world = joint_scene();
602 world.bodies[1].velocity = Vec3Fix::from_int(1, 0, 0);
603 let mut data = DebugDrawData::new();
604 world.debug_draw(&only(|f| f.draw_aabbs = true), &mut data);
606 assert_eq!(data.lines.len(), 24);
607 assert_eq!(data.lines[0].color, DebugColor::GRAY);
608 assert_eq!(data.lines[12].color, DebugColor::GREEN);
609 world.debug_draw(&only(|f| f.draw_centers = true), &mut data);
611 assert_eq!((data.lines.len(), data.points.len()), (0, 3));
612 world.debug_draw(&only(|f| f.draw_velocities = true), &mut data);
615 assert_eq!((data.lines.len(), data.points.len()), (4, 1));
616 world.debug_draw(&only(|f| f.draw_axes = true), &mut data);
618 assert_eq!((data.lines.len(), data.points.len()), (27, 9));
619 }
620
621 #[test]
622 fn draw_contacts_adds_two_points_and_a_normal_arrow_per_contact() {
623 use crate::collider::Contact;
624 use crate::solver::ContactConstraint;
625 let mut world = joint_scene();
626 world.add_contact(ContactConstraint::new(
627 0,
628 1,
629 Contact {
630 depth: Fix128::from_ratio(1, 10),
631 normal: Vec3Fix::UNIT_X,
632 point_a: Vec3Fix::ZERO,
633 point_b: Vec3Fix::UNIT_X,
634 },
635 ));
636 let mut data = DebugDrawData::new();
637 world.debug_draw(&only(|f| f.draw_contacts = true), &mut data);
638 assert_eq!((data.lines.len(), data.points.len()), (0, 2));
639 let flags = only(|f| {
640 f.draw_contacts = true;
641 f.draw_contact_normals = true;
642 });
643 world.debug_draw(&flags, &mut data);
644 assert_eq!((data.lines.len(), data.points.len()), (3, 3));
645 }
646}