Skip to main content

oxihuman_export/
collision_box_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5/// Export axis-aligned and oriented bounding box collision shapes.
6#[allow(dead_code)]
7pub struct CollisionBox {
8    pub name: String,
9    pub center: [f32; 3],
10    pub half_extents: [f32; 3],
11    pub rotation: [f32; 4], // quaternion xyzw
12}
13
14#[allow(dead_code)]
15pub struct CollisionBoxBundle {
16    pub boxes: Vec<CollisionBox>,
17}
18
19#[allow(dead_code)]
20pub fn new_collision_box_bundle() -> CollisionBoxBundle {
21    CollisionBoxBundle { boxes: vec![] }
22}
23
24#[allow(dead_code)]
25pub fn add_collision_box(bundle: &mut CollisionBoxBundle, b: CollisionBox) {
26    bundle.boxes.push(b);
27}
28
29#[allow(dead_code)]
30pub fn collision_box_count(bundle: &CollisionBoxBundle) -> usize {
31    bundle.boxes.len()
32}
33
34#[allow(dead_code)]
35pub fn box_volume(b: &CollisionBox) -> f32 {
36    b.half_extents[0] * b.half_extents[1] * b.half_extents[2] * 8.0
37}
38
39#[allow(dead_code)]
40pub fn box_surface_area(b: &CollisionBox) -> f32 {
41    let [ex, ey, ez] = b.half_extents;
42    8.0 * (ex * ey + ey * ez + ez * ex)
43}
44
45#[allow(dead_code)]
46pub fn default_collision_box(name: &str) -> CollisionBox {
47    CollisionBox {
48        name: name.to_string(),
49        center: [0.0; 3],
50        half_extents: [0.5, 0.5, 0.5],
51        rotation: [0.0, 0.0, 0.0, 1.0],
52    }
53}
54
55#[allow(dead_code)]
56pub fn validate_collision_box(b: &CollisionBox) -> bool {
57    b.half_extents.iter().all(|&e| e > 0.0) && !b.name.is_empty()
58}
59
60#[allow(dead_code)]
61pub fn collision_box_to_json(b: &CollisionBox) -> String {
62    format!(
63        "{{\"name\":\"{}\",\"center\":[{},{},{}],\"half_extents\":[{},{},{}]}}",
64        b.name,
65        b.center[0],
66        b.center[1],
67        b.center[2],
68        b.half_extents[0],
69        b.half_extents[1],
70        b.half_extents[2]
71    )
72}
73
74#[allow(dead_code)]
75pub fn collision_box_bundle_to_json(bundle: &CollisionBoxBundle) -> String {
76    format!("{{\"box_count\":{}}}", bundle.boxes.len())
77}
78
79#[allow(dead_code)]
80pub fn find_box_by_name<'a>(
81    bundle: &'a CollisionBoxBundle,
82    name: &str,
83) -> Option<&'a CollisionBox> {
84    bundle.boxes.iter().find(|b| b.name == name)
85}
86
87#[allow(dead_code)]
88pub fn total_box_volume(bundle: &CollisionBoxBundle) -> f32 {
89    bundle.boxes.iter().map(box_volume).sum()
90}
91
92#[allow(dead_code)]
93pub fn point_in_box(b: &CollisionBox, p: [f32; 3]) -> bool {
94    // AABB test (ignoring rotation)
95    (p[0] - b.center[0]).abs() <= b.half_extents[0]
96        && (p[1] - b.center[1]).abs() <= b.half_extents[1]
97        && (p[2] - b.center[2]).abs() <= b.half_extents[2]
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    fn unit_box() -> CollisionBox {
105        default_collision_box("torso")
106    }
107
108    #[test]
109    fn test_add_box() {
110        let mut b = new_collision_box_bundle();
111        add_collision_box(&mut b, unit_box());
112        assert_eq!(collision_box_count(&b), 1);
113    }
114
115    #[test]
116    fn test_box_volume() {
117        let b = unit_box();
118        assert!((box_volume(&b) - 1.0).abs() < 1e-5);
119    }
120
121    #[test]
122    fn test_box_surface_area() {
123        let b = unit_box();
124        assert!((box_surface_area(&b) - 6.0).abs() < 1e-5);
125    }
126
127    #[test]
128    fn test_validate_box() {
129        let b = unit_box();
130        assert!(validate_collision_box(&b));
131    }
132
133    #[test]
134    fn test_validate_zero_extent_fails() {
135        let mut b = unit_box();
136        b.half_extents[0] = 0.0;
137        assert!(!validate_collision_box(&b));
138    }
139
140    #[test]
141    fn test_find_box_found() {
142        let mut bundle = new_collision_box_bundle();
143        add_collision_box(&mut bundle, unit_box());
144        assert!(find_box_by_name(&bundle, "torso").is_some());
145    }
146
147    #[test]
148    fn test_point_in_box() {
149        let b = unit_box();
150        assert!(point_in_box(&b, [0.0, 0.0, 0.0]));
151        assert!(!point_in_box(&b, [1.0, 0.0, 0.0]));
152    }
153
154    #[test]
155    fn test_total_volume() {
156        let mut bundle = new_collision_box_bundle();
157        add_collision_box(&mut bundle, unit_box());
158        add_collision_box(&mut bundle, unit_box());
159        assert!((total_box_volume(&bundle) - 2.0).abs() < 1e-5);
160    }
161
162    #[test]
163    fn test_to_json() {
164        let b = unit_box();
165        let j = collision_box_to_json(&b);
166        assert!(j.contains("torso"));
167    }
168
169    #[test]
170    fn test_bundle_to_json() {
171        let mut bundle = new_collision_box_bundle();
172        add_collision_box(&mut bundle, unit_box());
173        let j = collision_box_bundle_to_json(&bundle);
174        assert!(j.contains("box_count"));
175    }
176}