1#![cfg_attr(all(target_arch = "wasm32", boxddd_wasm_provider), allow(dead_code))]
2
3use crate::core::foundation::ReplayLease;
4use crate::core::{callback_state, validation};
5#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
6use crate::error::Error;
7use crate::error::Result;
8use crate::shapes::ShapeType;
9use crate::types::{Aabb, Plane, Pos, ShapeId, Transform, Vec3, WorldTransform};
10use crate::world::World;
11use crate::world::ledger::CallbackProvenanceIndex;
12use boxddd_sys::ffi;
13use std::cell::{Cell, RefCell};
14use std::ffi::{CStr, c_void};
15use std::fmt;
16use std::mem;
17use std::panic::{AssertUnwindSafe, catch_unwind};
18use std::slice;
19
20#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
21mod provider;
22
23#[cfg(all(test, target_arch = "wasm32", boxddd_wasm_provider))]
24pub(crate) use provider::provider_debug_registry_count;
25#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
26pub(crate) use provider::{
27 ProviderDebugFrameGuard, register_provider_debug_registry, take_provider_debug_error,
28 unregister_provider_debug_registry,
29};
30#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
31pub use provider::{
32 boxddd_debug_draw_bounds, boxddd_debug_draw_box, boxddd_debug_draw_capsule,
33 boxddd_debug_draw_point, boxddd_debug_draw_segment, boxddd_debug_draw_shape,
34 boxddd_debug_draw_sphere, boxddd_debug_draw_string, boxddd_debug_draw_transform,
35 boxddd_debug_report_error, boxddd_debug_shape_create, boxddd_debug_shape_destroy,
36};
37
38#[repr(transparent)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
45#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
46pub struct HexColor(u32);
47
48impl HexColor {
49 pub const BLACK: Self = Self::from_rgb_u32(0x000000);
51 pub const WHITE: Self = Self::from_rgb_u32(0xffffff);
53 pub const RED: Self = Self::from_rgb_u32(0xff0000);
55 pub const GREEN: Self = Self::from_rgb_u32(0x00ff00);
57 pub const BLUE: Self = Self::from_rgb_u32(0x0000ff);
59
60 #[inline]
62 pub const fn from_rgb(red: u8, green: u8, blue: u8) -> Self {
63 Self(((red as u32) << 16) | ((green as u32) << 8) | blue as u32)
64 }
65
66 #[inline]
68 pub const fn from_rgb_u32(rgb: u32) -> Self {
69 Self(rgb & 0x00ff_ffff)
70 }
71
72 #[inline]
74 pub const fn from_raw(raw: u32) -> Self {
75 Self(raw)
76 }
77
78 #[inline]
80 pub const fn raw_u32(self) -> u32 {
81 self.0
82 }
83
84 #[inline]
86 pub const fn rgb_u32(self) -> u32 {
87 self.0 & 0x00ff_ffff
88 }
89
90 #[inline]
92 pub const fn into_raw(self) -> u32 {
93 self.0
94 }
95
96 #[inline]
97 fn from_ffi(raw: ffi::b3HexColor) -> Self {
98 Self(raw)
99 }
100}
101
102#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
104#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
105pub struct DebugShapeHandle {
106 pub index: u32,
108 pub generation: u32,
110}
111
112impl DebugShapeHandle {
113 #[inline]
115 pub const fn new(index: u32, generation: u32) -> Option<Self> {
116 if generation == 0 {
117 None
118 } else {
119 Some(Self { index, generation })
120 }
121 }
122
123 #[inline]
125 pub const fn is_valid(self) -> bool {
126 self.generation != 0
127 }
128}
129
130#[derive(Clone, Debug, PartialEq)]
132pub struct DebugShapeAsset {
133 pub handle: DebugShapeHandle,
135 pub shape_id: ShapeId,
137 pub shape_type: ShapeType,
139 pub geometry: DebugShapeGeometry,
141}
142
143#[derive(Clone, Debug, PartialEq)]
145pub enum DebugShapeEvent {
146 Created(DebugShapeAsset),
148 Destroyed {
150 handle: DebugShapeHandle,
152 },
153 ClearAll,
155}
156
157#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
159#[derive(Clone, Debug, PartialEq, Eq)]
160pub struct DebugDrawDiagnostic {
161 pub message: String,
163}
164
165#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
167#[derive(Clone, Debug, PartialEq)]
168pub struct DebugHullFace {
169 pub indices: Vec<u32>,
171 pub plane: Plane,
173}
174
175#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
177#[derive(Clone, Debug, PartialEq)]
178pub struct DebugMesh {
179 pub bounds: Aabb,
181 pub vertices: Vec<Vec3>,
183 pub triangles: Vec<DebugMeshTriangle>,
185 pub material_count: i32,
187}
188
189#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
191#[derive(Copy, Clone, Debug, PartialEq, Eq)]
192pub struct DebugMeshTriangle {
193 pub indices: [u32; 3],
195 pub material_index: Option<u8>,
197}
198
199#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
201#[derive(Clone, Debug, PartialEq)]
202pub struct DebugCompoundChild {
203 pub transform: Transform,
205 pub material_indices: [i32; ffi::B3_MAX_COMPOUND_MESH_MATERIALS as usize],
207 pub geometry: DebugShapeGeometry,
209}
210
211#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
213#[derive(Clone, Debug, PartialEq)]
214pub enum DebugShapeGeometry {
215 Sphere {
217 center: Vec3,
219 radius: f32,
221 },
222 Capsule {
224 center1: Vec3,
226 center2: Vec3,
228 radius: f32,
230 },
231 Hull {
233 aabb: Aabb,
235 points: Vec<Vec3>,
237 faces: Vec<DebugHullFace>,
239 },
240 Mesh {
242 mesh: DebugMesh,
244 scale: Vec3,
246 },
247 HeightField {
249 mesh: DebugMesh,
251 },
252 Compound {
254 children: Vec<DebugCompoundChild>,
256 },
257}
258
259impl DebugShapeGeometry {
260 #[inline]
262 pub const fn shape_type(&self) -> Option<ShapeType> {
263 match self {
264 Self::Sphere { .. } => Some(ShapeType::Sphere),
265 Self::Capsule { .. } => Some(ShapeType::Capsule),
266 Self::Hull { .. } => Some(ShapeType::Hull),
267 Self::Mesh { .. } => Some(ShapeType::Mesh),
268 Self::HeightField { .. } => Some(ShapeType::HeightField),
269 Self::Compound { .. } => Some(ShapeType::Compound),
270 }
271 }
272}
273
274#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
280#[derive(Clone, Debug, PartialEq)]
281pub enum DebugDrawCommand {
282 Shape {
284 handle: Option<DebugShapeHandle>,
286 transform: WorldTransform,
288 color: HexColor,
290 },
291 Segment {
293 p1: Pos,
295 p2: Pos,
297 color: HexColor,
299 },
300 Transform(WorldTransform),
302 Point {
304 position: Pos,
306 size: f32,
308 color: HexColor,
310 },
311 Sphere {
313 center: Pos,
315 radius: f32,
317 color: HexColor,
319 alpha: f32,
321 },
322 Capsule {
324 p1: Pos,
326 p2: Pos,
328 radius: f32,
330 color: HexColor,
332 alpha: f32,
334 },
335 Bounds {
337 aabb: Aabb,
339 color: HexColor,
341 },
342 Box {
344 extents: Vec3,
346 transform: WorldTransform,
348 color: HexColor,
350 },
351 String {
353 position: Pos,
355 text: String,
357 color: HexColor,
359 },
360}
361
362#[derive(Clone, Debug, Default, PartialEq)]
370pub struct DebugDrawFrame {
371 pub events: Vec<DebugShapeEvent>,
373 pub commands: Vec<DebugDrawCommand>,
375 pub diagnostics: Vec<DebugDrawDiagnostic>,
377}
378
379impl DebugDrawFrame {
380 pub fn clear(&mut self) {
382 self.events.clear();
383 self.commands.clear();
384 self.diagnostics.clear();
385 }
386}
387
388pub trait DebugDraw {
403 fn draw_shape(
405 &mut self,
406 _handle: Option<DebugShapeHandle>,
407 _transform: WorldTransform,
408 _color: HexColor,
409 ) {
410 }
411
412 fn draw_segment(&mut self, _p1: Pos, _p2: Pos, _color: HexColor) {}
414 fn draw_transform(&mut self, _transform: WorldTransform) {}
416 fn draw_point(&mut self, _position: Pos, _size: f32, _color: HexColor) {}
418 fn draw_sphere(&mut self, _center: Pos, _radius: f32, _color: HexColor, _alpha: f32) {}
420 fn draw_capsule(&mut self, _p1: Pos, _p2: Pos, _radius: f32, _color: HexColor, _alpha: f32) {}
422 fn draw_bounds(&mut self, _aabb: Aabb, _color: HexColor) {}
424 fn draw_box(&mut self, _extents: Vec3, _transform: WorldTransform, _color: HexColor) {}
426 fn draw_string(&mut self, _position: Pos, _text: &str, _color: HexColor) {}
428}
429
430#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
432#[derive(Copy, Clone, Debug)]
433pub struct DebugDrawOptions {
434 pub drawing_bounds: Aabb,
436 pub mask_bits: u64,
438 pub force_scale: f32,
440 pub joint_scale: f32,
442 pub draw_shapes: bool,
444 pub draw_joints: bool,
446 pub draw_joint_extras: bool,
448 pub draw_bounds: bool,
450 pub draw_mass: bool,
452 pub draw_sleep: bool,
454 pub draw_body_names: bool,
456 pub draw_contacts: bool,
458 pub draw_anchor_a: bool,
460 pub draw_graph_colors: bool,
462 pub draw_contact_features: bool,
464 pub draw_contact_normals: bool,
466 pub draw_contact_forces: bool,
468 pub draw_islands: bool,
470}
471
472impl Default for DebugDrawOptions {
473 fn default() -> Self {
474 Self {
475 drawing_bounds: Aabb {
476 lower_bound: Vec3::new(-1.0e9, -1.0e9, -1.0e9),
477 upper_bound: Vec3::new(1.0e9, 1.0e9, 1.0e9),
478 },
479 mask_bits: u64::MAX,
480 force_scale: 1.0,
481 joint_scale: 1.0,
482 draw_shapes: true,
483 draw_joints: true,
484 draw_joint_extras: false,
485 draw_bounds: false,
486 draw_mass: false,
487 draw_sleep: false,
488 draw_body_names: false,
489 draw_contacts: false,
490 draw_anchor_a: false,
491 draw_graph_colors: false,
492 draw_contact_features: false,
493 draw_contact_normals: false,
494 draw_contact_forces: false,
495 draw_islands: false,
496 }
497 }
498}
499
500pub(crate) struct DebugShapeRegistry {
501 inner: RefCell<DebugShapeStore>,
502 provenance: CallbackProvenanceIndex,
503 failures: callback_state::CallbackInvocationSlot,
504 poisoned: Cell<bool>,
505 failure_generation: Cell<u64>,
506 #[cfg(test)]
507 panic_next_native_callback: Cell<bool>,
508}
509
510#[derive(Default)]
511struct DebugShapeStore {
512 slots: Vec<DebugShapeSlot>,
513 free: Vec<usize>,
514 events: Vec<DebugShapeEvent>,
515 diagnostics: Vec<DebugDrawDiagnostic>,
516 cleared: bool,
517}
518
519#[derive(Debug)]
520struct DebugShapeSlot {
521 generation: u32,
522 asset: Option<DebugShapeAsset>,
523}
524
525impl fmt::Debug for DebugShapeRegistry {
526 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
527 let inner = self.inner.borrow();
528 f.debug_struct("DebugShapeRegistry")
529 .field("slots", &inner.slots.len())
530 .field("free", &inner.free.len())
531 .field("pending_events", &inner.events.len())
532 .field("pending_diagnostics", &inner.diagnostics.len())
533 .finish()
534 }
535}
536
537#[derive(Copy, Clone, Debug)]
538struct DebugShapeResource {
539 handle: DebugShapeHandle,
540}
541
542impl DebugShapeRegistry {
543 pub(crate) fn new(
544 provenance: CallbackProvenanceIndex,
545 failures: callback_state::CallbackInvocationSlot,
546 ) -> Self {
547 Self {
548 inner: RefCell::new(DebugShapeStore::default()),
549 provenance,
550 failures,
551 poisoned: Cell::new(false),
552 failure_generation: Cell::new(0),
553 #[cfg(test)]
554 panic_next_native_callback: Cell::new(false),
555 }
556 }
557
558 fn invoke_native_callback<R: Copy>(&self, fallback: R, callback: impl FnOnce() -> R) -> R {
559 match catch_unwind(AssertUnwindSafe(|| {
560 let _guard = callback_state::CallbackGuard::enter();
561 #[cfg(test)]
562 if self.panic_next_native_callback.replace(false) {
563 panic!("injected debug-shape callback panic");
564 }
565 callback()
566 })) {
567 Ok(value) => value,
568 Err(_) => {
569 self.record_callback_failure(callback_state::CallbackFailure::Panicked);
570 fallback
571 }
572 }
573 }
574
575 fn record_callback_failure(&self, failure: callback_state::CallbackFailure) {
576 self.poisoned.set(true);
577 self.failure_generation
578 .set(self.failure_generation.get().saturating_add(1));
579 self.failures.record(failure);
580 }
581
582 pub(crate) fn is_poisoned(&self) -> bool {
583 self.poisoned.get()
584 }
585
586 pub(crate) fn failure_generation(&self) -> u64 {
587 self.failure_generation.get()
588 }
589
590 #[cfg(test)]
591 fn panic_next_native_callback(&self) {
592 self.panic_next_native_callback.set(true);
593 }
594
595 fn create_asset_handle(&self, raw: &ffi::b3DebugShape) -> Option<DebugShapeHandle> {
596 let mut store = self.inner.borrow_mut();
597 let snapshot = match unsafe { snapshot_debug_shape(raw, &self.provenance) } {
598 Ok(snapshot) => snapshot,
599 Err(message) => {
600 store.diagnostics.push(DebugDrawDiagnostic {
601 message: message.to_owned(),
602 });
603 drop(store);
604 self.record_callback_failure(callback_state::CallbackFailure::InvalidNativeInput);
605 return None;
606 }
607 };
608 let handle = store.alloc_handle();
609 let asset = DebugShapeAsset {
610 handle,
611 shape_id: snapshot.shape_id,
612 shape_type: snapshot.shape_type,
613 geometry: snapshot.geometry,
614 };
615 store.slots[handle.index as usize].asset = Some(asset.clone());
616 store.events.push(DebugShapeEvent::Created(asset));
617 Some(handle)
618 }
619
620 fn create_native_resource(&self, raw: &ffi::b3DebugShape) -> *mut c_void {
621 let Some(handle) = self.create_asset_handle(raw) else {
622 return std::ptr::null_mut();
623 };
624 Box::into_raw(Box::new(DebugShapeResource { handle })) as *mut c_void
625 }
626
627 fn destroy_native_resource(&self, resource: DebugShapeResource) {
628 let _ = self.destroy_handle(resource.handle);
629 }
630
631 fn destroy_handle(&self, handle: DebugShapeHandle) -> bool {
632 let destroyed = self.inner.borrow_mut().destroy_handle(handle);
633 if !destroyed {
634 self.record_callback_failure(callback_state::CallbackFailure::InvalidNativeInput);
635 }
636 destroyed
637 }
638
639 pub(crate) fn drain_into(&self, frame: &mut DebugDrawFrame) {
640 let mut store = self.inner.borrow_mut();
641 frame.events.append(&mut store.events);
642 frame.diagnostics.append(&mut store.diagnostics);
643 }
644
645 #[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
646 fn push_diagnostic(&self, message: impl Into<String>) {
647 self.inner
648 .borrow_mut()
649 .diagnostics
650 .push(DebugDrawDiagnostic {
651 message: message.into(),
652 });
653 }
654
655 pub(crate) fn clear_all(&self) {
656 let mut store = self.inner.borrow_mut();
657 for slot in &mut store.slots {
658 if slot.asset.take().is_some() {
659 slot.generation = next_generation(slot.generation);
660 }
661 }
662 let slot_len = store.slots.len();
663 store.free.clear();
664 store.free.extend(0..slot_len);
665 store.events.clear();
666 store.diagnostics.clear();
667 store.events.push(DebugShapeEvent::ClearAll);
668 store.cleared = true;
669 }
670}
671
672impl DebugShapeStore {
673 fn alloc_handle(&mut self) -> DebugShapeHandle {
674 self.cleared = false;
675 if let Some(index) = self.free.pop() {
676 let generation = self.slots[index].generation;
677 DebugShapeHandle {
678 index: index as u32,
679 generation,
680 }
681 } else {
682 let index = self.slots.len();
683 self.slots.push(DebugShapeSlot {
684 generation: 1,
685 asset: None,
686 });
687 DebugShapeHandle {
688 index: index as u32,
689 generation: 1,
690 }
691 }
692 }
693
694 fn destroy_handle(&mut self, handle: DebugShapeHandle) -> bool {
695 let Some(slot) = self.slots.get_mut(handle.index as usize) else {
696 self.diagnostics.push(DebugDrawDiagnostic {
697 message: "debug shape destroy referenced an unknown handle".to_owned(),
698 });
699 return false;
700 };
701 if slot.generation != handle.generation || slot.asset.is_none() {
702 if self.cleared {
703 return true;
704 }
705 self.diagnostics.push(DebugDrawDiagnostic {
706 message: "debug shape destroy referenced a stale handle".to_owned(),
707 });
708 return false;
709 }
710
711 slot.asset = None;
712 slot.generation = next_generation(slot.generation);
713 self.events.push(DebugShapeEvent::Destroyed { handle });
714 self.free.push(handle.index as usize);
715 true
716 }
717}
718
719#[inline]
720const fn next_generation(current: u32) -> u32 {
721 let next = current.wrapping_add(1);
722 if next == 0 { 1 } else { next }
723}
724
725#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
726pub(crate) unsafe extern "C" fn create_debug_shape(
727 debug_shape: *const ffi::b3DebugShape,
728 user_context: *mut c_void,
729) -> *mut c_void {
730 if user_context.is_null() {
731 return std::ptr::null_mut();
732 }
733 let registry = unsafe { &*(user_context as *const DebugShapeRegistry) };
734 registry.invoke_native_callback(std::ptr::null_mut(), || {
735 let Some(debug_shape) = (unsafe { debug_shape.as_ref() }) else {
736 registry.record_callback_failure(callback_state::CallbackFailure::InvalidNativeInput);
737 return std::ptr::null_mut();
738 };
739 registry.create_native_resource(debug_shape)
740 })
741}
742
743#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
744pub(crate) unsafe extern "C" fn destroy_debug_shape(
745 user_shape: *mut c_void,
746 user_context: *mut c_void,
747) {
748 if user_shape.is_null() {
749 return;
750 }
751 if !user_context.is_null() {
752 let registry = unsafe { &*(user_context as *const DebugShapeRegistry) };
753 registry.invoke_native_callback((), || {
754 let resource = unsafe { *Box::from_raw(user_shape as *mut DebugShapeResource) };
755 registry.destroy_native_resource(resource);
756 });
757 } else {
758 let _ = catch_unwind(AssertUnwindSafe(|| {
759 let _guard = callback_state::CallbackGuard::enter();
760 drop(unsafe { Box::from_raw(user_shape as *mut DebugShapeResource) });
761 }));
762 }
763}
764
765struct DebugShapeSnapshot {
766 shape_id: ShapeId,
767 shape_type: ShapeType,
768 geometry: DebugShapeGeometry,
769}
770
771type SnapshotResult<T> = std::result::Result<T, &'static str>;
772
773unsafe fn snapshot_debug_shape(
774 raw: &ffi::b3DebugShape,
775 provenance: &CallbackProvenanceIndex,
776) -> SnapshotResult<DebugShapeSnapshot> {
777 let shape_type = ShapeType::from_raw(raw.type_).ok_or("debug shape has unknown shape type")?;
778 let geometry = unsafe {
779 match shape_type {
780 ShapeType::Sphere => snapshot_sphere_ptr(raw.__bindgen_anon_1.sphere)?,
781 ShapeType::Capsule => snapshot_capsule_ptr(raw.__bindgen_anon_1.capsule)?,
782 ShapeType::Hull => snapshot_hull_ptr(raw.__bindgen_anon_1.hull)?,
783 ShapeType::Mesh => snapshot_mesh_ptr(raw.__bindgen_anon_1.mesh)?,
784 ShapeType::HeightField => snapshot_height_field_ptr(raw.__bindgen_anon_1.heightField)?,
785 ShapeType::Compound => snapshot_compound_ptr(raw.__bindgen_anon_1.compound)?,
786 }
787 };
788 let shape_id = provenance
789 .resolve_shape(raw.shapeId)
790 .ok_or("debug shape referenced an unknown shape")?;
791 Ok(DebugShapeSnapshot {
792 shape_id,
793 shape_type,
794 geometry,
795 })
796}
797
798unsafe fn snapshot_child_shape(raw: ffi::b3ChildShape) -> SnapshotResult<DebugShapeGeometry> {
799 unsafe {
800 match ShapeType::from_raw(raw.type_).ok_or("compound child has unknown shape type")? {
801 ShapeType::Sphere => Ok(snapshot_sphere(raw.__bindgen_anon_1.sphere)),
802 ShapeType::Capsule => Ok(snapshot_capsule(raw.__bindgen_anon_1.capsule)),
803 ShapeType::Hull => snapshot_hull_ptr(raw.__bindgen_anon_1.hull),
804 ShapeType::Mesh => {
805 let mesh = raw.__bindgen_anon_1.mesh;
806 snapshot_mesh(&mesh)
807 }
808 ShapeType::Compound => Err("nested compound debug child is not supported by Box3D"),
809 ShapeType::HeightField => Err("height-field debug child is not supported by Box3D"),
810 }
811 }
812}
813
814unsafe fn snapshot_sphere_ptr(ptr: *const ffi::b3Sphere) -> SnapshotResult<DebugShapeGeometry> {
815 let sphere = unsafe { ptr.as_ref() }.ok_or("debug sphere pointer was null")?;
816 Ok(snapshot_sphere(*sphere))
817}
818
819fn snapshot_sphere(raw: ffi::b3Sphere) -> DebugShapeGeometry {
820 DebugShapeGeometry::Sphere {
821 center: Vec3::from_raw(raw.center),
822 radius: raw.radius,
823 }
824}
825
826unsafe fn snapshot_capsule_ptr(ptr: *const ffi::b3Capsule) -> SnapshotResult<DebugShapeGeometry> {
827 let capsule = unsafe { ptr.as_ref() }.ok_or("debug capsule pointer was null")?;
828 Ok(snapshot_capsule(*capsule))
829}
830
831fn snapshot_capsule(raw: ffi::b3Capsule) -> DebugShapeGeometry {
832 DebugShapeGeometry::Capsule {
833 center1: Vec3::from_raw(raw.center1),
834 center2: Vec3::from_raw(raw.center2),
835 radius: raw.radius,
836 }
837}
838
839unsafe fn snapshot_hull_ptr(ptr: *const ffi::b3HullData) -> SnapshotResult<DebugShapeGeometry> {
840 let hull = unsafe { ptr.as_ref() }.ok_or("debug hull pointer was null")?;
841 unsafe { snapshot_hull(hull) }
842}
843
844unsafe fn snapshot_hull(hull: &ffi::b3HullData) -> SnapshotResult<DebugShapeGeometry> {
845 let points = unsafe {
846 trailing_slice::<ffi::b3Vec3>(
847 hull as *const _ as *const u8,
848 hull.byteCount,
849 hull.pointOffset,
850 hull.vertexCount,
851 )?
852 }
853 .iter()
854 .copied()
855 .map(Vec3::from_raw)
856 .collect();
857 let edges = unsafe {
858 trailing_slice::<ffi::b3HullHalfEdge>(
859 hull as *const _ as *const u8,
860 hull.byteCount,
861 hull.edgeOffset,
862 hull.edgeCount,
863 )?
864 };
865 let raw_faces = unsafe {
866 trailing_slice::<ffi::b3HullFace>(
867 hull as *const _ as *const u8,
868 hull.byteCount,
869 hull.faceOffset,
870 hull.faceCount,
871 )?
872 };
873 let planes = unsafe {
874 trailing_slice::<ffi::b3Plane>(
875 hull as *const _ as *const u8,
876 hull.byteCount,
877 hull.planeOffset,
878 hull.faceCount,
879 )?
880 };
881
882 let mut faces = Vec::with_capacity(raw_faces.len());
883 for (face, plane) in raw_faces.iter().zip(planes) {
884 let start = face.edge as usize;
885 if start >= edges.len() {
886 return Err("debug hull face edge index was out of range");
887 }
888 let mut indices = Vec::new();
889 let mut edge_index = start;
890 for _ in 0..=edges.len() {
891 let edge = edges
892 .get(edge_index)
893 .ok_or("debug hull edge index was out of range")?;
894 indices.push(edge.origin as u32);
895 edge_index = edge.next as usize;
896 if edge_index == start {
897 break;
898 }
899 }
900 if edge_index != start {
901 return Err("debug hull face did not form a closed loop");
902 }
903 faces.push(DebugHullFace {
904 indices,
905 plane: Plane::from_raw(*plane),
906 });
907 }
908
909 Ok(DebugShapeGeometry::Hull {
910 aabb: Aabb::from_raw(hull.aabb),
911 points,
912 faces,
913 })
914}
915
916unsafe fn snapshot_mesh_ptr(ptr: *const ffi::b3Mesh) -> SnapshotResult<DebugShapeGeometry> {
917 let mesh = unsafe { ptr.as_ref() }.ok_or("debug mesh pointer was null")?;
918 unsafe { snapshot_mesh(mesh) }
919}
920
921unsafe fn snapshot_mesh(mesh: &ffi::b3Mesh) -> SnapshotResult<DebugShapeGeometry> {
922 let data = unsafe { mesh.data.as_ref() }.ok_or("debug mesh data pointer was null")?;
923 let raw_vertices = unsafe {
924 trailing_slice::<ffi::b3Vec3>(
925 data as *const _ as *const u8,
926 data.byteCount,
927 data.vertexOffset,
928 data.vertexCount,
929 )?
930 };
931 let raw_triangles = unsafe {
932 trailing_slice::<ffi::b3MeshTriangle>(
933 data as *const _ as *const u8,
934 data.byteCount,
935 data.triangleOffset,
936 data.triangleCount,
937 )?
938 };
939 let raw_material_indices = if data.materialOffset == 0 {
940 &[]
941 } else {
942 unsafe {
943 trailing_slice::<u8>(
944 data as *const _ as *const u8,
945 data.byteCount,
946 data.materialOffset,
947 data.triangleCount,
948 )?
949 }
950 };
951
952 let vertices: Vec<_> = raw_vertices.iter().copied().map(Vec3::from_raw).collect();
953 let mut triangles = Vec::with_capacity(raw_triangles.len());
954 for (index, triangle) in raw_triangles.iter().enumerate() {
955 let indices = [triangle.index1, triangle.index2, triangle.index3];
956 if indices
957 .iter()
958 .any(|index| *index < 0 || *index as usize >= vertices.len())
959 {
960 return Err("debug mesh triangle index was out of range");
961 }
962 triangles.push(DebugMeshTriangle {
963 indices: [indices[0] as u32, indices[1] as u32, indices[2] as u32],
964 material_index: raw_material_indices.get(index).copied(),
965 });
966 }
967
968 Ok(DebugShapeGeometry::Mesh {
969 mesh: DebugMesh {
970 bounds: Aabb::from_raw(data.bounds),
971 vertices,
972 triangles,
973 material_count: data.materialCount,
974 },
975 scale: Vec3::from_raw(mesh.scale),
976 })
977}
978
979unsafe fn snapshot_height_field_ptr(
980 ptr: *const ffi::b3HeightFieldData,
981) -> SnapshotResult<DebugShapeGeometry> {
982 let height_field = unsafe { ptr.as_ref() }.ok_or("debug height-field pointer was null")?;
983 unsafe { snapshot_height_field(height_field) }
984}
985
986unsafe fn snapshot_height_field(
987 height_field: &ffi::b3HeightFieldData,
988) -> SnapshotResult<DebugShapeGeometry> {
989 let sample_count = checked_grid_count(height_field.rowCount, height_field.columnCount)?;
990 let cell_count = checked_grid_count(height_field.rowCount - 1, height_field.columnCount - 1)?;
991 let triangle_count = cell_count
992 .checked_mul(2)
993 .ok_or("debug height-field triangle count overflowed")?;
994 let compressed_heights = unsafe {
995 trailing_slice::<u16>(
996 height_field as *const _ as *const u8,
997 height_field.byteCount,
998 height_field.heightsOffset,
999 sample_count as i32,
1000 )?
1001 };
1002 let material_indices = unsafe {
1003 trailing_slice::<u8>(
1004 height_field as *const _ as *const u8,
1005 height_field.byteCount,
1006 height_field.materialOffset,
1007 cell_count as i32,
1008 )?
1009 };
1010
1011 let mut vertices = Vec::with_capacity(sample_count);
1012 for row in 0..height_field.rowCount {
1013 for column in 0..height_field.columnCount {
1014 let index = (row * height_field.columnCount + column) as usize;
1015 let height = height_field.minHeight
1016 + height_field.heightScale * compressed_heights[index] as f32;
1017 vertices.push(Vec3::new(
1018 column as f32 * height_field.scale.x,
1019 height * height_field.scale.y,
1020 row as f32 * height_field.scale.z,
1021 ));
1022 }
1023 }
1024
1025 let mut triangles = Vec::with_capacity(triangle_count);
1026 for row in 0..height_field.rowCount - 1 {
1027 for column in 0..height_field.columnCount - 1 {
1028 let cell_index = (row * (height_field.columnCount - 1) + column) as usize;
1029 let material_index = material_indices
1030 .get(cell_index)
1031 .copied()
1032 .ok_or("debug height-field material index was out of range")?;
1033 if material_index == ffi::B3_HEIGHT_FIELD_HOLE as u8 {
1034 continue;
1035 }
1036 let index11 = (row * height_field.columnCount + column) as u32;
1037 let index12 = index11 + 1;
1038 let index21 = ((row + 1) * height_field.columnCount + column) as u32;
1039 let index22 = index21 + 1;
1040 let first = if height_field.clockwise {
1041 [index11, index12, index21]
1042 } else {
1043 [index11, index21, index12]
1044 };
1045 let second = if height_field.clockwise {
1046 [index22, index21, index12]
1047 } else {
1048 [index22, index12, index21]
1049 };
1050 triangles.push(DebugMeshTriangle {
1051 indices: first,
1052 material_index: Some(material_index),
1053 });
1054 triangles.push(DebugMeshTriangle {
1055 indices: second,
1056 material_index: Some(material_index),
1057 });
1058 }
1059 }
1060
1061 Ok(DebugShapeGeometry::HeightField {
1062 mesh: DebugMesh {
1063 bounds: Aabb::from_raw(height_field.aabb),
1064 vertices,
1065 triangles,
1066 material_count: 256,
1067 },
1068 })
1069}
1070
1071unsafe fn snapshot_compound_ptr(
1072 ptr: *const ffi::b3CompoundData,
1073) -> SnapshotResult<DebugShapeGeometry> {
1074 let compound = unsafe { ptr.as_ref() }.ok_or("debug compound pointer was null")?;
1075 let total = compound
1076 .capsuleCount
1077 .checked_add(compound.hullCount)
1078 .and_then(|count| count.checked_add(compound.meshCount))
1079 .and_then(|count| count.checked_add(compound.sphereCount))
1080 .ok_or("debug compound child count overflowed")?;
1081 if total < 0 {
1082 return Err("debug compound child count was negative");
1083 }
1084
1085 let mut children = Vec::with_capacity(total as usize);
1086 for index in 0..total {
1087 let child = unsafe { ffi::b3GetCompoundChild(compound, index) };
1088 children.push(DebugCompoundChild {
1089 transform: Transform::from_raw(child.transform),
1090 material_indices: child.materialIndices,
1091 geometry: unsafe { snapshot_child_shape(child)? },
1092 });
1093 }
1094 Ok(DebugShapeGeometry::Compound { children })
1095}
1096
1097fn checked_grid_count(rows: i32, columns: i32) -> SnapshotResult<usize> {
1098 if rows <= 0 || columns <= 0 {
1099 return Err("debug height-field dimensions were invalid");
1100 }
1101 (rows as usize)
1102 .checked_mul(columns as usize)
1103 .ok_or("debug height-field dimensions overflowed")
1104}
1105
1106unsafe fn trailing_slice<'a, T>(
1107 base: *const u8,
1108 byte_count: i32,
1109 offset: i32,
1110 count: i32,
1111) -> SnapshotResult<&'a [T]> {
1112 if base.is_null() || byte_count <= 0 || offset <= 0 || count < 0 {
1113 return Err("debug shape trailing storage was invalid");
1114 }
1115 let start = offset as usize;
1116 let len = count as usize;
1117 let bytes = len
1118 .checked_mul(mem::size_of::<T>())
1119 .ok_or("debug shape trailing storage length overflowed")?;
1120 let end = start
1121 .checked_add(bytes)
1122 .ok_or("debug shape trailing storage end overflowed")?;
1123 if end > byte_count as usize {
1124 return Err("debug shape trailing storage exceeded its allocation");
1125 }
1126 let ptr = unsafe { base.add(start) as *const T };
1127 Ok(unsafe { slice::from_raw_parts(ptr, len) })
1128}
1129
1130struct DebugDrawContext<'a> {
1131 drawer: &'a mut dyn DebugDraw,
1132 state: callback_state::LocalCallbackState,
1133}
1134
1135fn run_debug_draw_callback<R: Copy>(
1136 context: &mut DebugDrawContext<'_>,
1137 default: R,
1138 callback: impl FnOnce(&mut dyn DebugDraw) -> R,
1139) -> R {
1140 let DebugDrawContext { drawer, state } = context;
1141 state.invoke(default, || callback(*drawer))
1142}
1143
1144fn debug_shape_handle_from_user_shape(user_shape: *mut c_void) -> Option<DebugShapeHandle> {
1145 if user_shape.is_null() {
1146 None
1147 } else {
1148 Some(unsafe { (*(user_shape as *const DebugShapeResource)).handle })
1149 }
1150}
1151
1152fn apply_options(draw: &mut ffi::b3DebugDraw, options: DebugDrawOptions, context: *mut c_void) {
1153 draw.drawingBounds = options.drawing_bounds.into_raw();
1154 draw.forceScale = options.force_scale;
1155 draw.jointScale = options.joint_scale;
1156 draw.drawShapes = options.draw_shapes;
1157 draw.drawJoints = options.draw_joints;
1158 draw.drawJointExtras = options.draw_joint_extras;
1159 draw.drawBounds = options.draw_bounds;
1160 draw.drawMass = options.draw_mass;
1161 draw.drawSleep = options.draw_sleep;
1162 draw.drawBodyNames = options.draw_body_names;
1163 draw.drawContacts = options.draw_contacts;
1164 draw.drawAnchorA = options.draw_anchor_a;
1165 draw.drawGraphColors = options.draw_graph_colors;
1166 draw.drawContactFeatures = options.draw_contact_features;
1167 draw.drawContactNormals = options.draw_contact_normals;
1168 draw.drawContactForces = options.draw_contact_forces;
1169 draw.drawIslands = options.draw_islands;
1170 draw.context = context;
1171}
1172
1173pub(crate) struct CollectDebugDraw<'a> {
1174 commands: &'a mut Vec<DebugDrawCommand>,
1175 len: usize,
1176}
1177
1178impl<'a> CollectDebugDraw<'a> {
1179 pub(crate) fn new(commands: &'a mut Vec<DebugDrawCommand>) -> Self {
1180 Self { commands, len: 0 }
1181 }
1182
1183 pub(crate) fn finish(self) {
1184 self.commands.truncate(self.len);
1185 }
1186
1187 fn replace_or_push(&mut self, command: DebugDrawCommand) {
1188 if let Some(slot) = self.commands.get_mut(self.len) {
1189 *slot = command;
1190 } else {
1191 self.commands.push(command);
1192 }
1193 self.len += 1;
1194 }
1195}
1196
1197impl DebugDraw for CollectDebugDraw<'_> {
1198 fn draw_shape(
1199 &mut self,
1200 handle: Option<DebugShapeHandle>,
1201 transform: WorldTransform,
1202 color: HexColor,
1203 ) {
1204 self.replace_or_push(DebugDrawCommand::Shape {
1205 handle,
1206 transform,
1207 color,
1208 });
1209 }
1210
1211 fn draw_segment(&mut self, p1: Pos, p2: Pos, color: HexColor) {
1212 self.replace_or_push(DebugDrawCommand::Segment { p1, p2, color });
1213 }
1214
1215 fn draw_transform(&mut self, transform: WorldTransform) {
1216 self.replace_or_push(DebugDrawCommand::Transform(transform));
1217 }
1218
1219 fn draw_point(&mut self, position: Pos, size: f32, color: HexColor) {
1220 self.replace_or_push(DebugDrawCommand::Point {
1221 position,
1222 size,
1223 color,
1224 });
1225 }
1226
1227 fn draw_sphere(&mut self, center: Pos, radius: f32, color: HexColor, alpha: f32) {
1228 self.replace_or_push(DebugDrawCommand::Sphere {
1229 center,
1230 radius,
1231 color,
1232 alpha,
1233 });
1234 }
1235
1236 fn draw_capsule(&mut self, p1: Pos, p2: Pos, radius: f32, color: HexColor, alpha: f32) {
1237 self.replace_or_push(DebugDrawCommand::Capsule {
1238 p1,
1239 p2,
1240 radius,
1241 color,
1242 alpha,
1243 });
1244 }
1245
1246 fn draw_bounds(&mut self, aabb: Aabb, color: HexColor) {
1247 self.replace_or_push(DebugDrawCommand::Bounds { aabb, color });
1248 }
1249
1250 fn draw_box(&mut self, extents: Vec3, transform: WorldTransform, color: HexColor) {
1251 self.replace_or_push(DebugDrawCommand::Box {
1252 extents,
1253 transform,
1254 color,
1255 });
1256 }
1257
1258 fn draw_string(&mut self, position: Pos, text: &str, color: HexColor) {
1259 match self.commands.get_mut(self.len) {
1260 Some(DebugDrawCommand::String {
1261 position: stored_position,
1262 text: stored_text,
1263 color: stored_color,
1264 }) => {
1265 *stored_position = position;
1266 stored_text.clear();
1267 stored_text.push_str(text);
1268 *stored_color = color;
1269 self.len += 1;
1270 }
1271 _ => self.replace_or_push(DebugDrawCommand::String {
1272 position,
1273 text: text.to_owned(),
1274 color,
1275 }),
1276 }
1277 }
1278}
1279
1280unsafe extern "C" fn draw_shape(
1281 user_shape: *mut c_void,
1282 transform: ffi::b3WorldTransform,
1283 color: ffi::b3HexColor,
1284 context: *mut c_void,
1285) {
1286 if context.is_null() {
1287 return;
1288 }
1289 let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1290 run_debug_draw_callback(context, (), |drawer| {
1291 drawer.draw_shape(
1292 debug_shape_handle_from_user_shape(user_shape),
1293 WorldTransform::from_raw(transform),
1294 HexColor::from_ffi(color),
1295 );
1296 });
1297}
1298
1299unsafe extern "C" fn draw_segment(
1300 p1: ffi::b3Pos,
1301 p2: ffi::b3Pos,
1302 color: ffi::b3HexColor,
1303 context: *mut c_void,
1304) {
1305 if context.is_null() {
1306 return;
1307 }
1308 let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1309 run_debug_draw_callback(context, (), |drawer| {
1310 drawer.draw_segment(
1311 Pos::from_raw(p1),
1312 Pos::from_raw(p2),
1313 HexColor::from_ffi(color),
1314 );
1315 });
1316}
1317
1318unsafe extern "C" fn draw_transform(transform: ffi::b3WorldTransform, context: *mut c_void) {
1319 if context.is_null() {
1320 return;
1321 }
1322 let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1323 run_debug_draw_callback(context, (), |drawer| {
1324 drawer.draw_transform(WorldTransform::from_raw(transform));
1325 });
1326}
1327
1328unsafe extern "C" fn draw_point(
1329 position: ffi::b3Pos,
1330 size: f32,
1331 color: ffi::b3HexColor,
1332 context: *mut c_void,
1333) {
1334 if context.is_null() {
1335 return;
1336 }
1337 let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1338 run_debug_draw_callback(context, (), |drawer| {
1339 drawer.draw_point(Pos::from_raw(position), size, HexColor::from_ffi(color));
1340 });
1341}
1342
1343unsafe extern "C" fn draw_sphere(
1344 center: ffi::b3Pos,
1345 radius: f32,
1346 color: ffi::b3HexColor,
1347 alpha: f32,
1348 context: *mut c_void,
1349) {
1350 if context.is_null() {
1351 return;
1352 }
1353 let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1354 run_debug_draw_callback(context, (), |drawer| {
1355 drawer.draw_sphere(
1356 Pos::from_raw(center),
1357 radius,
1358 HexColor::from_ffi(color),
1359 alpha,
1360 );
1361 });
1362}
1363
1364unsafe extern "C" fn draw_capsule(
1365 p1: ffi::b3Pos,
1366 p2: ffi::b3Pos,
1367 radius: f32,
1368 color: ffi::b3HexColor,
1369 alpha: f32,
1370 context: *mut c_void,
1371) {
1372 if context.is_null() {
1373 return;
1374 }
1375 let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1376 run_debug_draw_callback(context, (), |drawer| {
1377 drawer.draw_capsule(
1378 Pos::from_raw(p1),
1379 Pos::from_raw(p2),
1380 radius,
1381 HexColor::from_ffi(color),
1382 alpha,
1383 );
1384 });
1385}
1386
1387unsafe extern "C" fn draw_bounds(aabb: ffi::b3AABB, color: ffi::b3HexColor, context: *mut c_void) {
1388 if context.is_null() {
1389 return;
1390 }
1391 let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1392 run_debug_draw_callback(context, (), |drawer| {
1393 drawer.draw_bounds(Aabb::from_raw(aabb), HexColor::from_ffi(color));
1394 });
1395}
1396
1397unsafe extern "C" fn draw_box(
1398 extents: ffi::b3Vec3,
1399 transform: ffi::b3WorldTransform,
1400 color: ffi::b3HexColor,
1401 context: *mut c_void,
1402) {
1403 if context.is_null() {
1404 return;
1405 }
1406 let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1407 run_debug_draw_callback(context, (), |drawer| {
1408 drawer.draw_box(
1409 Vec3::from_raw(extents),
1410 WorldTransform::from_raw(transform),
1411 HexColor::from_ffi(color),
1412 );
1413 });
1414}
1415
1416unsafe extern "C" fn draw_string(
1417 position: ffi::b3Pos,
1418 text: *const std::ffi::c_char,
1419 color: ffi::b3HexColor,
1420 context: *mut c_void,
1421) {
1422 if context.is_null() || text.is_null() {
1423 return;
1424 }
1425 let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1426 run_debug_draw_callback(context, (), |drawer| {
1427 let text = unsafe { CStr::from_ptr(text) }.to_string_lossy();
1428 drawer.draw_string(Pos::from_raw(position), &text, HexColor::from_ffi(color));
1429 });
1430}
1431
1432pub(crate) fn with_debug_draw(
1433 drawer: &mut dyn DebugDraw,
1434 options: DebugDrawOptions,
1435 invoke: impl FnOnce(&mut ffi::b3DebugDraw) -> Result<()>,
1436) -> Result<()> {
1437 callback_state::check_not_in_callback()?;
1438 #[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
1439 {
1440 let _ = (drawer, options, invoke);
1441 Err(Error::UnsupportedOnWasm)
1442 }
1443 #[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
1444 {
1445 with_debug_draw_context(drawer, options, invoke)
1446 }
1447}
1448
1449pub(crate) fn with_replay_debug_draw(
1450 drawer: &mut dyn DebugDraw,
1451 options: DebugDrawOptions,
1452 lease: &ReplayLease,
1453 invoke: impl FnOnce(&mut ffi::b3DebugDraw) -> Result<()>,
1454) -> Result<()> {
1455 callback_state::check_not_in_callback()?;
1456 #[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
1457 {
1458 let _ = (drawer, options, lease, invoke);
1459 Err(Error::UnsupportedOnWasm)
1460 }
1461 #[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
1462 {
1463 let _call = lease.enter_call()?;
1464 with_debug_draw_context(drawer, options, invoke)
1465 }
1466}
1467
1468#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
1469fn with_debug_draw_context(
1470 drawer: &mut dyn DebugDraw,
1471 options: DebugDrawOptions,
1472 invoke: impl FnOnce(&mut ffi::b3DebugDraw) -> Result<()>,
1473) -> Result<()> {
1474 validate_options(options)?;
1475 let owner_call_frame = callback_state::OwnerCallFrame::enter();
1476 let result = {
1477 let mut context = DebugDrawContext {
1478 drawer,
1479 state: callback_state::LocalCallbackState::new(),
1480 };
1481 let mut draw = unsafe { ffi::b3DefaultDebugDraw() };
1482 draw.DrawShapeFcn = Some(draw_shape);
1483 draw.DrawSegmentFcn = Some(draw_segment);
1484 draw.DrawTransformFcn = Some(draw_transform);
1485 draw.DrawPointFcn = Some(draw_point);
1486 draw.DrawSphereFcn = Some(draw_sphere);
1487 draw.DrawCapsuleFcn = Some(draw_capsule);
1488 draw.DrawBoundsFcn = Some(draw_bounds);
1489 draw.DrawBoxFcn = Some(draw_box);
1490 draw.DrawStringFcn = Some(draw_string);
1491 apply_options(&mut draw, options, &mut context as *mut _ as *mut c_void);
1492
1493 let native_result = invoke(&mut draw);
1494 let callback_result = context.state.drain();
1495 native_result.and(callback_result)
1496 };
1497 drop(owner_call_frame);
1498 result
1499}
1500
1501fn validate_options(options: DebugDrawOptions) -> Result<()> {
1502 validation::finite("debug_draw.force_scale", options.force_scale)?;
1503 validation::finite("debug_draw.joint_scale", options.joint_scale)?;
1504 options.drawing_bounds.validate()?;
1505 Ok(())
1506}
1507
1508impl World {
1509 pub fn debug_draw_frame(&mut self, options: DebugDrawOptions) -> Result<DebugDrawFrame> {
1521 let mut frame = DebugDrawFrame::default();
1522 self.debug_draw_frame_into(&mut frame, options)?;
1523 Ok(frame)
1524 }
1525
1526 pub fn debug_draw_frame_into(
1536 &mut self,
1537 out: &mut DebugDrawFrame,
1538 options: DebugDrawOptions,
1539 ) -> Result<()> {
1540 callback_state::check_not_in_callback()?;
1541 #[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
1542 {
1543 out.clear();
1544 validate_options(options)?;
1545
1546 let token = self.state().provider_debug_shapes_token;
1547 if token == 0 {
1548 return Err(Error::UnsupportedOnWasm);
1549 }
1550
1551 let owner_call_frame = callback_state::OwnerCallFrame::enter();
1552 let frame_guard = ProviderDebugFrameGuard::new(token, &mut out.commands);
1553 let result = {
1554 let _call = self.enter_world_call()?;
1555 let mut draw = unsafe { ffi::b3DefaultDebugDraw() };
1556 unsafe { ffi::boxddd_provider_debug_init_draw(&mut draw, token) };
1557 apply_options(&mut draw, options, token as usize as *mut c_void);
1558 unsafe { ffi::b3World_Draw(self.raw(), &mut draw, options.mask_bits) };
1559 Ok(())
1560 };
1561 drop(frame_guard);
1562
1563 let result = result.and_then(|()| match take_provider_debug_error(token) {
1564 Some(error) => Err(error),
1565 None => Ok(()),
1566 });
1567 self.state().debug_shapes.drain_into(out);
1568 drop(owner_call_frame);
1569 result
1570 }
1571 #[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
1572 {
1573 out.clear();
1574 let mut collector = CollectDebugDraw::new(&mut out.commands);
1575 let result = self.debug_draw(&mut collector, options);
1576 collector.finish();
1577 self.state().debug_shapes.drain_into(out);
1578 result
1579 }
1580 }
1581
1582 pub fn debug_draw(
1594 &mut self,
1595 drawer: &mut impl DebugDraw,
1596 options: DebugDrawOptions,
1597 ) -> Result<()> {
1598 let _call = self.enter_world_call()?;
1599 with_debug_draw(drawer, options, |draw| {
1600 unsafe { ffi::b3World_Draw(self.raw(), draw, options.mask_bits) };
1601 Ok(())
1602 })
1603 }
1604}
1605
1606#[cfg(all(test, not(all(target_arch = "wasm32", boxddd_wasm_provider))))]
1607mod tests {
1608 use super::*;
1609 use crate::{Error, Foundation, Sphere};
1610
1611 #[test]
1612 fn internal_debug_shape_panic_is_contained_and_poisons_the_world() {
1613 let foundation = Foundation::initialize_default().unwrap();
1614 let mut world = foundation.create_world(foundation.world_def()).unwrap();
1615 let body = world.create_body(foundation.body_def()).unwrap();
1616 world
1617 .create_sphere_shape(body, &foundation.shape_def(), &Sphere::new(Vec3::ZERO, 0.5))
1618 .unwrap();
1619
1620 world.state().debug_shapes.panic_next_native_callback();
1621 assert!(world.debug_draw_frame(DebugDrawOptions::default()).is_ok());
1622 assert_eq!(world.gravity().unwrap_err(), Error::OwnerPoisoned);
1623 }
1624}