Skip to main content

boxddd/
debug_draw.rs

1#![cfg_attr(all(target_arch = "wasm32", boxddd_wasm_provider), allow(dead_code))]
2
3use crate::core::{box3d_lock, callback_state};
4use crate::error::{Error, Result};
5use crate::shapes::ShapeType;
6use crate::types::{Aabb, Plane, Pos, ShapeId, Transform, Vec3, WorldTransform};
7use crate::world::World;
8use boxddd_sys::ffi;
9use std::cell::RefCell;
10#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
11use std::collections::HashMap;
12use std::ffi::{CStr, c_void};
13use std::fmt;
14use std::mem;
15use std::slice;
16
17/// Packed Box3D debug color.
18///
19/// Box3D stores RGB in the low 24 bits and may use the high byte for a debug
20/// material preset. Use [`HexColor::rgb_u32`] when only the visible color is
21/// needed, and [`HexColor::raw_u32`] when preserving renderer metadata.
22#[repr(transparent)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
25pub struct HexColor(u32);
26
27impl HexColor {
28    /// Black.
29    pub const BLACK: Self = Self::from_rgb_u32(0x000000);
30    /// White.
31    pub const WHITE: Self = Self::from_rgb_u32(0xffffff);
32    /// Red.
33    pub const RED: Self = Self::from_rgb_u32(0xff0000);
34    /// Green.
35    pub const GREEN: Self = Self::from_rgb_u32(0x00ff00);
36    /// Blue.
37    pub const BLUE: Self = Self::from_rgb_u32(0x0000ff);
38
39    /// Creates a color from red, green, and blue components.
40    #[inline]
41    pub const fn from_rgb(red: u8, green: u8, blue: u8) -> Self {
42        Self(((red as u32) << 16) | ((green as u32) << 8) | blue as u32)
43    }
44
45    /// Creates a color from a packed `0xRRGGBB` value.
46    #[inline]
47    pub const fn from_rgb_u32(rgb: u32) -> Self {
48        Self(rgb & 0x00ff_ffff)
49    }
50
51    /// Creates a color from a raw Box3D debug color payload.
52    #[inline]
53    pub const fn from_raw(raw: u32) -> Self {
54        Self(raw)
55    }
56
57    /// Returns the full raw payload, including the high material byte.
58    #[inline]
59    pub const fn raw_u32(self) -> u32 {
60        self.0
61    }
62
63    /// Returns this color as a packed `0xRRGGBB` value.
64    #[inline]
65    pub const fn rgb_u32(self) -> u32 {
66        self.0 & 0x00ff_ffff
67    }
68
69    /// Returns the full raw Box3D color payload.
70    #[inline]
71    pub const fn into_raw(self) -> u32 {
72        self.0
73    }
74
75    #[inline]
76    fn from_ffi(raw: ffi::b3HexColor) -> Self {
77        Self(raw as u32)
78    }
79}
80
81/// Stable typed handle for a persistent Box3D debug shape asset.
82#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
83#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
84pub struct DebugShapeHandle {
85    /// Slot index owned by the debug shape store.
86    pub index: u32,
87    /// Generation used to reject stale renderer cache entries.
88    pub generation: u32,
89}
90
91impl DebugShapeHandle {
92    /// Creates a handle. Generation zero is reserved as invalid.
93    #[inline]
94    pub const fn new(index: u32, generation: u32) -> Option<Self> {
95        if generation == 0 {
96            None
97        } else {
98            Some(Self { index, generation })
99        }
100    }
101
102    /// Returns whether this handle is non-zero and usable as a renderer cache key.
103    #[inline]
104    pub const fn is_valid(self) -> bool {
105        self.generation != 0
106    }
107}
108
109/// Legacy shape metadata emitted by the `0.1` debug draw command model.
110///
111/// `0.2` debug drawing uses [`DebugShapeHandle`] for frame commands and
112/// [`DebugShapeAsset`] for owned geometry snapshots. This type remains as a
113/// small migration aid for code that stored the former metadata shape.
114#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
115#[derive(Copy, Clone, Debug, PartialEq, Eq)]
116pub struct DebugShape {
117    /// Shape associated with the result.
118    pub shape_id: ShapeId,
119    /// Shape type reported by Box3D, when available.
120    pub shape_type: Option<ShapeType>,
121}
122
123impl DebugShape {
124    /// Converts an owned debug shape asset into its `0.1` metadata view.
125    #[inline]
126    pub const fn from_asset(asset: &DebugShapeAsset) -> Self {
127        Self {
128            shape_id: asset.shape_id,
129            shape_type: Some(asset.shape_type),
130        }
131    }
132}
133
134/// Owned asset emitted when Box3D creates a persistent debug shape.
135#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136#[derive(Clone, Debug, PartialEq)]
137pub struct DebugShapeAsset {
138    /// Stable handle referenced by subsequent shape draw commands.
139    pub handle: DebugShapeHandle,
140    /// Box3D shape that produced this debug asset.
141    pub shape_id: ShapeId,
142    /// Box3D shape kind.
143    pub shape_type: ShapeType,
144    /// Owned renderer-agnostic geometry snapshot.
145    pub geometry: DebugShapeGeometry,
146}
147
148/// Lifecycle event for persistent debug shape assets.
149#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
150#[derive(Clone, Debug, PartialEq)]
151pub enum DebugShapeEvent {
152    /// A new renderer asset should be created or refreshed.
153    Created(DebugShapeAsset),
154    /// A previously emitted asset should be removed from renderer caches.
155    Destroyed {
156        /// Retired handle.
157        handle: DebugShapeHandle,
158    },
159    /// The owning world has invalidated every debug handle.
160    ClearAll,
161}
162
163/// Diagnostic captured while collecting a debug draw frame.
164#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
165#[derive(Clone, Debug, PartialEq, Eq)]
166pub struct DebugDrawDiagnostic {
167    /// Human-readable message.
168    pub message: String,
169}
170
171/// Face polygon copied from a convex hull.
172#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
173#[derive(Clone, Debug, PartialEq)]
174pub struct DebugHullFace {
175    /// Indices into [`DebugShapeGeometry::Hull::points`].
176    pub indices: Vec<u32>,
177    /// Local plane for the face.
178    pub plane: Plane,
179}
180
181/// Owned triangle mesh snapshot used by mesh-like debug geometry.
182#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
183#[derive(Clone, Debug, PartialEq)]
184pub struct DebugMesh {
185    /// Local-space bounds.
186    pub bounds: Aabb,
187    /// Mesh vertices.
188    pub vertices: Vec<Vec3>,
189    /// Mesh triangles.
190    pub triangles: Vec<DebugMeshTriangle>,
191    /// Number of material slots stored by the source shape.
192    pub material_count: i32,
193}
194
195/// Triangle copied from cooked mesh-like data.
196#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
197#[derive(Copy, Clone, Debug, PartialEq, Eq)]
198pub struct DebugMeshTriangle {
199    /// Indices into [`DebugMesh::vertices`].
200    pub indices: [u32; 3],
201    /// Optional per-triangle material index.
202    pub material_index: Option<u8>,
203}
204
205/// Owned child geometry inside a compound debug shape.
206#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
207#[derive(Clone, Debug, PartialEq)]
208pub struct DebugCompoundChild {
209    /// Transform from compound-local space to child-local space.
210    pub transform: Transform,
211    /// Material indices reported by Box3D for this child.
212    pub material_indices: [i32; ffi::B3_MAX_COMPOUND_MESH_MATERIALS as usize],
213    /// Owned child geometry.
214    pub geometry: DebugShapeGeometry,
215}
216
217/// Renderer-agnostic owned geometry for persistent debug shapes.
218#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
219#[derive(Clone, Debug, PartialEq)]
220pub enum DebugShapeGeometry {
221    /// Sphere geometry in the shape's local space.
222    Sphere {
223        /// Local center.
224        center: Vec3,
225        /// Radius.
226        radius: f32,
227    },
228    /// Capsule geometry in the shape's local space.
229    Capsule {
230        /// First local endpoint.
231        center1: Vec3,
232        /// Second local endpoint.
233        center2: Vec3,
234        /// Radius.
235        radius: f32,
236    },
237    /// Convex hull geometry.
238    Hull {
239        /// Local-space bounds.
240        aabb: Aabb,
241        /// Hull points.
242        points: Vec<Vec3>,
243        /// Convex faces as point index polygons.
244        faces: Vec<DebugHullFace>,
245    },
246    /// Cooked triangle mesh geometry.
247    Mesh {
248        /// Owned mesh data.
249        mesh: DebugMesh,
250        /// Per-shape scale.
251        scale: Vec3,
252    },
253    /// Height-field data expanded into a mesh snapshot.
254    HeightField {
255        /// Owned mesh data.
256        mesh: DebugMesh,
257    },
258    /// Compound geometry flattened into owned children.
259    Compound {
260        /// Flattened child shapes.
261        children: Vec<DebugCompoundChild>,
262    },
263}
264
265impl DebugShapeGeometry {
266    /// Returns the Box3D shape type represented by this geometry.
267    #[inline]
268    pub const fn shape_type(&self) -> Option<ShapeType> {
269        match self {
270            Self::Sphere { .. } => Some(ShapeType::Sphere),
271            Self::Capsule { .. } => Some(ShapeType::Capsule),
272            Self::Hull { .. } => Some(ShapeType::Hull),
273            Self::Mesh { .. } => Some(ShapeType::Mesh),
274            Self::HeightField { .. } => Some(ShapeType::HeightField),
275            Self::Compound { .. } => Some(ShapeType::Compound),
276        }
277    }
278}
279
280/// Debug draw command emitted for one frame.
281#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
282#[derive(Clone, Debug, PartialEq)]
283pub enum DebugDrawCommand {
284    /// Draw a persistent shape asset.
285    Shape {
286        /// Optional persistent debug shape handle.
287        handle: Option<DebugShapeHandle>,
288        /// World transform of the shape.
289        transform: WorldTransform,
290        /// Shape color.
291        color: HexColor,
292    },
293    /// Draw a line segment.
294    Segment {
295        /// First endpoint.
296        p1: Pos,
297        /// Second endpoint.
298        p2: Pos,
299        /// Segment color.
300        color: HexColor,
301    },
302    /// Draw a transform basis.
303    Transform(WorldTransform),
304    /// Draw a point marker.
305    Point {
306        /// Point position.
307        position: Pos,
308        /// Point size in debug-draw units.
309        size: f32,
310        /// Point color.
311        color: HexColor,
312    },
313    /// Draw a sphere.
314    Sphere {
315        /// Sphere center.
316        center: Pos,
317        /// Sphere radius.
318        radius: f32,
319        /// Sphere color.
320        color: HexColor,
321        /// Sphere alpha.
322        alpha: f32,
323    },
324    /// Draw a capsule.
325    Capsule {
326        /// First capsule endpoint.
327        p1: Pos,
328        /// Second capsule endpoint.
329        p2: Pos,
330        /// Capsule radius.
331        radius: f32,
332        /// Capsule color.
333        color: HexColor,
334        /// Capsule alpha.
335        alpha: f32,
336    },
337    /// Draw an AABB.
338    Bounds {
339        /// Bounds to draw.
340        aabb: Aabb,
341        /// Bounds color.
342        color: HexColor,
343    },
344    /// Draw an oriented box.
345    Box {
346        /// Box half extents.
347        extents: Vec3,
348        /// Box world transform.
349        transform: WorldTransform,
350        /// Box color.
351        color: HexColor,
352    },
353    /// Draw text.
354    String {
355        /// Text position.
356        position: Pos,
357        /// Text content.
358        text: String,
359        /// Text color.
360        color: HexColor,
361    },
362}
363
364/// Complete data collected from one debug draw pass.
365#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
366#[derive(Clone, Debug, Default, PartialEq)]
367pub struct DebugDrawFrame {
368    /// Persistent shape lifecycle events emitted since the previous drain.
369    pub events: Vec<DebugShapeEvent>,
370    /// Immediate draw commands for the current frame.
371    pub commands: Vec<DebugDrawCommand>,
372    /// Non-fatal diagnostics captured while copying debug shape data.
373    pub diagnostics: Vec<DebugDrawDiagnostic>,
374}
375
376impl DebugDrawFrame {
377    /// Clears all events, commands, and diagnostics while preserving capacity.
378    pub fn clear(&mut self) {
379        self.events.clear();
380        self.commands.clear();
381        self.diagnostics.clear();
382    }
383}
384
385/// Trait implemented by low-level debug draw sinks.
386///
387/// Most users should prefer [`World::try_debug_draw_frame`], which exposes a
388/// lifecycle-correct data model. This trait remains useful for internal helpers
389/// such as recording query visualization and panic containment tests.
390pub trait DebugDraw {
391    /// Draws a persistent shape outline.
392    fn draw_shape(
393        &mut self,
394        _handle: Option<DebugShapeHandle>,
395        _transform: WorldTransform,
396        _color: HexColor,
397    ) {
398    }
399
400    /// Draws a line segment.
401    fn draw_segment(&mut self, _p1: Pos, _p2: Pos, _color: HexColor) {}
402    /// Draws a transform basis.
403    fn draw_transform(&mut self, _transform: WorldTransform) {}
404    /// Draws a point marker.
405    fn draw_point(&mut self, _position: Pos, _size: f32, _color: HexColor) {}
406    /// Draws a sphere.
407    fn draw_sphere(&mut self, _center: Pos, _radius: f32, _color: HexColor, _alpha: f32) {}
408    /// Draws a capsule.
409    fn draw_capsule(&mut self, _p1: Pos, _p2: Pos, _radius: f32, _color: HexColor, _alpha: f32) {}
410    /// Draws an AABB.
411    fn draw_bounds(&mut self, _aabb: Aabb, _color: HexColor) {}
412    /// Draws an oriented box.
413    fn draw_box(&mut self, _extents: Vec3, _transform: WorldTransform, _color: HexColor) {}
414    /// Draws text.
415    fn draw_string(&mut self, _position: Pos, _text: &str, _color: HexColor) {}
416}
417
418/// Options passed to Box3D debug drawing.
419#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
420#[derive(Copy, Clone, Debug)]
421pub struct DebugDrawOptions {
422    /// Bounds limiting debug drawing.
423    pub drawing_bounds: Aabb,
424    /// Collision mask used by the query.
425    pub mask_bits: u64,
426    /// Scale applied to force visualizations.
427    pub force_scale: f32,
428    /// Scale applied to joint visualizations.
429    pub joint_scale: f32,
430    /// Whether shape outlines are drawn.
431    pub draw_shapes: bool,
432    /// Whether joints are drawn.
433    pub draw_joints: bool,
434    /// Whether joint extra details are drawn.
435    pub draw_joint_extras: bool,
436    /// Whether AABBs are drawn.
437    pub draw_bounds: bool,
438    /// Whether mass data is drawn.
439    pub draw_mass: bool,
440    /// Whether body names are drawn.
441    pub draw_body_names: bool,
442    /// Whether contacts are drawn.
443    pub draw_contacts: bool,
444    /// Anchor display mode used by Box3D.
445    pub draw_anchor_a: i32,
446    /// Whether graph-color debug coloring is drawn.
447    pub draw_graph_colors: bool,
448    /// Whether contact feature ids are drawn.
449    pub draw_contact_features: bool,
450    /// Whether contact normals are drawn.
451    pub draw_contact_normals: bool,
452    /// Whether contact forces are drawn.
453    pub draw_contact_forces: bool,
454    /// Whether friction forces are drawn.
455    pub draw_friction_forces: bool,
456    /// Whether solver islands are drawn.
457    pub draw_islands: bool,
458}
459
460impl Default for DebugDrawOptions {
461    fn default() -> Self {
462        Self {
463            drawing_bounds: Aabb {
464                lower_bound: Vec3::new(-1.0e9, -1.0e9, -1.0e9),
465                upper_bound: Vec3::new(1.0e9, 1.0e9, 1.0e9),
466            },
467            mask_bits: u64::MAX,
468            force_scale: 1.0,
469            joint_scale: 1.0,
470            draw_shapes: true,
471            draw_joints: true,
472            draw_joint_extras: false,
473            draw_bounds: false,
474            draw_mass: false,
475            draw_body_names: false,
476            draw_contacts: false,
477            draw_anchor_a: 0,
478            draw_graph_colors: false,
479            draw_contact_features: false,
480            draw_contact_normals: false,
481            draw_contact_forces: false,
482            draw_friction_forces: false,
483            draw_islands: false,
484        }
485    }
486}
487
488#[derive(Default)]
489pub(crate) struct DebugShapeRegistry {
490    inner: RefCell<DebugShapeStore>,
491}
492
493#[derive(Default)]
494struct DebugShapeStore {
495    slots: Vec<DebugShapeSlot>,
496    free: Vec<usize>,
497    events: Vec<DebugShapeEvent>,
498    diagnostics: Vec<DebugDrawDiagnostic>,
499    cleared: bool,
500}
501
502#[derive(Debug)]
503struct DebugShapeSlot {
504    generation: u32,
505    asset: Option<DebugShapeAsset>,
506}
507
508impl fmt::Debug for DebugShapeRegistry {
509    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
510        let inner = self.inner.borrow();
511        f.debug_struct("DebugShapeRegistry")
512            .field("slots", &inner.slots.len())
513            .field("free", &inner.free.len())
514            .field("pending_events", &inner.events.len())
515            .field("pending_diagnostics", &inner.diagnostics.len())
516            .finish()
517    }
518}
519
520#[derive(Copy, Clone, Debug)]
521struct DebugShapeResource {
522    handle: DebugShapeHandle,
523}
524
525impl DebugShapeRegistry {
526    fn create_asset_handle(&self, raw: &ffi::b3DebugShape) -> Option<DebugShapeHandle> {
527        let mut store = self.inner.borrow_mut();
528        let snapshot = match unsafe { snapshot_debug_shape(raw) } {
529            Ok(snapshot) => snapshot,
530            Err(message) => {
531                store.diagnostics.push(DebugDrawDiagnostic {
532                    message: message.to_owned(),
533                });
534                return None;
535            }
536        };
537        let handle = store.alloc_handle();
538        let asset = DebugShapeAsset {
539            handle,
540            shape_id: snapshot.shape_id,
541            shape_type: snapshot.shape_type,
542            geometry: snapshot.geometry,
543        };
544        store.slots[handle.index as usize].asset = Some(asset.clone());
545        store.events.push(DebugShapeEvent::Created(asset));
546        Some(handle)
547    }
548
549    fn create_native_resource(&self, raw: &ffi::b3DebugShape) -> *mut c_void {
550        let Some(handle) = self.create_asset_handle(raw) else {
551            return std::ptr::null_mut();
552        };
553        Box::into_raw(Box::new(DebugShapeResource { handle })) as *mut c_void
554    }
555
556    fn destroy_native_resource(&self, resource: DebugShapeResource) {
557        self.destroy_handle(resource.handle);
558    }
559
560    fn destroy_handle(&self, handle: DebugShapeHandle) {
561        self.inner.borrow_mut().destroy_handle(handle);
562    }
563
564    pub(crate) fn drain_into(&self, frame: &mut DebugDrawFrame) {
565        let mut store = self.inner.borrow_mut();
566        frame.events.extend(store.events.drain(..));
567        frame.diagnostics.extend(store.diagnostics.drain(..));
568    }
569
570    #[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
571    fn push_diagnostic(&self, message: impl Into<String>) {
572        self.inner
573            .borrow_mut()
574            .diagnostics
575            .push(DebugDrawDiagnostic {
576                message: message.into(),
577            });
578    }
579
580    pub(crate) fn clear_all(&self) {
581        let mut store = self.inner.borrow_mut();
582        for slot in &mut store.slots {
583            if slot.asset.take().is_some() {
584                slot.generation = next_generation(slot.generation);
585            }
586        }
587        let slot_len = store.slots.len();
588        store.free.clear();
589        store.free.extend(0..slot_len);
590        store.events.clear();
591        store.diagnostics.clear();
592        store.events.push(DebugShapeEvent::ClearAll);
593        store.cleared = true;
594    }
595}
596
597impl DebugShapeStore {
598    fn alloc_handle(&mut self) -> DebugShapeHandle {
599        self.cleared = false;
600        if let Some(index) = self.free.pop() {
601            let generation = self.slots[index].generation;
602            DebugShapeHandle {
603                index: index as u32,
604                generation,
605            }
606        } else {
607            let index = self.slots.len();
608            self.slots.push(DebugShapeSlot {
609                generation: 1,
610                asset: None,
611            });
612            DebugShapeHandle {
613                index: index as u32,
614                generation: 1,
615            }
616        }
617    }
618
619    fn destroy_handle(&mut self, handle: DebugShapeHandle) {
620        let Some(slot) = self.slots.get_mut(handle.index as usize) else {
621            self.diagnostics.push(DebugDrawDiagnostic {
622                message: "debug shape destroy referenced an unknown handle".to_owned(),
623            });
624            return;
625        };
626        if slot.generation != handle.generation || slot.asset.is_none() {
627            if self.cleared {
628                return;
629            }
630            self.diagnostics.push(DebugDrawDiagnostic {
631                message: "debug shape destroy referenced a stale handle".to_owned(),
632            });
633            return;
634        }
635
636        slot.asset = None;
637        slot.generation = next_generation(slot.generation);
638        self.events.push(DebugShapeEvent::Destroyed { handle });
639        self.free.push(handle.index as usize);
640    }
641}
642
643#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
644thread_local! {
645    static PROVIDER_DEBUG: RefCell<ProviderDebugRegistry> = RefCell::new(ProviderDebugRegistry::default());
646}
647
648#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
649#[derive(Default)]
650struct ProviderDebugRegistry {
651    registries: HashMap<u32, ProviderDebugWorld>,
652    shapes: HashMap<u32, ProviderDebugShape>,
653    next_token: u32,
654    next_shape: u32,
655}
656
657#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
658struct ProviderDebugWorld {
659    registry: *const DebugShapeRegistry,
660    active_commands: Option<*mut Vec<DebugDrawCommand>>,
661    first_error: Option<Error>,
662}
663
664#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
665#[derive(Copy, Clone)]
666struct ProviderDebugShape {
667    token: u32,
668    handle: DebugShapeHandle,
669}
670
671#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
672impl ProviderDebugRegistry {
673    fn allocate_token_id(&mut self) -> Option<u32> {
674        self.next_token = self.next_token.checked_add(1)?;
675        Some(self.next_token)
676    }
677
678    fn allocate_shape_id(&mut self) -> Option<u32> {
679        self.next_shape = self.next_shape.checked_add(1)?;
680        Some(self.next_shape)
681    }
682}
683
684#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
685pub(crate) struct ProviderDebugFrameGuard {
686    token: u32,
687}
688
689#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
690impl ProviderDebugFrameGuard {
691    pub(crate) fn new(token: u32, commands: &mut Vec<DebugDrawCommand>) -> Self {
692        set_provider_debug_frame(token, commands);
693        Self { token }
694    }
695}
696
697#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
698impl Drop for ProviderDebugFrameGuard {
699    fn drop(&mut self) {
700        clear_provider_debug_frame(self.token);
701    }
702}
703
704#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
705pub(crate) fn register_provider_debug_registry(registry: &DebugShapeRegistry) -> Option<u32> {
706    PROVIDER_DEBUG.with(|state| {
707        let mut state = state.borrow_mut();
708        let token = state.allocate_token_id()?;
709        state.registries.insert(
710            token,
711            ProviderDebugWorld {
712                registry: registry as *const DebugShapeRegistry,
713                active_commands: None,
714                first_error: None,
715            },
716        );
717        Some(token)
718    })
719}
720
721#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
722pub(crate) fn unregister_provider_debug_registry(token: u32) {
723    if token == 0 {
724        return;
725    }
726    PROVIDER_DEBUG.with(|state| {
727        let mut state = state.borrow_mut();
728        state.registries.remove(&token);
729        state.shapes.retain(|_, shape| shape.token != token);
730    });
731}
732
733#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
734fn set_provider_debug_frame(token: u32, commands: &mut Vec<DebugDrawCommand>) {
735    PROVIDER_DEBUG.with(|state| {
736        let mut state = state.borrow_mut();
737        if let Some(world) = state.registries.get_mut(&token) {
738            world.active_commands = Some(commands as *mut Vec<DebugDrawCommand>);
739            world.first_error = None;
740        }
741    });
742}
743
744#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
745fn clear_provider_debug_frame(token: u32) {
746    PROVIDER_DEBUG.with(|state| {
747        let mut state = state.borrow_mut();
748        if let Some(world) = state.registries.get_mut(&token) {
749            world.active_commands = None;
750        }
751    });
752}
753
754#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
755pub(crate) fn take_provider_debug_error(token: u32) -> Option<Error> {
756    if token == 0 {
757        return None;
758    }
759    PROVIDER_DEBUG.with(|state| {
760        let mut state = state.borrow_mut();
761        state
762            .registries
763            .get_mut(&token)
764            .and_then(|world| world.first_error.take())
765    })
766}
767
768#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
769fn provider_registry(token: u32) -> Option<*const DebugShapeRegistry> {
770    if token == 0 {
771        return None;
772    }
773    PROVIDER_DEBUG.with(|state| {
774        let state = state.borrow();
775        state.registries.get(&token).map(|world| world.registry)
776    })
777}
778
779#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
780fn provider_record_diagnostic(token: u32, message: impl Into<String>) {
781    if let Some(registry) = provider_registry(token) {
782        unsafe { &*registry }.push_diagnostic(message);
783    }
784}
785
786#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
787fn provider_fail(token: u32, message: impl Into<String>) {
788    if token != 0 {
789        PROVIDER_DEBUG.with(|state| {
790            let mut state = state.borrow_mut();
791            if let Some(world) = state.registries.get_mut(&token) {
792                world
793                    .first_error
794                    .get_or_insert(Error::ProviderCallbackFailed);
795            }
796        });
797    }
798    provider_record_diagnostic(token, message);
799}
800
801#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
802fn provider_alloc_shape(token: u32, handle: DebugShapeHandle) -> Option<u32> {
803    PROVIDER_DEBUG.with(|state| {
804        let mut state = state.borrow_mut();
805        let raw_shape = state.allocate_shape_id()?;
806        state
807            .shapes
808            .insert(raw_shape, ProviderDebugShape { token, handle });
809        Some(raw_shape)
810    })
811}
812
813#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
814fn provider_can_alloc_shape() -> bool {
815    PROVIDER_DEBUG.with(|state| state.borrow().next_shape < u32::MAX)
816}
817
818#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
819fn provider_take_shape(token: u32, raw_shape: u32) -> Option<DebugShapeHandle> {
820    if raw_shape == 0 {
821        return None;
822    }
823    PROVIDER_DEBUG.with(|state| {
824        let mut state = state.borrow_mut();
825        let shape = state.shapes.get(&raw_shape).copied()?;
826        if shape.token != token {
827            return None;
828        }
829        state.shapes.remove(&raw_shape);
830        Some(shape.handle)
831    })
832}
833
834#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
835fn provider_shape_handle(token: u32, raw_shape: u32) -> Option<DebugShapeHandle> {
836    if raw_shape == 0 {
837        return None;
838    }
839    PROVIDER_DEBUG.with(|state| {
840        let state = state.borrow();
841        state
842            .shapes
843            .get(&raw_shape)
844            .copied()
845            .filter(|shape| shape.token == token)
846            .map(|shape| shape.handle)
847    })
848}
849
850#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
851fn provider_push_command(token: u32, command: DebugDrawCommand) -> bool {
852    if token == 0 {
853        return false;
854    }
855    let mut missing_frame = false;
856    let pushed = PROVIDER_DEBUG.with(|state| {
857        let state = state.borrow();
858        let Some(world) = state.registries.get(&token) else {
859            missing_frame = true;
860            return false;
861        };
862        let Some(commands) = world.active_commands else {
863            missing_frame = true;
864            return false;
865        };
866        unsafe { (*commands).push(command) };
867        true
868    });
869    if !pushed && missing_frame {
870        provider_fail(
871            token,
872            "debug draw provider callback arrived without an active frame",
873        );
874    }
875    pushed
876}
877
878#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
879unsafe fn provider_read<T: Copy>(ptr: *const T) -> Option<T> {
880    unsafe { ptr.as_ref().copied() }
881}
882
883#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
884#[unsafe(no_mangle)]
885pub extern "C" fn boxddd_debug_report_error(token: u32, code: u32) {
886    let message = match code {
887        1 => "debug draw provider exports were not registered or were incomplete",
888        2 => "debug draw provider dispatcher threw an exception",
889        _ => "debug draw provider dispatcher failed",
890    };
891    provider_fail(token, message);
892}
893
894#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
895#[unsafe(no_mangle)]
896pub extern "C" fn boxddd_debug_shape_create(
897    token: u32,
898    debug_shape: *const ffi::b3DebugShape,
899) -> u32 {
900    let Some(registry) = provider_registry(token) else {
901        return 0;
902    };
903    let Some(raw) = (unsafe { debug_shape.as_ref() }) else {
904        provider_fail(token, "debug shape create callback received a null shape");
905        return 0;
906    };
907    if !provider_can_alloc_shape() {
908        provider_fail(token, "debug draw provider shape handle table is full");
909        return 0;
910    }
911    let Some(handle) = (unsafe { &*registry }).create_asset_handle(raw) else {
912        return 0;
913    };
914    match provider_alloc_shape(token, handle) {
915        Some(raw_shape) => raw_shape,
916        None => {
917            provider_fail(token, "debug draw provider shape handle table is full");
918            0
919        }
920    }
921}
922
923#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
924#[unsafe(no_mangle)]
925pub extern "C" fn boxddd_debug_shape_destroy(token: u32, raw_shape: u32) {
926    let Some(handle) = provider_take_shape(token, raw_shape) else {
927        provider_record_diagnostic(
928            token,
929            "debug shape destroy referenced an unknown provider handle",
930        );
931        return;
932    };
933    if let Some(registry) = provider_registry(token) {
934        unsafe { &*registry }.destroy_handle(handle);
935    }
936}
937
938#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
939#[unsafe(no_mangle)]
940pub extern "C" fn boxddd_debug_draw_shape(
941    token: u32,
942    raw_shape: u32,
943    transform: *const ffi::b3WorldTransform,
944    color: u32,
945) -> i32 {
946    let Some(transform) = (unsafe { provider_read(transform) }) else {
947        provider_fail(token, "debug draw shape callback received a null transform");
948        return 0;
949    };
950    let handle = provider_shape_handle(token, raw_shape);
951    provider_push_command(
952        token,
953        DebugDrawCommand::Shape {
954            handle,
955            transform: WorldTransform::from_raw(transform),
956            color: HexColor::from_raw(color),
957        },
958    ) as i32
959}
960
961#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
962#[unsafe(no_mangle)]
963pub extern "C" fn boxddd_debug_draw_segment(
964    token: u32,
965    p1: *const ffi::b3Pos,
966    p2: *const ffi::b3Pos,
967    color: u32,
968) {
969    let (Some(p1), Some(p2)) = (unsafe { provider_read(p1) }, unsafe { provider_read(p2) }) else {
970        provider_fail(
971            token,
972            "debug draw segment callback received a null endpoint",
973        );
974        return;
975    };
976    provider_push_command(
977        token,
978        DebugDrawCommand::Segment {
979            p1: Pos::from_raw(p1),
980            p2: Pos::from_raw(p2),
981            color: HexColor::from_raw(color),
982        },
983    );
984}
985
986#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
987#[unsafe(no_mangle)]
988pub extern "C" fn boxddd_debug_draw_transform(token: u32, transform: *const ffi::b3WorldTransform) {
989    let Some(transform) = (unsafe { provider_read(transform) }) else {
990        provider_fail(
991            token,
992            "debug draw transform callback received a null transform",
993        );
994        return;
995    };
996    provider_push_command(
997        token,
998        DebugDrawCommand::Transform(WorldTransform::from_raw(transform)),
999    );
1000}
1001
1002#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
1003#[unsafe(no_mangle)]
1004pub extern "C" fn boxddd_debug_draw_point(
1005    token: u32,
1006    position: *const ffi::b3Pos,
1007    size: f32,
1008    color: u32,
1009) {
1010    let Some(position) = (unsafe { provider_read(position) }) else {
1011        provider_fail(token, "debug draw point callback received a null position");
1012        return;
1013    };
1014    provider_push_command(
1015        token,
1016        DebugDrawCommand::Point {
1017            position: Pos::from_raw(position),
1018            size,
1019            color: HexColor::from_raw(color),
1020        },
1021    );
1022}
1023
1024#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
1025#[unsafe(no_mangle)]
1026pub extern "C" fn boxddd_debug_draw_sphere(
1027    token: u32,
1028    center: *const ffi::b3Pos,
1029    radius: f32,
1030    color: u32,
1031    alpha: f32,
1032) {
1033    let Some(center) = (unsafe { provider_read(center) }) else {
1034        provider_fail(token, "debug draw sphere callback received a null center");
1035        return;
1036    };
1037    provider_push_command(
1038        token,
1039        DebugDrawCommand::Sphere {
1040            center: Pos::from_raw(center),
1041            radius,
1042            color: HexColor::from_raw(color),
1043            alpha,
1044        },
1045    );
1046}
1047
1048#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
1049#[unsafe(no_mangle)]
1050pub extern "C" fn boxddd_debug_draw_capsule(
1051    token: u32,
1052    p1: *const ffi::b3Pos,
1053    p2: *const ffi::b3Pos,
1054    radius: f32,
1055    color: u32,
1056    alpha: f32,
1057) {
1058    let (Some(p1), Some(p2)) = (unsafe { provider_read(p1) }, unsafe { provider_read(p2) }) else {
1059        provider_fail(
1060            token,
1061            "debug draw capsule callback received a null endpoint",
1062        );
1063        return;
1064    };
1065    provider_push_command(
1066        token,
1067        DebugDrawCommand::Capsule {
1068            p1: Pos::from_raw(p1),
1069            p2: Pos::from_raw(p2),
1070            radius,
1071            color: HexColor::from_raw(color),
1072            alpha,
1073        },
1074    );
1075}
1076
1077#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
1078#[unsafe(no_mangle)]
1079pub extern "C" fn boxddd_debug_draw_bounds(token: u32, aabb: *const ffi::b3AABB, color: u32) {
1080    let Some(aabb) = (unsafe { provider_read(aabb) }) else {
1081        provider_fail(token, "debug draw bounds callback received a null AABB");
1082        return;
1083    };
1084    provider_push_command(
1085        token,
1086        DebugDrawCommand::Bounds {
1087            aabb: Aabb::from_raw(aabb),
1088            color: HexColor::from_raw(color),
1089        },
1090    );
1091}
1092
1093#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
1094#[unsafe(no_mangle)]
1095pub extern "C" fn boxddd_debug_draw_box(
1096    token: u32,
1097    extents: *const ffi::b3Vec3,
1098    transform: *const ffi::b3WorldTransform,
1099    color: u32,
1100) {
1101    let (Some(extents), Some(transform)) = (unsafe { provider_read(extents) }, unsafe {
1102        provider_read(transform)
1103    }) else {
1104        provider_fail(token, "debug draw box callback received a null argument");
1105        return;
1106    };
1107    provider_push_command(
1108        token,
1109        DebugDrawCommand::Box {
1110            extents: Vec3::from_raw(extents),
1111            transform: WorldTransform::from_raw(transform),
1112            color: HexColor::from_raw(color),
1113        },
1114    );
1115}
1116
1117#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
1118#[unsafe(no_mangle)]
1119pub extern "C" fn boxddd_debug_draw_string(
1120    token: u32,
1121    position: *const ffi::b3Pos,
1122    text: *const std::ffi::c_char,
1123    color: u32,
1124) {
1125    let Some(position) = (unsafe { provider_read(position) }) else {
1126        provider_fail(token, "debug draw string callback received a null position");
1127        return;
1128    };
1129    if text.is_null() {
1130        provider_fail(
1131            token,
1132            "debug draw string callback received a null text pointer",
1133        );
1134        return;
1135    }
1136    let text = unsafe { CStr::from_ptr(text) }
1137        .to_string_lossy()
1138        .into_owned();
1139    provider_push_command(
1140        token,
1141        DebugDrawCommand::String {
1142            position: Pos::from_raw(position),
1143            text,
1144            color: HexColor::from_raw(color),
1145        },
1146    );
1147}
1148
1149#[inline]
1150const fn next_generation(current: u32) -> u32 {
1151    let next = current.wrapping_add(1);
1152    if next == 0 { 1 } else { next }
1153}
1154
1155#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
1156pub(crate) unsafe extern "C" fn create_debug_shape(
1157    debug_shape: *const ffi::b3DebugShape,
1158    user_context: *mut c_void,
1159) -> *mut c_void {
1160    if debug_shape.is_null() || user_context.is_null() {
1161        return std::ptr::null_mut();
1162    }
1163    let registry = unsafe { &*(user_context as *const DebugShapeRegistry) };
1164    registry.create_native_resource(unsafe { &*debug_shape })
1165}
1166
1167#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
1168pub(crate) unsafe extern "C" fn destroy_debug_shape(
1169    user_shape: *mut c_void,
1170    user_context: *mut c_void,
1171) {
1172    if user_shape.is_null() {
1173        return;
1174    }
1175    let resource = unsafe { *Box::from_raw(user_shape as *mut DebugShapeResource) };
1176    if !user_context.is_null() {
1177        let registry = unsafe { &*(user_context as *const DebugShapeRegistry) };
1178        registry.destroy_native_resource(resource);
1179    }
1180}
1181
1182struct DebugShapeSnapshot {
1183    shape_id: ShapeId,
1184    shape_type: ShapeType,
1185    geometry: DebugShapeGeometry,
1186}
1187
1188type SnapshotResult<T> = std::result::Result<T, &'static str>;
1189
1190unsafe fn snapshot_debug_shape(raw: &ffi::b3DebugShape) -> SnapshotResult<DebugShapeSnapshot> {
1191    let shape_type = ShapeType::from_raw(raw.type_).ok_or("debug shape has unknown shape type")?;
1192    let geometry = unsafe {
1193        match shape_type {
1194            ShapeType::Sphere => snapshot_sphere_ptr(raw.__bindgen_anon_1.sphere)?,
1195            ShapeType::Capsule => snapshot_capsule_ptr(raw.__bindgen_anon_1.capsule)?,
1196            ShapeType::Hull => snapshot_hull_ptr(raw.__bindgen_anon_1.hull)?,
1197            ShapeType::Mesh => snapshot_mesh_ptr(raw.__bindgen_anon_1.mesh)?,
1198            ShapeType::HeightField => snapshot_height_field_ptr(raw.__bindgen_anon_1.heightField)?,
1199            ShapeType::Compound => snapshot_compound_ptr(raw.__bindgen_anon_1.compound)?,
1200        }
1201    };
1202    Ok(DebugShapeSnapshot {
1203        shape_id: ShapeId::from_raw(raw.shapeId),
1204        shape_type,
1205        geometry,
1206    })
1207}
1208
1209unsafe fn snapshot_child_shape(raw: ffi::b3ChildShape) -> SnapshotResult<DebugShapeGeometry> {
1210    unsafe {
1211        match ShapeType::from_raw(raw.type_).ok_or("compound child has unknown shape type")? {
1212            ShapeType::Sphere => Ok(snapshot_sphere(raw.__bindgen_anon_1.sphere)),
1213            ShapeType::Capsule => Ok(snapshot_capsule(raw.__bindgen_anon_1.capsule)),
1214            ShapeType::Hull => snapshot_hull_ptr(raw.__bindgen_anon_1.hull),
1215            ShapeType::Mesh => {
1216                let mesh = raw.__bindgen_anon_1.mesh;
1217                snapshot_mesh(&mesh)
1218            }
1219            ShapeType::Compound => Err("nested compound debug child is not supported by Box3D"),
1220            ShapeType::HeightField => Err("height-field debug child is not supported by Box3D"),
1221        }
1222    }
1223}
1224
1225unsafe fn snapshot_sphere_ptr(ptr: *const ffi::b3Sphere) -> SnapshotResult<DebugShapeGeometry> {
1226    let sphere = unsafe { ptr.as_ref() }.ok_or("debug sphere pointer was null")?;
1227    Ok(snapshot_sphere(*sphere))
1228}
1229
1230fn snapshot_sphere(raw: ffi::b3Sphere) -> DebugShapeGeometry {
1231    DebugShapeGeometry::Sphere {
1232        center: Vec3::from_raw(raw.center),
1233        radius: raw.radius,
1234    }
1235}
1236
1237unsafe fn snapshot_capsule_ptr(ptr: *const ffi::b3Capsule) -> SnapshotResult<DebugShapeGeometry> {
1238    let capsule = unsafe { ptr.as_ref() }.ok_or("debug capsule pointer was null")?;
1239    Ok(snapshot_capsule(*capsule))
1240}
1241
1242fn snapshot_capsule(raw: ffi::b3Capsule) -> DebugShapeGeometry {
1243    DebugShapeGeometry::Capsule {
1244        center1: Vec3::from_raw(raw.center1),
1245        center2: Vec3::from_raw(raw.center2),
1246        radius: raw.radius,
1247    }
1248}
1249
1250unsafe fn snapshot_hull_ptr(ptr: *const ffi::b3HullData) -> SnapshotResult<DebugShapeGeometry> {
1251    let hull = unsafe { ptr.as_ref() }.ok_or("debug hull pointer was null")?;
1252    unsafe { snapshot_hull(hull) }
1253}
1254
1255unsafe fn snapshot_hull(hull: &ffi::b3HullData) -> SnapshotResult<DebugShapeGeometry> {
1256    let points = unsafe {
1257        trailing_slice::<ffi::b3Vec3>(
1258            hull as *const _ as *const u8,
1259            hull.byteCount,
1260            hull.pointOffset,
1261            hull.vertexCount,
1262        )?
1263    }
1264    .iter()
1265    .copied()
1266    .map(Vec3::from_raw)
1267    .collect();
1268    let edges = unsafe {
1269        trailing_slice::<ffi::b3HullHalfEdge>(
1270            hull as *const _ as *const u8,
1271            hull.byteCount,
1272            hull.edgeOffset,
1273            hull.edgeCount,
1274        )?
1275    };
1276    let raw_faces = unsafe {
1277        trailing_slice::<ffi::b3HullFace>(
1278            hull as *const _ as *const u8,
1279            hull.byteCount,
1280            hull.faceOffset,
1281            hull.faceCount,
1282        )?
1283    };
1284    let planes = unsafe {
1285        trailing_slice::<ffi::b3Plane>(
1286            hull as *const _ as *const u8,
1287            hull.byteCount,
1288            hull.planeOffset,
1289            hull.faceCount,
1290        )?
1291    };
1292
1293    let mut faces = Vec::with_capacity(raw_faces.len());
1294    for (face, plane) in raw_faces.iter().zip(planes) {
1295        let start = face.edge as usize;
1296        if start >= edges.len() {
1297            return Err("debug hull face edge index was out of range");
1298        }
1299        let mut indices = Vec::new();
1300        let mut edge_index = start;
1301        for _ in 0..=edges.len() {
1302            let edge = edges
1303                .get(edge_index)
1304                .ok_or("debug hull edge index was out of range")?;
1305            indices.push(edge.origin as u32);
1306            edge_index = edge.next as usize;
1307            if edge_index == start {
1308                break;
1309            }
1310        }
1311        if edge_index != start {
1312            return Err("debug hull face did not form a closed loop");
1313        }
1314        faces.push(DebugHullFace {
1315            indices,
1316            plane: Plane::from_raw(*plane),
1317        });
1318    }
1319
1320    Ok(DebugShapeGeometry::Hull {
1321        aabb: Aabb::from_raw(hull.aabb),
1322        points,
1323        faces,
1324    })
1325}
1326
1327unsafe fn snapshot_mesh_ptr(ptr: *const ffi::b3Mesh) -> SnapshotResult<DebugShapeGeometry> {
1328    let mesh = unsafe { ptr.as_ref() }.ok_or("debug mesh pointer was null")?;
1329    unsafe { snapshot_mesh(mesh) }
1330}
1331
1332unsafe fn snapshot_mesh(mesh: &ffi::b3Mesh) -> SnapshotResult<DebugShapeGeometry> {
1333    let data = unsafe { mesh.data.as_ref() }.ok_or("debug mesh data pointer was null")?;
1334    let raw_vertices = unsafe {
1335        trailing_slice::<ffi::b3Vec3>(
1336            data as *const _ as *const u8,
1337            data.byteCount,
1338            data.vertexOffset,
1339            data.vertexCount,
1340        )?
1341    };
1342    let raw_triangles = unsafe {
1343        trailing_slice::<ffi::b3MeshTriangle>(
1344            data as *const _ as *const u8,
1345            data.byteCount,
1346            data.triangleOffset,
1347            data.triangleCount,
1348        )?
1349    };
1350    let raw_material_indices = if data.materialOffset == 0 {
1351        &[]
1352    } else {
1353        unsafe {
1354            trailing_slice::<u8>(
1355                data as *const _ as *const u8,
1356                data.byteCount,
1357                data.materialOffset,
1358                data.triangleCount,
1359            )?
1360        }
1361    };
1362
1363    let vertices: Vec<_> = raw_vertices.iter().copied().map(Vec3::from_raw).collect();
1364    let mut triangles = Vec::with_capacity(raw_triangles.len());
1365    for (index, triangle) in raw_triangles.iter().enumerate() {
1366        let indices = [triangle.index1, triangle.index2, triangle.index3];
1367        if indices
1368            .iter()
1369            .any(|index| *index < 0 || *index as usize >= vertices.len())
1370        {
1371            return Err("debug mesh triangle index was out of range");
1372        }
1373        triangles.push(DebugMeshTriangle {
1374            indices: [indices[0] as u32, indices[1] as u32, indices[2] as u32],
1375            material_index: raw_material_indices.get(index).copied(),
1376        });
1377    }
1378
1379    Ok(DebugShapeGeometry::Mesh {
1380        mesh: DebugMesh {
1381            bounds: Aabb::from_raw(data.bounds),
1382            vertices,
1383            triangles,
1384            material_count: data.materialCount,
1385        },
1386        scale: Vec3::from_raw(mesh.scale),
1387    })
1388}
1389
1390unsafe fn snapshot_height_field_ptr(
1391    ptr: *const ffi::b3HeightFieldData,
1392) -> SnapshotResult<DebugShapeGeometry> {
1393    let height_field = unsafe { ptr.as_ref() }.ok_or("debug height-field pointer was null")?;
1394    unsafe { snapshot_height_field(height_field) }
1395}
1396
1397unsafe fn snapshot_height_field(
1398    height_field: &ffi::b3HeightFieldData,
1399) -> SnapshotResult<DebugShapeGeometry> {
1400    let sample_count = checked_grid_count(height_field.rowCount, height_field.columnCount)?;
1401    let cell_count = checked_grid_count(height_field.rowCount - 1, height_field.columnCount - 1)?;
1402    let triangle_count = cell_count
1403        .checked_mul(2)
1404        .ok_or("debug height-field triangle count overflowed")?;
1405    let compressed_heights = unsafe {
1406        trailing_slice::<u16>(
1407            height_field as *const _ as *const u8,
1408            height_field.byteCount,
1409            height_field.heightsOffset,
1410            sample_count as i32,
1411        )?
1412    };
1413    let material_indices = unsafe {
1414        trailing_slice::<u8>(
1415            height_field as *const _ as *const u8,
1416            height_field.byteCount,
1417            height_field.materialOffset,
1418            cell_count as i32,
1419        )?
1420    };
1421
1422    let mut vertices = Vec::with_capacity(sample_count);
1423    for row in 0..height_field.rowCount {
1424        for column in 0..height_field.columnCount {
1425            let index = (row * height_field.columnCount + column) as usize;
1426            let height = height_field.minHeight
1427                + height_field.heightScale * compressed_heights[index] as f32;
1428            vertices.push(Vec3::new(
1429                column as f32 * height_field.scale.x,
1430                height * height_field.scale.y,
1431                row as f32 * height_field.scale.z,
1432            ));
1433        }
1434    }
1435
1436    let mut triangles = Vec::with_capacity(triangle_count);
1437    for row in 0..height_field.rowCount - 1 {
1438        for column in 0..height_field.columnCount - 1 {
1439            let cell_index = (row * (height_field.columnCount - 1) + column) as usize;
1440            let material_index = material_indices
1441                .get(cell_index)
1442                .copied()
1443                .ok_or("debug height-field material index was out of range")?;
1444            if material_index == ffi::B3_HEIGHT_FIELD_HOLE as u8 {
1445                continue;
1446            }
1447            let index11 = (row * height_field.columnCount + column) as u32;
1448            let index12 = index11 + 1;
1449            let index21 = ((row + 1) * height_field.columnCount + column) as u32;
1450            let index22 = index21 + 1;
1451            let first = if height_field.clockwise {
1452                [index11, index12, index21]
1453            } else {
1454                [index11, index21, index12]
1455            };
1456            let second = if height_field.clockwise {
1457                [index22, index21, index12]
1458            } else {
1459                [index22, index12, index21]
1460            };
1461            triangles.push(DebugMeshTriangle {
1462                indices: first,
1463                material_index: Some(material_index),
1464            });
1465            triangles.push(DebugMeshTriangle {
1466                indices: second,
1467                material_index: Some(material_index),
1468            });
1469        }
1470    }
1471
1472    Ok(DebugShapeGeometry::HeightField {
1473        mesh: DebugMesh {
1474            bounds: Aabb::from_raw(height_field.aabb),
1475            vertices,
1476            triangles,
1477            material_count: 256,
1478        },
1479    })
1480}
1481
1482unsafe fn snapshot_compound_ptr(
1483    ptr: *const ffi::b3CompoundData,
1484) -> SnapshotResult<DebugShapeGeometry> {
1485    let compound = unsafe { ptr.as_ref() }.ok_or("debug compound pointer was null")?;
1486    let total = compound
1487        .capsuleCount
1488        .checked_add(compound.hullCount)
1489        .and_then(|count| count.checked_add(compound.meshCount))
1490        .and_then(|count| count.checked_add(compound.sphereCount))
1491        .ok_or("debug compound child count overflowed")?;
1492    if total < 0 {
1493        return Err("debug compound child count was negative");
1494    }
1495
1496    let mut children = Vec::with_capacity(total as usize);
1497    for index in 0..total {
1498        let child = unsafe { ffi::b3GetCompoundChild(compound, index) };
1499        children.push(DebugCompoundChild {
1500            transform: Transform::from_raw(child.transform),
1501            material_indices: child.materialIndices,
1502            geometry: unsafe { snapshot_child_shape(child)? },
1503        });
1504    }
1505    Ok(DebugShapeGeometry::Compound { children })
1506}
1507
1508fn checked_grid_count(rows: i32, columns: i32) -> SnapshotResult<usize> {
1509    if rows <= 0 || columns <= 0 {
1510        return Err("debug height-field dimensions were invalid");
1511    }
1512    (rows as usize)
1513        .checked_mul(columns as usize)
1514        .ok_or("debug height-field dimensions overflowed")
1515}
1516
1517unsafe fn trailing_slice<'a, T>(
1518    base: *const u8,
1519    byte_count: i32,
1520    offset: i32,
1521    count: i32,
1522) -> SnapshotResult<&'a [T]> {
1523    if base.is_null() || byte_count <= 0 || offset <= 0 || count < 0 {
1524        return Err("debug shape trailing storage was invalid");
1525    }
1526    let start = offset as usize;
1527    let len = count as usize;
1528    let bytes = len
1529        .checked_mul(mem::size_of::<T>())
1530        .ok_or("debug shape trailing storage length overflowed")?;
1531    let end = start
1532        .checked_add(bytes)
1533        .ok_or("debug shape trailing storage end overflowed")?;
1534    if end > byte_count as usize {
1535        return Err("debug shape trailing storage exceeded its allocation");
1536    }
1537    let ptr = unsafe { base.add(start) as *const T };
1538    Ok(unsafe { slice::from_raw_parts(ptr, len) })
1539}
1540
1541struct DebugDrawContext<'a> {
1542    drawer: &'a mut dyn DebugDraw,
1543    panicked: bool,
1544}
1545
1546fn run_debug_draw_callback<R>(
1547    context: &mut DebugDrawContext<'_>,
1548    default: R,
1549    callback: impl FnOnce(&mut dyn DebugDraw) -> R,
1550) -> R {
1551    if context.panicked {
1552        return default;
1553    }
1554
1555    match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1556        let _guard = callback_state::CallbackGuard::enter();
1557        callback(context.drawer)
1558    })) {
1559        Ok(value) => value,
1560        Err(_) => {
1561            context.panicked = true;
1562            default
1563        }
1564    }
1565}
1566
1567fn debug_shape_handle_from_user_shape(user_shape: *mut c_void) -> Option<DebugShapeHandle> {
1568    if user_shape.is_null() {
1569        None
1570    } else {
1571        Some(unsafe { (*(user_shape as *const DebugShapeResource)).handle })
1572    }
1573}
1574
1575fn apply_options(draw: &mut ffi::b3DebugDraw, options: DebugDrawOptions, context: *mut c_void) {
1576    draw.drawingBounds = options.drawing_bounds.into_raw();
1577    draw.forceScale = options.force_scale;
1578    draw.jointScale = options.joint_scale;
1579    draw.drawShapes = options.draw_shapes;
1580    draw.drawJoints = options.draw_joints;
1581    draw.drawJointExtras = options.draw_joint_extras;
1582    draw.drawBounds = options.draw_bounds;
1583    draw.drawMass = options.draw_mass;
1584    draw.drawBodyNames = options.draw_body_names;
1585    draw.drawContacts = options.draw_contacts;
1586    draw.drawAnchorA = options.draw_anchor_a;
1587    draw.drawGraphColors = options.draw_graph_colors;
1588    draw.drawContactFeatures = options.draw_contact_features;
1589    draw.drawContactNormals = options.draw_contact_normals;
1590    draw.drawContactForces = options.draw_contact_forces;
1591    draw.drawFrictionForces = options.draw_friction_forces;
1592    draw.drawIslands = options.draw_islands;
1593    draw.context = context;
1594}
1595
1596pub(crate) struct CollectDebugDraw<'a> {
1597    commands: &'a mut Vec<DebugDrawCommand>,
1598    len: usize,
1599}
1600
1601impl<'a> CollectDebugDraw<'a> {
1602    pub(crate) fn new(commands: &'a mut Vec<DebugDrawCommand>) -> Self {
1603        Self { commands, len: 0 }
1604    }
1605
1606    pub(crate) fn finish(self) {
1607        self.commands.truncate(self.len);
1608    }
1609
1610    fn replace_or_push(&mut self, command: DebugDrawCommand) {
1611        if let Some(slot) = self.commands.get_mut(self.len) {
1612            *slot = command;
1613        } else {
1614            self.commands.push(command);
1615        }
1616        self.len += 1;
1617    }
1618}
1619
1620impl DebugDraw for CollectDebugDraw<'_> {
1621    fn draw_shape(
1622        &mut self,
1623        handle: Option<DebugShapeHandle>,
1624        transform: WorldTransform,
1625        color: HexColor,
1626    ) {
1627        self.replace_or_push(DebugDrawCommand::Shape {
1628            handle,
1629            transform,
1630            color,
1631        });
1632    }
1633
1634    fn draw_segment(&mut self, p1: Pos, p2: Pos, color: HexColor) {
1635        self.replace_or_push(DebugDrawCommand::Segment { p1, p2, color });
1636    }
1637
1638    fn draw_transform(&mut self, transform: WorldTransform) {
1639        self.replace_or_push(DebugDrawCommand::Transform(transform));
1640    }
1641
1642    fn draw_point(&mut self, position: Pos, size: f32, color: HexColor) {
1643        self.replace_or_push(DebugDrawCommand::Point {
1644            position,
1645            size,
1646            color,
1647        });
1648    }
1649
1650    fn draw_sphere(&mut self, center: Pos, radius: f32, color: HexColor, alpha: f32) {
1651        self.replace_or_push(DebugDrawCommand::Sphere {
1652            center,
1653            radius,
1654            color,
1655            alpha,
1656        });
1657    }
1658
1659    fn draw_capsule(&mut self, p1: Pos, p2: Pos, radius: f32, color: HexColor, alpha: f32) {
1660        self.replace_or_push(DebugDrawCommand::Capsule {
1661            p1,
1662            p2,
1663            radius,
1664            color,
1665            alpha,
1666        });
1667    }
1668
1669    fn draw_bounds(&mut self, aabb: Aabb, color: HexColor) {
1670        self.replace_or_push(DebugDrawCommand::Bounds { aabb, color });
1671    }
1672
1673    fn draw_box(&mut self, extents: Vec3, transform: WorldTransform, color: HexColor) {
1674        self.replace_or_push(DebugDrawCommand::Box {
1675            extents,
1676            transform,
1677            color,
1678        });
1679    }
1680
1681    fn draw_string(&mut self, position: Pos, text: &str, color: HexColor) {
1682        match self.commands.get_mut(self.len) {
1683            Some(DebugDrawCommand::String {
1684                position: stored_position,
1685                text: stored_text,
1686                color: stored_color,
1687            }) => {
1688                *stored_position = position;
1689                stored_text.clear();
1690                stored_text.push_str(text);
1691                *stored_color = color;
1692                self.len += 1;
1693            }
1694            _ => self.replace_or_push(DebugDrawCommand::String {
1695                position,
1696                text: text.to_owned(),
1697                color,
1698            }),
1699        }
1700    }
1701}
1702
1703unsafe extern "C" fn draw_shape(
1704    user_shape: *mut c_void,
1705    transform: ffi::b3WorldTransform,
1706    color: ffi::b3HexColor,
1707    context: *mut c_void,
1708) -> bool {
1709    let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1710    run_debug_draw_callback(context, (), |drawer| {
1711        drawer.draw_shape(
1712            debug_shape_handle_from_user_shape(user_shape),
1713            WorldTransform::from_raw(transform),
1714            HexColor::from_ffi(color),
1715        );
1716    });
1717    !context.panicked
1718}
1719
1720unsafe extern "C" fn draw_segment(
1721    p1: ffi::b3Pos,
1722    p2: ffi::b3Pos,
1723    color: ffi::b3HexColor,
1724    context: *mut c_void,
1725) {
1726    let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1727    run_debug_draw_callback(context, (), |drawer| {
1728        drawer.draw_segment(
1729            Pos::from_raw(p1),
1730            Pos::from_raw(p2),
1731            HexColor::from_ffi(color),
1732        );
1733    });
1734}
1735
1736unsafe extern "C" fn draw_transform(transform: ffi::b3WorldTransform, context: *mut c_void) {
1737    let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1738    run_debug_draw_callback(context, (), |drawer| {
1739        drawer.draw_transform(WorldTransform::from_raw(transform));
1740    });
1741}
1742
1743unsafe extern "C" fn draw_point(
1744    position: ffi::b3Pos,
1745    size: f32,
1746    color: ffi::b3HexColor,
1747    context: *mut c_void,
1748) {
1749    let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1750    run_debug_draw_callback(context, (), |drawer| {
1751        drawer.draw_point(Pos::from_raw(position), size, HexColor::from_ffi(color));
1752    });
1753}
1754
1755unsafe extern "C" fn draw_sphere(
1756    center: ffi::b3Pos,
1757    radius: f32,
1758    color: ffi::b3HexColor,
1759    alpha: f32,
1760    context: *mut c_void,
1761) {
1762    let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1763    run_debug_draw_callback(context, (), |drawer| {
1764        drawer.draw_sphere(
1765            Pos::from_raw(center),
1766            radius,
1767            HexColor::from_ffi(color),
1768            alpha,
1769        );
1770    });
1771}
1772
1773unsafe extern "C" fn draw_capsule(
1774    p1: ffi::b3Pos,
1775    p2: ffi::b3Pos,
1776    radius: f32,
1777    color: ffi::b3HexColor,
1778    alpha: f32,
1779    context: *mut c_void,
1780) {
1781    let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1782    run_debug_draw_callback(context, (), |drawer| {
1783        drawer.draw_capsule(
1784            Pos::from_raw(p1),
1785            Pos::from_raw(p2),
1786            radius,
1787            HexColor::from_ffi(color),
1788            alpha,
1789        );
1790    });
1791}
1792
1793unsafe extern "C" fn draw_bounds(aabb: ffi::b3AABB, color: ffi::b3HexColor, context: *mut c_void) {
1794    let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1795    run_debug_draw_callback(context, (), |drawer| {
1796        drawer.draw_bounds(Aabb::from_raw(aabb), HexColor::from_ffi(color));
1797    });
1798}
1799
1800unsafe extern "C" fn draw_box(
1801    extents: ffi::b3Vec3,
1802    transform: ffi::b3WorldTransform,
1803    color: ffi::b3HexColor,
1804    context: *mut c_void,
1805) {
1806    let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1807    run_debug_draw_callback(context, (), |drawer| {
1808        drawer.draw_box(
1809            Vec3::from_raw(extents),
1810            WorldTransform::from_raw(transform),
1811            HexColor::from_ffi(color),
1812        );
1813    });
1814}
1815
1816unsafe extern "C" fn draw_string(
1817    position: ffi::b3Pos,
1818    text: *const std::ffi::c_char,
1819    color: ffi::b3HexColor,
1820    context: *mut c_void,
1821) {
1822    if text.is_null() {
1823        return;
1824    }
1825    let context = unsafe { &mut *(context as *mut DebugDrawContext<'_>) };
1826    let text = unsafe { CStr::from_ptr(text) }.to_string_lossy();
1827    run_debug_draw_callback(context, (), |drawer| {
1828        drawer.draw_string(Pos::from_raw(position), &text, HexColor::from_ffi(color));
1829    });
1830}
1831
1832pub(crate) fn with_debug_draw(
1833    drawer: &mut dyn DebugDraw,
1834    options: DebugDrawOptions,
1835    invoke: impl FnOnce(&mut ffi::b3DebugDraw) -> Result<()>,
1836) -> Result<()> {
1837    callback_state::check_not_in_callback()?;
1838    #[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
1839    {
1840        let _ = (drawer, options, invoke);
1841        Err(Error::UnsupportedOnWasm)
1842    }
1843    #[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
1844    {
1845        validate_options(options)?;
1846
1847        let mut context = DebugDrawContext {
1848            drawer,
1849            panicked: false,
1850        };
1851        let mut draw = unsafe { ffi::b3DefaultDebugDraw() };
1852        draw.DrawShapeFcn = Some(draw_shape);
1853        draw.DrawSegmentFcn = Some(draw_segment);
1854        draw.DrawTransformFcn = Some(draw_transform);
1855        draw.DrawPointFcn = Some(draw_point);
1856        draw.DrawSphereFcn = Some(draw_sphere);
1857        draw.DrawCapsuleFcn = Some(draw_capsule);
1858        draw.DrawBoundsFcn = Some(draw_bounds);
1859        draw.DrawBoxFcn = Some(draw_box);
1860        draw.DrawStringFcn = Some(draw_string);
1861        apply_options(&mut draw, options, &mut context as *mut _ as *mut c_void);
1862
1863        invoke(&mut draw)?;
1864        if context.panicked {
1865            Err(Error::CallbackPanicked)
1866        } else {
1867            Ok(())
1868        }
1869    }
1870}
1871
1872fn validate_options(options: DebugDrawOptions) -> Result<()> {
1873    if options.force_scale.is_finite()
1874        && options.joint_scale.is_finite()
1875        && options.drawing_bounds.is_valid()
1876    {
1877        Ok(())
1878    } else {
1879        Err(Error::InvalidArgument)
1880    }
1881}
1882
1883impl World {
1884    /// Collects a lifecycle-aware debug draw frame or panics if Box3D rejects the draw.
1885    pub fn debug_draw_frame(&mut self, options: DebugDrawOptions) -> DebugDrawFrame {
1886        self.try_debug_draw_frame(options)
1887            .expect("Box3D debug draw failed")
1888    }
1889
1890    /// Tries to collect a lifecycle-aware debug draw frame.
1891    pub fn try_debug_draw_frame(&mut self, options: DebugDrawOptions) -> Result<DebugDrawFrame> {
1892        let mut frame = DebugDrawFrame::default();
1893        self.try_debug_draw_frame_into(&mut frame, options)?;
1894        Ok(frame)
1895    }
1896
1897    /// Collects a debug draw frame into `out` or panics if Box3D rejects the draw.
1898    pub fn debug_draw_frame_into(&mut self, out: &mut DebugDrawFrame, options: DebugDrawOptions) {
1899        self.try_debug_draw_frame_into(out, options)
1900            .expect("Box3D debug draw failed");
1901    }
1902
1903    /// Tries to collect a debug draw frame into `out`.
1904    pub fn try_debug_draw_frame_into(
1905        &mut self,
1906        out: &mut DebugDrawFrame,
1907        options: DebugDrawOptions,
1908    ) -> Result<()> {
1909        #[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
1910        {
1911            out.clear();
1912            validate_options(options)?;
1913            callback_state::check_not_in_callback()?;
1914
1915            let token = self.provider_debug_shapes_token;
1916            if token == 0 {
1917                return Err(Error::UnsupportedOnWasm);
1918            }
1919
1920            let frame_guard = ProviderDebugFrameGuard::new(token, &mut out.commands);
1921            let mut draw = unsafe { ffi::b3DefaultDebugDraw() };
1922            unsafe { ffi::boxddd_provider_debug_init_draw(&mut draw, token) };
1923            apply_options(&mut draw, options, token as usize as *mut c_void);
1924
1925            let result = {
1926                let _guard = box3d_lock::lock();
1927                self.check_world_valid_locked()?;
1928                unsafe { ffi::b3World_Draw(self.raw(), &mut draw, options.mask_bits) };
1929                Ok(())
1930            };
1931            drop(frame_guard);
1932
1933            let provider_error = unsafe { ffi::boxddd_provider_debug_take_error(token) };
1934            if provider_error != 0 {
1935                boxddd_debug_report_error(token, provider_error as u32);
1936            }
1937            let result = result.and_then(|()| match take_provider_debug_error(token) {
1938                Some(error) => Err(error),
1939                None => Ok(()),
1940            });
1941            self.debug_shapes.drain_into(out);
1942            result
1943        }
1944        #[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
1945        {
1946            out.clear();
1947            let mut collector = CollectDebugDraw::new(&mut out.commands);
1948            let result = self.try_debug_draw(&mut collector, options);
1949            collector.finish();
1950            self.debug_shapes.drain_into(out);
1951            result
1952        }
1953    }
1954
1955    /// Collects debug draw commands or panics if Box3D rejects the draw.
1956    pub fn debug_draw_collect(&mut self, options: DebugDrawOptions) -> Vec<DebugDrawCommand> {
1957        self.try_debug_draw_collect(options)
1958            .expect("Box3D debug draw failed")
1959    }
1960
1961    /// Tries to collect debug draw commands.
1962    pub fn try_debug_draw_collect(
1963        &mut self,
1964        options: DebugDrawOptions,
1965    ) -> Result<Vec<DebugDrawCommand>> {
1966        let mut commands = Vec::new();
1967        self.try_debug_draw_collect_into(&mut commands, options)?;
1968        Ok(commands)
1969    }
1970
1971    /// Collects debug draw commands into `out` or panics if Box3D rejects the draw.
1972    pub fn debug_draw_collect_into(
1973        &mut self,
1974        out: &mut Vec<DebugDrawCommand>,
1975        options: DebugDrawOptions,
1976    ) {
1977        self.try_debug_draw_collect_into(out, options)
1978            .expect("Box3D debug draw failed");
1979    }
1980
1981    /// Tries to collect debug draw commands into `out`.
1982    pub fn try_debug_draw_collect_into(
1983        &mut self,
1984        out: &mut Vec<DebugDrawCommand>,
1985        options: DebugDrawOptions,
1986    ) -> Result<()> {
1987        let mut frame = DebugDrawFrame {
1988            commands: mem::take(out),
1989            ..DebugDrawFrame::default()
1990        };
1991        let result = self.try_debug_draw_frame_into(&mut frame, options);
1992        *out = frame.commands;
1993        result
1994    }
1995
1996    /// Runs debug drawing or panics if Box3D rejects the draw.
1997    pub fn debug_draw(&mut self, drawer: &mut impl DebugDraw, options: DebugDrawOptions) {
1998        self.try_debug_draw(drawer, options)
1999            .expect("Box3D debug draw failed");
2000    }
2001
2002    /// Tries to run debug drawing with a custom sink.
2003    pub fn try_debug_draw(
2004        &mut self,
2005        drawer: &mut impl DebugDraw,
2006        options: DebugDrawOptions,
2007    ) -> Result<()> {
2008        with_debug_draw(drawer, options, |draw| {
2009            let _guard = box3d_lock::lock();
2010            self.check_world_valid_locked()?;
2011            unsafe { ffi::b3World_Draw(self.raw(), draw, options.mask_bits) };
2012            Ok(())
2013        })
2014    }
2015}