Skip to main content

oxihuman_export/
displacement_map_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan) / SPDX-License-Identifier: Apache-2.0 / #![allow(dead_code)]
2
3/// Displacement map export data.
4#[allow(dead_code)]
5#[derive(Debug, Clone)]
6pub struct DisplacementMapExport {
7    pub width: u32,
8    pub height: u32,
9    pub data: Vec<f32>,
10    pub scale: f32,
11}
12
13#[allow(dead_code)]
14impl DisplacementMapExport {
15    /// Create a flat displacement map.
16    pub fn flat(width: u32, height: u32) -> Self {
17        Self {
18            width,
19            height,
20            data: vec![0.0; (width * height) as usize],
21            scale: 1.0,
22        }
23    }
24
25    /// Create from height data.
26    pub fn from_data(width: u32, height: u32, data: Vec<f32>, scale: f32) -> Self {
27        Self {
28            width,
29            height,
30            data,
31            scale,
32        }
33    }
34
35    /// Pixel count.
36    pub fn pixel_count(&self) -> usize {
37        (self.width * self.height) as usize
38    }
39
40    /// Get displacement at (x, y).
41    pub fn get(&self, x: u32, y: u32) -> f32 {
42        self.data[(y * self.width + x) as usize] * self.scale
43    }
44
45    /// Set displacement at (x, y).
46    pub fn set(&mut self, x: u32, y: u32, value: f32) {
47        self.data[(y * self.width + x) as usize] = value;
48    }
49
50    /// Min/max displacement.
51    pub fn range(&self) -> (f32, f32) {
52        let min = self.data.iter().cloned().fold(f32::MAX, f32::min);
53        let max = self.data.iter().cloned().fold(f32::MIN, f32::max);
54        (min * self.scale, max * self.scale)
55    }
56
57    /// Export to 16-bit integer bytes.
58    pub fn to_u16_bytes(&self) -> Vec<u8> {
59        let (min, max) = self.range();
60        let range = (max - min).max(1e-10);
61        let mut bytes = Vec::new();
62        bytes.extend_from_slice(&self.width.to_le_bytes());
63        bytes.extend_from_slice(&self.height.to_le_bytes());
64        for &v in &self.data {
65            let norm = ((v * self.scale - min) / range * 65535.0) as u16;
66            bytes.extend_from_slice(&norm.to_le_bytes());
67        }
68        bytes
69    }
70
71    /// Byte size of u16 export.
72    pub fn u16_byte_size(&self) -> usize {
73        8 + self.pixel_count() * 2
74    }
75}
76
77/// Export to JSON.
78#[allow(dead_code)]
79pub fn displacement_map_to_json(map: &DisplacementMapExport) -> String {
80    let (min, max) = map.range();
81    format!(
82        "{{\"width\":{},\"height\":{},\"scale\":{},\"min\":{},\"max\":{}}}",
83        map.width, map.height, map.scale, min, max
84    )
85}
86
87/// Validate map.
88#[allow(dead_code)]
89pub fn validate_displacement_map(map: &DisplacementMapExport) -> bool {
90    map.data.len() == map.pixel_count() && map.data.iter().all(|v| v.is_finite())
91}
92
93// ── New required API ──────────────────────────────────────────────────────────
94
95pub struct DisplacementMap {
96    pub width: u32,
97    pub height: u32,
98    pub data: Vec<f32>,
99    pub midlevel: f32,
100    pub strength: f32,
101}
102
103pub fn new_displacement_map(w: u32, h: u32) -> DisplacementMap {
104    DisplacementMap {
105        width: w,
106        height: h,
107        data: vec![0.0; (w * h) as usize],
108        midlevel: 0.5,
109        strength: 1.0,
110    }
111}
112
113pub fn disp_set(m: &mut DisplacementMap, x: u32, y: u32, v: f32) {
114    if x < m.width && y < m.height {
115        m.data[(y * m.width + x) as usize] = v;
116    }
117}
118
119pub fn disp_get(m: &DisplacementMap, x: u32, y: u32) -> f32 {
120    if x < m.width && y < m.height {
121        m.data[(y * m.width + x) as usize]
122    } else {
123        0.0
124    }
125}
126
127pub fn disp_to_u16(m: &DisplacementMap) -> Vec<u16> {
128    m.data
129        .iter()
130        .map(|&v| {
131            let norm = ((v - m.midlevel) * m.strength * 0.5 + 0.5).clamp(0.0, 1.0);
132            (norm * 65535.0) as u16
133        })
134        .collect()
135}
136
137pub fn disp_max_height(m: &DisplacementMap) -> f32 {
138    m.data.iter().cloned().fold(f32::NEG_INFINITY, f32::max)
139}
140
141pub fn disp_to_bytes(m: &DisplacementMap) -> Vec<u8> {
142    let mut b = Vec::new();
143    b.extend_from_slice(&m.width.to_le_bytes());
144    b.extend_from_slice(&m.height.to_le_bytes());
145    for &v in &m.data {
146        b.extend_from_slice(&v.to_le_bytes());
147    }
148    b
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn test_flat() {
157        let m = DisplacementMapExport::flat(4, 4);
158        assert_eq!(m.pixel_count(), 16);
159    }
160
161    #[test]
162    fn test_get_set() {
163        let mut m = DisplacementMapExport::flat(2, 2);
164        m.set(0, 0, 0.5);
165        assert!((m.get(0, 0) - 0.5).abs() < 1e-5);
166    }
167
168    #[test]
169    fn test_range() {
170        let m = DisplacementMapExport::from_data(2, 1, vec![0.0, 1.0], 2.0);
171        let (min, max) = m.range();
172        assert!((min).abs() < 1e-5);
173        assert!((max - 2.0).abs() < 1e-5);
174    }
175
176    #[test]
177    fn test_to_u16_bytes() {
178        let m = DisplacementMapExport::flat(2, 2);
179        let bytes = m.to_u16_bytes();
180        assert_eq!(bytes.len(), m.u16_byte_size());
181    }
182
183    #[test]
184    fn test_validate() {
185        let m = DisplacementMapExport::flat(2, 2);
186        assert!(validate_displacement_map(&m));
187    }
188
189    #[test]
190    fn test_to_json() {
191        let m = DisplacementMapExport::flat(2, 2);
192        let json = displacement_map_to_json(&m);
193        assert!(json.contains("width"));
194    }
195
196    #[test]
197    fn test_custom_scale() {
198        let m = DisplacementMapExport::from_data(1, 1, vec![1.0], 3.0);
199        assert!((m.get(0, 0) - 3.0).abs() < 1e-5);
200    }
201
202    #[test]
203    fn test_invalid() {
204        let m = DisplacementMapExport {
205            width: 2,
206            height: 2,
207            data: vec![0.0],
208            scale: 1.0,
209        };
210        assert!(!validate_displacement_map(&m));
211    }
212
213    #[test]
214    fn test_pixel_count() {
215        let m = DisplacementMapExport::flat(3, 5);
216        assert_eq!(m.pixel_count(), 15);
217    }
218
219    #[test]
220    fn test_u16_byte_size() {
221        let m = DisplacementMapExport::flat(4, 4);
222        assert_eq!(m.u16_byte_size(), 8 + 32);
223    }
224}