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#[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 pub const BLACK: Self = Self::from_rgb_u32(0x000000);
30 pub const WHITE: Self = Self::from_rgb_u32(0xffffff);
32 pub const RED: Self = Self::from_rgb_u32(0xff0000);
34 pub const GREEN: Self = Self::from_rgb_u32(0x00ff00);
36 pub const BLUE: Self = Self::from_rgb_u32(0x0000ff);
38
39 #[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 #[inline]
47 pub const fn from_rgb_u32(rgb: u32) -> Self {
48 Self(rgb & 0x00ff_ffff)
49 }
50
51 #[inline]
53 pub const fn from_raw(raw: u32) -> Self {
54 Self(raw)
55 }
56
57 #[inline]
59 pub const fn raw_u32(self) -> u32 {
60 self.0
61 }
62
63 #[inline]
65 pub const fn rgb_u32(self) -> u32 {
66 self.0 & 0x00ff_ffff
67 }
68
69 #[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#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
83#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
84pub struct DebugShapeHandle {
85 pub index: u32,
87 pub generation: u32,
89}
90
91impl DebugShapeHandle {
92 #[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 #[inline]
104 pub const fn is_valid(self) -> bool {
105 self.generation != 0
106 }
107}
108
109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
115#[derive(Copy, Clone, Debug, PartialEq, Eq)]
116pub struct DebugShape {
117 pub shape_id: ShapeId,
119 pub shape_type: Option<ShapeType>,
121}
122
123impl DebugShape {
124 #[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#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136#[derive(Clone, Debug, PartialEq)]
137pub struct DebugShapeAsset {
138 pub handle: DebugShapeHandle,
140 pub shape_id: ShapeId,
142 pub shape_type: ShapeType,
144 pub geometry: DebugShapeGeometry,
146}
147
148#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
150#[derive(Clone, Debug, PartialEq)]
151pub enum DebugShapeEvent {
152 Created(DebugShapeAsset),
154 Destroyed {
156 handle: DebugShapeHandle,
158 },
159 ClearAll,
161}
162
163#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
165#[derive(Clone, Debug, PartialEq, Eq)]
166pub struct DebugDrawDiagnostic {
167 pub message: String,
169}
170
171#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
173#[derive(Clone, Debug, PartialEq)]
174pub struct DebugHullFace {
175 pub indices: Vec<u32>,
177 pub plane: Plane,
179}
180
181#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
183#[derive(Clone, Debug, PartialEq)]
184pub struct DebugMesh {
185 pub bounds: Aabb,
187 pub vertices: Vec<Vec3>,
189 pub triangles: Vec<DebugMeshTriangle>,
191 pub material_count: i32,
193}
194
195#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
197#[derive(Copy, Clone, Debug, PartialEq, Eq)]
198pub struct DebugMeshTriangle {
199 pub indices: [u32; 3],
201 pub material_index: Option<u8>,
203}
204
205#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
207#[derive(Clone, Debug, PartialEq)]
208pub struct DebugCompoundChild {
209 pub transform: Transform,
211 pub material_indices: [i32; ffi::B3_MAX_COMPOUND_MESH_MATERIALS as usize],
213 pub geometry: DebugShapeGeometry,
215}
216
217#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
219#[derive(Clone, Debug, PartialEq)]
220pub enum DebugShapeGeometry {
221 Sphere {
223 center: Vec3,
225 radius: f32,
227 },
228 Capsule {
230 center1: Vec3,
232 center2: Vec3,
234 radius: f32,
236 },
237 Hull {
239 aabb: Aabb,
241 points: Vec<Vec3>,
243 faces: Vec<DebugHullFace>,
245 },
246 Mesh {
248 mesh: DebugMesh,
250 scale: Vec3,
252 },
253 HeightField {
255 mesh: DebugMesh,
257 },
258 Compound {
260 children: Vec<DebugCompoundChild>,
262 },
263}
264
265impl DebugShapeGeometry {
266 #[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#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
282#[derive(Clone, Debug, PartialEq)]
283pub enum DebugDrawCommand {
284 Shape {
286 handle: Option<DebugShapeHandle>,
288 transform: WorldTransform,
290 color: HexColor,
292 },
293 Segment {
295 p1: Pos,
297 p2: Pos,
299 color: HexColor,
301 },
302 Transform(WorldTransform),
304 Point {
306 position: Pos,
308 size: f32,
310 color: HexColor,
312 },
313 Sphere {
315 center: Pos,
317 radius: f32,
319 color: HexColor,
321 alpha: f32,
323 },
324 Capsule {
326 p1: Pos,
328 p2: Pos,
330 radius: f32,
332 color: HexColor,
334 alpha: f32,
336 },
337 Bounds {
339 aabb: Aabb,
341 color: HexColor,
343 },
344 Box {
346 extents: Vec3,
348 transform: WorldTransform,
350 color: HexColor,
352 },
353 String {
355 position: Pos,
357 text: String,
359 color: HexColor,
361 },
362}
363
364#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
366#[derive(Clone, Debug, Default, PartialEq)]
367pub struct DebugDrawFrame {
368 pub events: Vec<DebugShapeEvent>,
370 pub commands: Vec<DebugDrawCommand>,
372 pub diagnostics: Vec<DebugDrawDiagnostic>,
374}
375
376impl DebugDrawFrame {
377 pub fn clear(&mut self) {
379 self.events.clear();
380 self.commands.clear();
381 self.diagnostics.clear();
382 }
383}
384
385pub trait DebugDraw {
391 fn draw_shape(
393 &mut self,
394 _handle: Option<DebugShapeHandle>,
395 _transform: WorldTransform,
396 _color: HexColor,
397 ) {
398 }
399
400 fn draw_segment(&mut self, _p1: Pos, _p2: Pos, _color: HexColor) {}
402 fn draw_transform(&mut self, _transform: WorldTransform) {}
404 fn draw_point(&mut self, _position: Pos, _size: f32, _color: HexColor) {}
406 fn draw_sphere(&mut self, _center: Pos, _radius: f32, _color: HexColor, _alpha: f32) {}
408 fn draw_capsule(&mut self, _p1: Pos, _p2: Pos, _radius: f32, _color: HexColor, _alpha: f32) {}
410 fn draw_bounds(&mut self, _aabb: Aabb, _color: HexColor) {}
412 fn draw_box(&mut self, _extents: Vec3, _transform: WorldTransform, _color: HexColor) {}
414 fn draw_string(&mut self, _position: Pos, _text: &str, _color: HexColor) {}
416}
417
418#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
420#[derive(Copy, Clone, Debug)]
421pub struct DebugDrawOptions {
422 pub drawing_bounds: Aabb,
424 pub mask_bits: u64,
426 pub force_scale: f32,
428 pub joint_scale: f32,
430 pub draw_shapes: bool,
432 pub draw_joints: bool,
434 pub draw_joint_extras: bool,
436 pub draw_bounds: bool,
438 pub draw_mass: bool,
440 pub draw_body_names: bool,
442 pub draw_contacts: bool,
444 pub draw_anchor_a: i32,
446 pub draw_graph_colors: bool,
448 pub draw_contact_features: bool,
450 pub draw_contact_normals: bool,
452 pub draw_contact_forces: bool,
454 pub draw_friction_forces: bool,
456 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 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 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 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 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 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 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 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 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 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 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}