Skip to main content

oxihuman_export/
collision_shape_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Collision shape export for physics engines.
6
7use std::f32::consts::PI;
8
9/// Type of collision primitive.
10#[allow(dead_code)]
11#[derive(Debug, Clone, PartialEq)]
12pub enum CollisionShapeType {
13    Sphere,
14    Box,
15    Capsule,
16    ConvexHull,
17    Mesh,
18}
19
20/// A collision shape descriptor.
21#[allow(dead_code)]
22#[derive(Debug, Clone)]
23pub struct CollisionShapeExport {
24    pub shape_type: CollisionShapeType,
25    pub name: String,
26    pub position: [f32; 3],
27    pub rotation: [f32; 4],
28    pub scale: [f32; 3],
29    pub params: [f32; 4],
30}
31
32/// Collection of collision shapes.
33#[allow(dead_code)]
34#[derive(Debug, Clone)]
35pub struct CollisionShapeBundle {
36    pub shapes: Vec<CollisionShapeExport>,
37}
38
39/// Create a sphere collision shape.
40#[allow(dead_code)]
41pub fn sphere_collision(name: &str, center: [f32; 3], radius: f32) -> CollisionShapeExport {
42    CollisionShapeExport {
43        shape_type: CollisionShapeType::Sphere,
44        name: name.to_string(),
45        position: center,
46        rotation: [0.0, 0.0, 0.0, 1.0],
47        scale: [1.0; 3],
48        params: [radius, 0.0, 0.0, 0.0],
49    }
50}
51
52/// Create a box collision shape (half-extents).
53#[allow(dead_code)]
54pub fn box_collision(name: &str, center: [f32; 3], half_extents: [f32; 3]) -> CollisionShapeExport {
55    CollisionShapeExport {
56        shape_type: CollisionShapeType::Box,
57        name: name.to_string(),
58        position: center,
59        rotation: [0.0, 0.0, 0.0, 1.0],
60        scale: [1.0; 3],
61        params: [half_extents[0], half_extents[1], half_extents[2], 0.0],
62    }
63}
64
65/// New empty bundle.
66#[allow(dead_code)]
67pub fn new_collision_bundle() -> CollisionShapeBundle {
68    CollisionShapeBundle { shapes: Vec::new() }
69}
70
71/// Add a shape to bundle.
72#[allow(dead_code)]
73pub fn add_collision_shape(bundle: &mut CollisionShapeBundle, shape: CollisionShapeExport) {
74    bundle.shapes.push(shape);
75}
76
77/// Shape count.
78#[allow(dead_code)]
79pub fn collision_shape_count(bundle: &CollisionShapeBundle) -> usize {
80    bundle.shapes.len()
81}
82
83/// Volume estimate for a shape (sphere or box).
84#[allow(dead_code)]
85pub fn shape_volume(shape: &CollisionShapeExport) -> f32 {
86    match shape.shape_type {
87        CollisionShapeType::Sphere => (4.0 / 3.0) * PI * shape.params[0].powi(3),
88        CollisionShapeType::Box => 8.0 * shape.params[0] * shape.params[1] * shape.params[2],
89        _ => 0.0,
90    }
91}
92
93/// Validate bundle: all shape names non-empty.
94#[allow(dead_code)]
95pub fn validate_collision_bundle(bundle: &CollisionShapeBundle) -> bool {
96    bundle.shapes.iter().all(|s| !s.name.is_empty())
97}
98
99/// Export to JSON.
100#[allow(dead_code)]
101pub fn collision_bundle_to_json(bundle: &CollisionShapeBundle) -> String {
102    format!("{{\"shape_count\":{}}}", collision_shape_count(bundle))
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_sphere_collision() {
111        let s = sphere_collision("sphere0", [0.0; 3], 1.0);
112        assert_eq!(s.shape_type, CollisionShapeType::Sphere);
113    }
114
115    #[test]
116    fn test_box_collision() {
117        let b = box_collision("box0", [0.0; 3], [1.0, 1.0, 1.0]);
118        assert_eq!(b.shape_type, CollisionShapeType::Box);
119    }
120
121    #[test]
122    fn test_add_collision_shape() {
123        let mut bundle = new_collision_bundle();
124        add_collision_shape(&mut bundle, sphere_collision("s", [0.0; 3], 1.0));
125        assert_eq!(collision_shape_count(&bundle), 1);
126    }
127
128    #[test]
129    fn test_sphere_volume() {
130        let s = sphere_collision("s", [0.0; 3], 1.0);
131        let vol = shape_volume(&s);
132        assert!((vol - 4.0 * PI / 3.0).abs() < 1e-4);
133    }
134
135    #[test]
136    fn test_box_volume() {
137        let b = box_collision("b", [0.0; 3], [1.0, 2.0, 3.0]);
138        let vol = shape_volume(&b);
139        assert!((vol - 48.0).abs() < 1e-4);
140    }
141
142    #[test]
143    fn test_validate_bundle_valid() {
144        let mut bundle = new_collision_bundle();
145        add_collision_shape(&mut bundle, sphere_collision("s", [0.0; 3], 1.0));
146        assert!(validate_collision_bundle(&bundle));
147    }
148
149    #[test]
150    fn test_validate_bundle_empty_name() {
151        let mut bundle = new_collision_bundle();
152        add_collision_shape(&mut bundle, sphere_collision("", [0.0; 3], 1.0));
153        assert!(!validate_collision_bundle(&bundle));
154    }
155
156    #[test]
157    fn test_collision_bundle_to_json() {
158        let bundle = new_collision_bundle();
159        let j = collision_bundle_to_json(&bundle);
160        assert!(j.contains("\"shape_count\":0"));
161    }
162
163    #[test]
164    fn test_empty_bundle() {
165        let bundle = new_collision_bundle();
166        assert_eq!(collision_shape_count(&bundle), 0);
167    }
168
169    #[test]
170    fn test_pi_usage() {
171        // Verify we use std::f32::consts::PI
172        let s = sphere_collision("s", [0.0; 3], 1.0);
173        let vol = shape_volume(&s);
174        assert!(vol > 4.0);
175    }
176}