1use oxihuman_physics::BodyProxies;
7
8#[allow(dead_code)]
12#[derive(Debug, Clone, PartialEq)]
13pub struct DebugLine {
14 pub start: [f32; 3],
15 pub end: [f32; 3],
16 pub color: [f32; 4],
18 pub thickness: f32,
19}
20
21#[allow(dead_code)]
23#[derive(Debug, Clone, PartialEq)]
24pub struct DebugSphere {
25 pub center: [f32; 3],
26 pub radius: f32,
27 pub color: [f32; 4],
29 pub filled: bool,
30}
31
32#[allow(dead_code)]
34#[derive(Debug, Clone, PartialEq)]
35pub struct DebugArrow {
36 pub origin: [f32; 3],
37 pub direction: [f32; 3],
39 pub color: [f32; 4],
41}
42
43#[allow(dead_code)]
45#[derive(Debug, Clone, Default)]
46pub struct DebugDrawList {
47 pub lines: Vec<DebugLine>,
48 pub spheres: Vec<DebugSphere>,
49 pub arrows: Vec<DebugArrow>,
50 pub text_labels: Vec<(String, [f32; 3])>,
52}
53
54impl DebugDrawList {
55 #[allow(dead_code)]
57 pub fn new() -> Self {
58 DebugDrawList::default()
59 }
60
61 #[allow(dead_code)]
63 pub fn add_line(&mut self, start: [f32; 3], end: [f32; 3], color: [f32; 4]) {
64 self.lines.push(DebugLine {
65 start,
66 end,
67 color,
68 thickness: 1.0,
69 });
70 }
71
72 #[allow(dead_code)]
74 pub fn add_sphere(&mut self, center: [f32; 3], radius: f32, color: [f32; 4]) {
75 self.spheres.push(DebugSphere {
76 center,
77 radius,
78 color,
79 filled: false,
80 });
81 }
82
83 #[allow(dead_code)]
85 pub fn add_arrow(&mut self, origin: [f32; 3], dir: [f32; 3], color: [f32; 4]) {
86 self.arrows.push(DebugArrow {
87 origin,
88 direction: dir,
89 color,
90 });
91 }
92
93 #[allow(dead_code)]
95 pub fn add_label(&mut self, text: &str, pos: [f32; 3]) {
96 self.text_labels.push((text.to_string(), pos));
97 }
98
99 #[allow(dead_code)]
101 pub fn clear(&mut self) {
102 self.lines.clear();
103 self.spheres.clear();
104 self.arrows.clear();
105 self.text_labels.clear();
106 }
107
108 #[allow(dead_code)]
110 pub fn total_primitives(&self) -> usize {
111 self.lines.len() + self.spheres.len() + self.arrows.len() + self.text_labels.len()
112 }
113}
114
115#[allow(dead_code)]
119pub fn draw_mesh_normals(
120 list: &mut DebugDrawList,
121 positions: &[[f32; 3]],
122 normals: &[[f32; 3]],
123 scale: f32,
124 color: [f32; 4],
125) {
126 let n = positions.len().min(normals.len());
127 for i in 0..n {
128 let dir = [
129 normals[i][0] * scale,
130 normals[i][1] * scale,
131 normals[i][2] * scale,
132 ];
133 list.add_arrow(positions[i], dir, color);
134 }
135}
136
137#[allow(dead_code)]
139pub fn draw_aabb(list: &mut DebugDrawList, min: [f32; 3], max: [f32; 3], color: [f32; 4]) {
140 let c = [
142 [min[0], min[1], min[2]], [max[0], min[1], min[2]], [max[0], max[1], min[2]], [min[0], max[1], min[2]], [min[0], min[1], max[2]], [max[0], min[1], max[2]], [max[0], max[1], max[2]], [min[0], max[1], max[2]], ];
151 let edges = [
153 (0, 1),
154 (1, 2),
155 (2, 3),
156 (3, 0), (4, 5),
158 (5, 6),
159 (6, 7),
160 (7, 4), (0, 4),
162 (1, 5),
163 (2, 6),
164 (3, 7), ];
166 for (a, b) in edges {
167 list.add_line(c[a], c[b], color);
168 }
169}
170
171#[allow(dead_code)]
173pub fn draw_skeleton(
174 list: &mut DebugDrawList,
175 joint_positions: &[[f32; 3]],
176 parent_indices: &[Option<usize>],
177 color: [f32; 4],
178) {
179 let n = joint_positions.len().min(parent_indices.len());
180 for i in 0..n {
181 if let Some(parent) = parent_indices[i] {
182 if parent < joint_positions.len() {
183 list.add_line(joint_positions[i], joint_positions[parent], color);
184 }
185 }
186 }
187}
188
189#[allow(dead_code)]
193pub fn draw_physics_proxies_debug(
194 list: &mut DebugDrawList,
195 proxies: &BodyProxies,
196 color: [f32; 4],
197) {
198 for sphere in &proxies.spheres {
199 list.add_sphere(sphere.center, sphere.radius, color);
200 }
201 for capsule in &proxies.capsules {
202 list.add_sphere(capsule.center_a, capsule.radius, color);
203 list.add_sphere(capsule.center_b, capsule.radius, color);
204 list.add_line(capsule.center_a, capsule.center_b, color);
205 }
206}
207
208#[allow(dead_code)]
210pub fn debug_draw_to_json(list: &DebugDrawList) -> String {
211 let lines: Vec<String> = list
212 .lines
213 .iter()
214 .map(|l| {
215 format!(
216 r#"{{"start":[{},{},{}],"end":[{},{},{}],"color":[{},{},{},{}],"thickness":{}}}"#,
217 l.start[0],
218 l.start[1],
219 l.start[2],
220 l.end[0],
221 l.end[1],
222 l.end[2],
223 l.color[0],
224 l.color[1],
225 l.color[2],
226 l.color[3],
227 l.thickness
228 )
229 })
230 .collect();
231 let spheres: Vec<String> = list
232 .spheres
233 .iter()
234 .map(|s| {
235 format!(
236 r#"{{"center":[{},{},{}],"radius":{},"color":[{},{},{},{}],"filled":{}}}"#,
237 s.center[0],
238 s.center[1],
239 s.center[2],
240 s.radius,
241 s.color[0],
242 s.color[1],
243 s.color[2],
244 s.color[3],
245 s.filled
246 )
247 })
248 .collect();
249 let arrows: Vec<String> = list
250 .arrows
251 .iter()
252 .map(|a| {
253 format!(
254 r#"{{"origin":[{},{},{}],"direction":[{},{},{}],"color":[{},{},{},{}]}}"#,
255 a.origin[0],
256 a.origin[1],
257 a.origin[2],
258 a.direction[0],
259 a.direction[1],
260 a.direction[2],
261 a.color[0],
262 a.color[1],
263 a.color[2],
264 a.color[3]
265 )
266 })
267 .collect();
268 let labels: Vec<String> = list
269 .text_labels
270 .iter()
271 .map(|(text, pos)| {
272 format!(
273 r#"{{"text":"{}","pos":[{},{},{}]}}"#,
274 json_escape(text),
275 pos[0],
276 pos[1],
277 pos[2]
278 )
279 })
280 .collect();
281
282 format!(
283 r#"{{"lines":[{}],"spheres":[{}],"arrows":[{}],"labels":[{}]}}"#,
284 lines.join(","),
285 spheres.join(","),
286 arrows.join(","),
287 labels.join(",")
288 )
289}
290
291fn json_escape(s: &str) -> String {
294 let mut out = String::with_capacity(s.len());
295 for ch in s.chars() {
296 match ch {
297 '"' => out.push_str("\\\""),
298 '\\' => out.push_str("\\\\"),
299 '\n' => out.push_str("\\n"),
300 '\r' => out.push_str("\\r"),
301 '\t' => out.push_str("\\t"),
302 other => out.push(other),
303 }
304 }
305 out
306}
307
308#[cfg(test)]
311mod tests {
312 use super::*;
313 use oxihuman_physics::{BodyProxies, CapsuleProxy, SphereProxy};
314
315 #[test]
317 fn new_is_empty() {
318 let list = DebugDrawList::new();
319 assert_eq!(list.total_primitives(), 0);
320 }
321
322 #[test]
324 fn add_line_increments_count() {
325 let mut list = DebugDrawList::new();
326 list.add_line([0.0; 3], [1.0; 3], [1.0, 0.0, 0.0, 1.0]);
327 assert_eq!(list.lines.len(), 1);
328 assert_eq!(list.total_primitives(), 1);
329 }
330
331 #[test]
333 fn add_sphere_increments_count() {
334 let mut list = DebugDrawList::new();
335 list.add_sphere([0.0; 3], 1.0, [0.0, 1.0, 0.0, 1.0]);
336 assert_eq!(list.spheres.len(), 1);
337 }
338
339 #[test]
341 fn add_arrow_increments_count() {
342 let mut list = DebugDrawList::new();
343 list.add_arrow([0.0; 3], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0, 1.0]);
344 assert_eq!(list.arrows.len(), 1);
345 }
346
347 #[test]
349 fn add_label_increments_count() {
350 let mut list = DebugDrawList::new();
351 list.add_label("test", [0.0, 1.0, 0.0]);
352 assert_eq!(list.text_labels.len(), 1);
353 assert_eq!(list.text_labels[0].0, "test");
354 }
355
356 #[test]
358 fn clear_resets_all() {
359 let mut list = DebugDrawList::new();
360 list.add_line([0.0; 3], [1.0; 3], [1.0; 4]);
361 list.add_sphere([0.0; 3], 1.0, [1.0; 4]);
362 list.add_arrow([0.0; 3], [1.0, 0.0, 0.0], [1.0; 4]);
363 list.add_label("lbl", [0.0; 3]);
364 list.clear();
365 assert_eq!(list.total_primitives(), 0);
366 }
367
368 #[test]
370 fn total_primitives_sum() {
371 let mut list = DebugDrawList::new();
372 list.add_line([0.0; 3], [1.0; 3], [1.0; 4]);
373 list.add_line([0.0; 3], [2.0; 3], [1.0; 4]);
374 list.add_sphere([0.0; 3], 0.5, [1.0; 4]);
375 list.add_arrow([0.0; 3], [0.0, 1.0, 0.0], [1.0; 4]);
376 list.add_label("a", [0.0; 3]);
377 assert_eq!(list.total_primitives(), 5);
378 }
379
380 #[test]
382 fn draw_aabb_adds_12_lines() {
383 let mut list = DebugDrawList::new();
384 draw_aabb(&mut list, [-1.0; 3], [1.0; 3], [1.0, 1.0, 0.0, 1.0]);
385 assert_eq!(list.lines.len(), 12);
386 }
387
388 #[test]
390 fn draw_mesh_normals_adds_n_arrows() {
391 let positions = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
392 let normals = vec![[0.0, 1.0, 0.0], [0.0, 1.0, 0.0], [0.0, 1.0, 0.0]];
393 let mut list = DebugDrawList::new();
394 draw_mesh_normals(&mut list, &positions, &normals, 0.1, [0.0, 0.0, 1.0, 1.0]);
395 assert_eq!(list.arrows.len(), 3);
396 }
397
398 #[test]
400 fn draw_skeleton_adds_n_minus_1_lines() {
401 let positions = vec![
402 [0.0, 0.0, 0.0],
403 [0.0, 1.0, 0.0],
404 [0.0, 2.0, 0.0],
405 [0.0, 3.0, 0.0],
406 ];
407 let parents: Vec<Option<usize>> = vec![None, Some(0), Some(1), Some(2)];
408 let mut list = DebugDrawList::new();
409 draw_skeleton(&mut list, &positions, &parents, [1.0, 1.0, 1.0, 1.0]);
410 assert_eq!(list.lines.len(), 3); }
412
413 #[test]
415 fn debug_draw_to_json_non_empty() {
416 let mut list = DebugDrawList::new();
417 list.add_line([0.0; 3], [1.0; 3], [1.0; 4]);
418 let json = debug_draw_to_json(&list);
419 assert!(!json.is_empty());
420 assert!(json.contains("lines"));
421 }
422
423 #[test]
425 fn debug_draw_to_json_empty() {
426 let list = DebugDrawList::new();
427 let json = debug_draw_to_json(&list);
428 assert!(json.contains("lines"));
429 assert!(json.contains("spheres"));
430 }
431
432 #[test]
434 fn draw_physics_proxies_debug_no_panic() {
435 let mut proxies = BodyProxies::new();
436 proxies
437 .spheres
438 .push(SphereProxy::new([0.0, 1.0, 0.0], 0.1, "head"));
439 proxies.capsules.push(CapsuleProxy::new(
440 [0.0, 0.5, 0.0],
441 [0.0, 1.0, 0.0],
442 0.15,
443 "torso",
444 ));
445 let mut list = DebugDrawList::new();
446 draw_physics_proxies_debug(&mut list, &proxies, [0.0, 1.0, 0.0, 1.0]);
447 assert_eq!(list.spheres.len(), 3);
449 assert_eq!(list.lines.len(), 1);
450 }
451
452 #[test]
454 fn draw_aabb_min_max_in_lines() {
455 let min = [0.0f32; 3];
456 let max = [2.0f32; 3];
457 let mut list = DebugDrawList::new();
458 draw_aabb(&mut list, min, max, [1.0; 4]);
459 let has_min = list.lines.iter().any(|l| l.start == min || l.end == min);
461 assert!(has_min);
462 }
463}