Skip to main content

oxihuman_export/
flow_field_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5/// 3D velocity/flow field.
6pub struct FlowField {
7    pub width: usize,
8    pub height: usize,
9    pub depth: usize,
10    pub velocities: Vec<[f32; 3]>,
11}
12
13pub fn new_flow_field(w: usize, h: usize, d: usize) -> FlowField {
14    FlowField {
15        width: w,
16        height: h,
17        depth: d,
18        velocities: vec![[0.0; 3]; w * h * d],
19    }
20}
21
22fn flow_index(f: &FlowField, x: usize, y: usize, z: usize) -> usize {
23    z * f.height * f.width + y * f.width + x
24}
25
26pub fn flow_set(f: &mut FlowField, x: usize, y: usize, z: usize, v: [f32; 3]) {
27    if x < f.width && y < f.height && z < f.depth {
28        let idx = flow_index(f, x, y, z);
29        f.velocities[idx] = v;
30    }
31}
32
33pub fn flow_get(f: &FlowField, x: usize, y: usize, z: usize) -> [f32; 3] {
34    if x < f.width && y < f.height && z < f.depth {
35        f.velocities[flow_index(f, x, y, z)]
36    } else {
37        [0.0; 3]
38    }
39}
40
41pub fn flow_max_speed(f: &FlowField) -> f32 {
42    f.velocities
43        .iter()
44        .map(|&v| (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt())
45        .fold(0.0f32, f32::max)
46}
47
48pub fn flow_divergence_at(f: &FlowField, x: usize, y: usize, z: usize) -> f32 {
49    if x == 0 || x + 1 >= f.width || y == 0 || y + 1 >= f.height || z == 0 || z + 1 >= f.depth {
50        return 0.0;
51    }
52    let dvx = flow_get(f, x + 1, y, z)[0] - flow_get(f, x - 1, y, z)[0];
53    let dvy = flow_get(f, x, y + 1, z)[1] - flow_get(f, x, y - 1, z)[1];
54    let dvz = flow_get(f, x, y, z + 1)[2] - flow_get(f, x, y, z - 1)[2];
55    (dvx + dvy + dvz) * 0.5
56}
57
58pub fn flow_to_bytes(f: &FlowField) -> Vec<u8> {
59    let mut out = Vec::with_capacity(f.velocities.len() * 12);
60    for &v in &f.velocities {
61        out.extend_from_slice(&v[0].to_le_bytes());
62        out.extend_from_slice(&v[1].to_le_bytes());
63        out.extend_from_slice(&v[2].to_le_bytes());
64    }
65    out
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_new_flow_field_size() {
74        let f = new_flow_field(2, 3, 4);
75        assert_eq!(f.velocities.len(), 24);
76    }
77
78    #[test]
79    fn test_flow_set_get() {
80        let mut f = new_flow_field(4, 4, 4);
81        flow_set(&mut f, 1, 2, 3, [1.0, 2.0, 3.0]);
82        let v = flow_get(&f, 1, 2, 3);
83        assert!((v[0] - 1.0).abs() < 1e-5);
84    }
85
86    #[test]
87    fn test_flow_get_oob() {
88        let f = new_flow_field(4, 4, 4);
89        let v = flow_get(&f, 10, 10, 10);
90        assert_eq!(v, [0.0; 3]);
91    }
92
93    #[test]
94    fn test_flow_max_speed() {
95        let mut f = new_flow_field(3, 1, 1);
96        flow_set(&mut f, 0, 0, 0, [3.0, 4.0, 0.0]);
97        flow_set(&mut f, 1, 0, 0, [1.0, 0.0, 0.0]);
98        assert!((flow_max_speed(&f) - 5.0).abs() < 1e-5);
99    }
100
101    #[test]
102    fn test_flow_divergence_at_interior() {
103        /* uniform flow has zero divergence */
104        let mut f = new_flow_field(5, 5, 5);
105        for z in 0..5 {
106            for y in 0..5 {
107                for x in 0..5 {
108                    flow_set(&mut f, x, y, z, [1.0, 0.0, 0.0]);
109                }
110            }
111        }
112        let div = flow_divergence_at(&f, 2, 2, 2);
113        assert!(div.abs() < 1e-4);
114    }
115
116    #[test]
117    fn test_flow_to_bytes_len() {
118        let f = new_flow_field(2, 2, 2);
119        assert_eq!(flow_to_bytes(&f).len(), 8 * 12);
120    }
121
122    #[test]
123    fn test_flow_divergence_at_boundary() {
124        let f = new_flow_field(4, 4, 4);
125        /* boundary should return 0 without panic */
126        assert!((flow_divergence_at(&f, 0, 0, 0) - 0.0).abs() < 1e-6);
127    }
128}