Skip to main content

oxihuman_viewer/
render_debug.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! Debug rendering primitives (wireframe, normals, bounding boxes, etc.).
5
6#[allow(dead_code)]
7pub struct DebugLine {
8    pub start: [f32; 3],
9    pub end: [f32; 3],
10    pub color: [f32; 4],
11    pub width: f32,
12}
13
14#[allow(dead_code)]
15pub struct DebugPoint {
16    pub position: [f32; 3],
17    pub color: [f32; 4],
18    pub size: f32,
19}
20
21#[allow(dead_code)]
22pub struct DebugText {
23    pub position: [f32; 3],
24    pub text: String,
25    pub color: [f32; 4],
26    pub scale: f32,
27}
28
29#[allow(dead_code)]
30pub struct DebugDraw {
31    pub lines: Vec<DebugLine>,
32    pub points: Vec<DebugPoint>,
33    pub texts: Vec<DebugText>,
34    pub enabled: bool,
35}
36
37#[allow(dead_code)]
38pub fn new_debug_draw() -> DebugDraw {
39    DebugDraw {
40        lines: Vec::new(),
41        points: Vec::new(),
42        texts: Vec::new(),
43        enabled: true,
44    }
45}
46
47#[allow(dead_code)]
48pub fn draw_line(dd: &mut DebugDraw, start: [f32; 3], end: [f32; 3], color: [f32; 4]) {
49    if !dd.enabled {
50        return;
51    }
52    dd.lines.push(DebugLine {
53        start,
54        end,
55        color,
56        width: 1.0,
57    });
58}
59
60#[allow(dead_code)]
61pub fn draw_point(dd: &mut DebugDraw, pos: [f32; 3], color: [f32; 4], size: f32) {
62    if !dd.enabled {
63        return;
64    }
65    dd.points.push(DebugPoint {
66        position: pos,
67        color,
68        size,
69    });
70}
71
72#[allow(dead_code)]
73pub fn draw_text(dd: &mut DebugDraw, pos: [f32; 3], text: &str, color: [f32; 4]) {
74    if !dd.enabled {
75        return;
76    }
77    dd.texts.push(DebugText {
78        position: pos,
79        text: text.to_string(),
80        color,
81        scale: 1.0,
82    });
83}
84
85/// Draw 12 edges of an axis-aligned bounding box.
86#[allow(dead_code)]
87pub fn draw_aabb(dd: &mut DebugDraw, min: [f32; 3], max: [f32; 3], color: [f32; 4]) {
88    if !dd.enabled {
89        return;
90    }
91    let [x0, y0, z0] = min;
92    let [x1, y1, z1] = max;
93
94    // 8 corners
95    let corners = [
96        [x0, y0, z0],
97        [x1, y0, z0],
98        [x1, y1, z0],
99        [x0, y1, z0], // front face
100        [x0, y0, z1],
101        [x1, y0, z1],
102        [x1, y1, z1],
103        [x0, y1, z1], // back face
104    ];
105
106    // 12 edges
107    let edges: [(usize, usize); 12] = [
108        (0, 1),
109        (1, 2),
110        (2, 3),
111        (3, 0), // front
112        (4, 5),
113        (5, 6),
114        (6, 7),
115        (7, 4), // back
116        (0, 4),
117        (1, 5),
118        (2, 6),
119        (3, 7), // sides
120    ];
121
122    for (a, b) in &edges {
123        draw_line(dd, corners[*a], corners[*b], color);
124    }
125}
126
127/// Draw 3 great circles of a sphere (one per axis), each with `segments` segments.
128#[allow(dead_code)]
129pub fn draw_sphere_wireframe(
130    dd: &mut DebugDraw,
131    center: [f32; 3],
132    radius: f32,
133    color: [f32; 4],
134    segments: u32,
135) {
136    if !dd.enabled {
137        return;
138    }
139    let n = segments as usize;
140    for circle in 0..3usize {
141        for i in 0..n {
142            let t0 = i as f32 / n as f32 * std::f32::consts::TAU;
143            let t1 = (i + 1) as f32 / n as f32 * std::f32::consts::TAU;
144
145            let (s0, c0) = t0.sin_cos();
146            let (s1, c1) = t1.sin_cos();
147
148            let (p0, p1) = match circle {
149                0 => (
150                    [center[0] + radius * c0, center[1] + radius * s0, center[2]],
151                    [center[0] + radius * c1, center[1] + radius * s1, center[2]],
152                ),
153                1 => (
154                    [center[0] + radius * c0, center[1], center[2] + radius * s0],
155                    [center[0] + radius * c1, center[1], center[2] + radius * s1],
156                ),
157                _ => (
158                    [center[0], center[1] + radius * c0, center[2] + radius * s0],
159                    [center[0], center[1] + radius * c1, center[2] + radius * s1],
160                ),
161            };
162
163            draw_line(dd, p0, p1, color);
164        }
165    }
166}
167
168#[allow(dead_code)]
169pub fn draw_normal_vectors(
170    dd: &mut DebugDraw,
171    positions: &[[f32; 3]],
172    normals: &[[f32; 3]],
173    scale: f32,
174    color: [f32; 4],
175) {
176    if !dd.enabled {
177        return;
178    }
179    for (pos, nor) in positions.iter().zip(normals.iter()) {
180        let end = [
181            pos[0] + nor[0] * scale,
182            pos[1] + nor[1] * scale,
183            pos[2] + nor[2] * scale,
184        ];
185        draw_line(dd, *pos, end, color);
186    }
187}
188
189#[allow(dead_code)]
190pub fn draw_skeleton(
191    dd: &mut DebugDraw,
192    positions: &[[f32; 3]],
193    parent_indices: &[Option<usize>],
194    color: [f32; 4],
195) {
196    if !dd.enabled {
197        return;
198    }
199    for (i, parent_opt) in parent_indices.iter().enumerate() {
200        if let Some(parent) = parent_opt {
201            if i < positions.len() && *parent < positions.len() {
202                draw_line(dd, positions[i], positions[*parent], color);
203            }
204        }
205    }
206}
207
208/// Draw triangle edges from an indexed mesh.
209#[allow(dead_code)]
210pub fn draw_wireframe(
211    dd: &mut DebugDraw,
212    positions: &[[f32; 3]],
213    indices: &[u32],
214    color: [f32; 4],
215) {
216    if !dd.enabled {
217        return;
218    }
219    let tri_count = indices.len() / 3;
220    for t in 0..tri_count {
221        let i0 = indices[t * 3] as usize;
222        let i1 = indices[t * 3 + 1] as usize;
223        let i2 = indices[t * 3 + 2] as usize;
224        if i0 < positions.len() && i1 < positions.len() && i2 < positions.len() {
225            draw_line(dd, positions[i0], positions[i1], color);
226            draw_line(dd, positions[i1], positions[i2], color);
227            draw_line(dd, positions[i2], positions[i0], color);
228        }
229    }
230}
231
232#[allow(dead_code)]
233pub fn clear_debug_draw(dd: &mut DebugDraw) {
234    dd.lines.clear();
235    dd.points.clear();
236    dd.texts.clear();
237}
238
239#[allow(dead_code)]
240pub fn line_count(dd: &DebugDraw) -> usize {
241    dd.lines.len()
242}
243
244#[allow(dead_code)]
245pub fn total_primitive_count(dd: &DebugDraw) -> usize {
246    dd.lines.len() + dd.points.len() + dd.texts.len()
247}
248
249#[allow(dead_code)]
250pub fn set_enabled(dd: &mut DebugDraw, enabled: bool) {
251    dd.enabled = enabled;
252}
253
254// ── Tests ─────────────────────────────────────────────────────────────────────
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259
260    const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
261
262    #[test]
263    fn test_draw_line_adds_one() {
264        let mut dd = new_debug_draw();
265        draw_line(&mut dd, [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], WHITE);
266        assert_eq!(line_count(&dd), 1);
267    }
268
269    #[test]
270    fn test_draw_aabb_adds_12_lines() {
271        let mut dd = new_debug_draw();
272        draw_aabb(&mut dd, [0.0, 0.0, 0.0], [1.0, 1.0, 1.0], WHITE);
273        assert_eq!(line_count(&dd), 12);
274    }
275
276    #[test]
277    fn test_draw_sphere_wireframe_segments() {
278        let segments = 16u32;
279        let mut dd = new_debug_draw();
280        draw_sphere_wireframe(&mut dd, [0.0, 0.0, 0.0], 1.0, WHITE, segments);
281        assert_eq!(line_count(&dd), (segments * 3) as usize);
282    }
283
284    #[test]
285    fn test_draw_normal_vectors_adds_n_lines() {
286        let mut dd = new_debug_draw();
287        let positions: Vec<[f32; 3]> = (0..5).map(|i| [i as f32, 0.0, 0.0]).collect();
288        let normals: Vec<[f32; 3]> = vec![[0.0, 1.0, 0.0]; 5];
289        draw_normal_vectors(&mut dd, &positions, &normals, 0.1, WHITE);
290        assert_eq!(line_count(&dd), 5);
291    }
292
293    #[test]
294    fn test_draw_wireframe_adds_triangle_count_times_3() {
295        let mut dd = new_debug_draw();
296        let positions: Vec<[f32; 3]> = vec![
297            [0.0, 0.0, 0.0],
298            [1.0, 0.0, 0.0],
299            [0.5, 1.0, 0.0],
300            [1.5, 0.0, 0.0],
301            [2.0, 1.0, 0.0],
302        ];
303        let indices: Vec<u32> = vec![0, 1, 2, 1, 3, 4];
304        draw_wireframe(&mut dd, &positions, &indices, WHITE);
305        assert_eq!(line_count(&dd), 6); // 2 triangles * 3
306    }
307
308    #[test]
309    fn test_clear_resets_counts() {
310        let mut dd = new_debug_draw();
311        draw_line(&mut dd, [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], WHITE);
312        draw_point(&mut dd, [0.0, 0.0, 0.0], WHITE, 1.0);
313        draw_text(&mut dd, [0.0, 0.0, 0.0], "hi", WHITE);
314        clear_debug_draw(&mut dd);
315        assert_eq!(total_primitive_count(&dd), 0);
316    }
317
318    #[test]
319    fn test_total_primitive_count() {
320        let mut dd = new_debug_draw();
321        draw_line(&mut dd, [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], WHITE);
322        draw_point(&mut dd, [0.0, 0.0, 0.0], WHITE, 2.0);
323        draw_text(&mut dd, [0.0, 0.0, 0.0], "test", WHITE);
324        assert_eq!(total_primitive_count(&dd), 3);
325    }
326
327    #[test]
328    fn test_draw_point_adds_one() {
329        let mut dd = new_debug_draw();
330        draw_point(&mut dd, [0.0, 0.0, 0.0], WHITE, 1.0);
331        assert_eq!(dd.points.len(), 1);
332    }
333
334    #[test]
335    fn test_draw_text_adds_one() {
336        let mut dd = new_debug_draw();
337        draw_text(&mut dd, [0.0, 0.0, 0.0], "hello", WHITE);
338        assert_eq!(dd.texts.len(), 1);
339    }
340
341    #[test]
342    fn test_set_enabled_false_no_lines() {
343        let mut dd = new_debug_draw();
344        set_enabled(&mut dd, false);
345        draw_line(&mut dd, [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], WHITE);
346        assert_eq!(line_count(&dd), 0);
347    }
348
349    #[test]
350    fn test_draw_skeleton_adds_lines() {
351        let mut dd = new_debug_draw();
352        let positions: Vec<[f32; 3]> = vec![[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 2.0, 0.0]];
353        let parents: Vec<Option<usize>> = vec![None, Some(0), Some(1)];
354        draw_skeleton(&mut dd, &positions, &parents, WHITE);
355        assert_eq!(line_count(&dd), 2);
356    }
357
358    #[test]
359    fn test_new_debug_draw_empty() {
360        let dd = new_debug_draw();
361        assert_eq!(total_primitive_count(&dd), 0);
362        assert!(dd.enabled);
363    }
364
365    #[test]
366    fn test_line_count_direct() {
367        let mut dd = new_debug_draw();
368        for _ in 0..7 {
369            draw_line(&mut dd, [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], WHITE);
370        }
371        assert_eq!(line_count(&dd), 7);
372    }
373}