Skip to main content

oxihuman_export/
geometry_instancing_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Geometry instancing export for repeated mesh placement.
6
7/// A geometry instance with transform.
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct GeoInstance {
11    pub mesh_id: u32,
12    pub position: [f32; 3],
13    pub rotation: [f32; 4],
14    pub scale: [f32; 3],
15}
16
17/// Instancing export data.
18#[allow(dead_code)]
19#[derive(Debug, Clone)]
20pub struct GeoInstancingExport {
21    pub instances: Vec<GeoInstance>,
22}
23
24/// Create new export.
25#[allow(dead_code)]
26pub fn new_geo_instancing_export() -> GeoInstancingExport {
27    GeoInstancingExport { instances: vec![] }
28}
29
30/// Add instance with identity transform.
31#[allow(dead_code)]
32pub fn add_instance(e: &mut GeoInstancingExport, mesh_id: u32, pos: [f32; 3]) {
33    e.instances.push(GeoInstance {
34        mesh_id,
35        position: pos,
36        rotation: [0.0, 0.0, 0.0, 1.0],
37        scale: [1.0, 1.0, 1.0],
38    });
39}
40
41/// Add instance with full transform.
42#[allow(dead_code)]
43pub fn add_instance_full(
44    e: &mut GeoInstancingExport,
45    mesh_id: u32,
46    pos: [f32; 3],
47    rot: [f32; 4],
48    scale: [f32; 3],
49) {
50    e.instances.push(GeoInstance {
51        mesh_id,
52        position: pos,
53        rotation: rot,
54        scale,
55    });
56}
57
58/// Instance count.
59#[allow(dead_code)]
60pub fn gi_count(e: &GeoInstancingExport) -> usize {
61    e.instances.len()
62}
63
64/// Count instances of a specific mesh.
65#[allow(dead_code)]
66pub fn instances_of_mesh(e: &GeoInstancingExport, mesh_id: u32) -> usize {
67    e.instances.iter().filter(|i| i.mesh_id == mesh_id).count()
68}
69
70/// Unique mesh count.
71#[allow(dead_code)]
72pub fn unique_mesh_count(e: &GeoInstancingExport) -> usize {
73    let mut ids: Vec<u32> = e.instances.iter().map(|i| i.mesh_id).collect();
74    ids.sort_unstable();
75    ids.dedup();
76    ids.len()
77}
78
79/// Validate (quaternions roughly unit length).
80#[allow(dead_code)]
81pub fn gi_validate(e: &GeoInstancingExport) -> bool {
82    e.instances.iter().all(|i| {
83        let q = i.rotation;
84        let len_sq = q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3];
85        (len_sq - 1.0).abs() < 0.1
86    })
87}
88
89/// Export to JSON.
90#[allow(dead_code)]
91pub fn geo_instancing_to_json(e: &GeoInstancingExport) -> String {
92    format!(
93        "{{\"instances\":{},\"unique_meshes\":{}}}",
94        gi_count(e),
95        unique_mesh_count(e)
96    )
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    #[test]
103    fn test_new() {
104        let e = new_geo_instancing_export();
105        assert_eq!(gi_count(&e), 0);
106    }
107    #[test]
108    fn test_add() {
109        let mut e = new_geo_instancing_export();
110        add_instance(&mut e, 0, [1.0, 0.0, 0.0]);
111        assert_eq!(gi_count(&e), 1);
112    }
113    #[test]
114    fn test_add_full() {
115        let mut e = new_geo_instancing_export();
116        add_instance_full(&mut e, 0, [0.0; 3], [0.0, 0.0, 0.0, 1.0], [2.0; 3]);
117        assert_eq!(gi_count(&e), 1);
118    }
119    #[test]
120    fn test_instances_of() {
121        let mut e = new_geo_instancing_export();
122        add_instance(&mut e, 0, [0.0; 3]);
123        add_instance(&mut e, 0, [1.0; 3]);
124        add_instance(&mut e, 1, [0.0; 3]);
125        assert_eq!(instances_of_mesh(&e, 0), 2);
126    }
127    #[test]
128    fn test_unique() {
129        let mut e = new_geo_instancing_export();
130        add_instance(&mut e, 0, [0.0; 3]);
131        add_instance(&mut e, 1, [0.0; 3]);
132        assert_eq!(unique_mesh_count(&e), 2);
133    }
134    #[test]
135    fn test_validate() {
136        let mut e = new_geo_instancing_export();
137        add_instance(&mut e, 0, [0.0; 3]);
138        assert!(gi_validate(&e));
139    }
140    #[test]
141    fn test_to_json() {
142        let e = new_geo_instancing_export();
143        assert!(geo_instancing_to_json(&e).contains("\"instances\":0"));
144    }
145    #[test]
146    fn test_empty_unique() {
147        let e = new_geo_instancing_export();
148        assert_eq!(unique_mesh_count(&e), 0);
149    }
150    #[test]
151    fn test_position() {
152        let mut e = new_geo_instancing_export();
153        add_instance(&mut e, 0, [5.0, 3.0, 1.0]);
154        assert!((e.instances[0].position[0] - 5.0).abs() < 1e-6);
155    }
156    #[test]
157    fn test_default_scale() {
158        let mut e = new_geo_instancing_export();
159        add_instance(&mut e, 0, [0.0; 3]);
160        assert!((e.instances[0].scale[0] - 1.0).abs() < 1e-6);
161    }
162}