1#[allow(dead_code)]
7#[derive(Clone, PartialEq, Debug)]
8pub enum NormalMapSpace {
9 TangentSpace,
10 ObjectSpace,
11 WorldSpace,
12}
13
14#[allow(dead_code)]
15pub struct NormalMapConfig {
16 pub width: u32,
17 pub height: u32,
18 pub space: NormalMapSpace,
19 pub flip_green: bool,
20 pub samples: u32,
21}
22
23#[allow(dead_code)]
24pub struct NormalMapBuffer {
25 pub pixels: Vec<[u8; 3]>,
26 pub width: u32,
27 pub height: u32,
28}
29
30#[allow(dead_code)]
31pub fn default_normal_map_config(width: u32, height: u32) -> NormalMapConfig {
32 NormalMapConfig {
33 width,
34 height,
35 space: NormalMapSpace::TangentSpace,
36 flip_green: false,
37 samples: 1,
38 }
39}
40
41#[allow(dead_code)]
42pub fn new_normal_map_buffer(width: u32, height: u32) -> NormalMapBuffer {
43 let count = (width * height) as usize;
44 NormalMapBuffer {
45 pixels: vec![[128, 128, 255]; count],
46 width,
47 height,
48 }
49}
50
51#[allow(dead_code)]
52pub fn normal_to_rgb(n: [f32; 3]) -> [u8; 3] {
53 let clamp = |v: f32| v.clamp(-1.0, 1.0);
54 let to_u8 = |v: f32| ((clamp(v) * 0.5 + 0.5) * 255.0).round() as u8;
55 [to_u8(n[0]), to_u8(n[1]), to_u8(n[2])]
56}
57
58#[allow(dead_code)]
59pub fn rgb_to_normal(rgb: [u8; 3]) -> [f32; 3] {
60 let to_f = |v: u8| (v as f32 / 255.0) * 2.0 - 1.0;
61 let nx = to_f(rgb[0]);
62 let ny = to_f(rgb[1]);
63 let nz = to_f(rgb[2]);
64 let len = (nx * nx + ny * ny + nz * nz).sqrt().max(1e-8);
65 [nx / len, ny / len, nz / len]
66}
67
68#[allow(dead_code)]
69pub fn flat_normal_map(buffer: &mut NormalMapBuffer, normal: [f32; 3]) {
70 let rgb = normal_to_rgb(normal);
71 for pixel in buffer.pixels.iter_mut() {
72 *pixel = rgb;
73 }
74}
75
76#[allow(dead_code)]
77pub fn encode_normal_map_ppm(buffer: &NormalMapBuffer) -> Vec<u8> {
78 let header = format!("P6\n{} {}\n255\n", buffer.width, buffer.height);
79 let mut out = header.into_bytes();
80 for pixel in &buffer.pixels {
81 out.push(pixel[0]);
82 out.push(pixel[1]);
83 out.push(pixel[2]);
84 }
85 out
86}
87
88#[allow(dead_code)]
89pub fn compute_object_space_normals(positions: &[[f32; 3]], indices: &[u32]) -> Vec<[f32; 3]> {
90 let mut normals = vec![[0.0f32; 3]; positions.len()];
91 let tri_count = indices.len() / 3;
92 for tri in 0..tri_count {
93 let i0 = indices[tri * 3] as usize;
94 let i1 = indices[tri * 3 + 1] as usize;
95 let i2 = indices[tri * 3 + 2] as usize;
96 if i0 >= positions.len() || i1 >= positions.len() || i2 >= positions.len() {
97 continue;
98 }
99 let p0 = positions[i0];
100 let p1 = positions[i1];
101 let p2 = positions[i2];
102 let e1 = [p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]];
103 let e2 = [p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]];
104 let cross = [
105 e1[1] * e2[2] - e1[2] * e2[1],
106 e1[2] * e2[0] - e1[0] * e2[2],
107 e1[0] * e2[1] - e1[1] * e2[0],
108 ];
109 for idx in [i0, i1, i2] {
110 normals[idx][0] += cross[0];
111 normals[idx][1] += cross[1];
112 normals[idx][2] += cross[2];
113 }
114 }
115 for n in normals.iter_mut() {
116 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt().max(1e-8);
117 n[0] /= len;
118 n[1] /= len;
119 n[2] /= len;
120 }
121 normals
122}
123
124#[allow(dead_code)]
125pub fn normal_map_from_vertex_normals(
126 normals: &[[f32; 3]],
127 uvs: &[[f32; 2]],
128 width: u32,
129 height: u32,
130) -> NormalMapBuffer {
131 let mut buffer = new_normal_map_buffer(width, height);
132 let count = normals.len().min(uvs.len());
133 for i in 0..count {
134 let u = uvs[i][0].clamp(0.0, 1.0);
135 let v = uvs[i][1].clamp(0.0, 1.0);
136 let px = (u * (width as f32 - 1.0)).round() as u32;
137 let py = (v * (height as f32 - 1.0)).round() as u32;
138 set_normal_pixel(&mut buffer, px, py, normals[i]);
139 }
140 buffer
141}
142
143#[allow(dead_code)]
144pub fn blend_normal_maps(a: &NormalMapBuffer, b: &NormalMapBuffer, t: f32) -> NormalMapBuffer {
145 let width = a.width;
146 let height = a.height;
147 let count = (width * height) as usize;
148 let mut pixels = Vec::with_capacity(count);
149 let t = t.clamp(0.0, 1.0);
150 for i in 0..count {
151 let na = rgb_to_normal(a.pixels[i]);
152 let nb = if i < b.pixels.len() {
153 rgb_to_normal(b.pixels[i])
154 } else {
155 [0.0, 0.0, 1.0]
156 };
157 let blended = [
158 na[0] * (1.0 - t) + nb[0] * t,
159 na[1] * (1.0 - t) + nb[1] * t,
160 na[2] * (1.0 - t) + nb[2] * t,
161 ];
162 let len = (blended[0] * blended[0] + blended[1] * blended[1] + blended[2] * blended[2])
163 .sqrt()
164 .max(1e-8);
165 let norm = [blended[0] / len, blended[1] / len, blended[2] / len];
166 pixels.push(normal_to_rgb(norm));
167 }
168 NormalMapBuffer {
169 pixels,
170 width,
171 height,
172 }
173}
174
175#[allow(dead_code)]
176pub fn normal_map_pixel_count(buffer: &NormalMapBuffer) -> usize {
177 buffer.pixels.len()
178}
179
180#[allow(dead_code)]
181pub fn normal_map_size_bytes(buffer: &NormalMapBuffer) -> usize {
182 buffer.pixels.len() * 3
183}
184
185#[allow(dead_code)]
186pub fn set_normal_pixel(buffer: &mut NormalMapBuffer, x: u32, y: u32, normal: [f32; 3]) {
187 if x < buffer.width && y < buffer.height {
188 let idx = (y * buffer.width + x) as usize;
189 buffer.pixels[idx] = normal_to_rgb(normal);
190 }
191}
192
193#[allow(dead_code)]
194pub fn get_normal_pixel(buffer: &NormalMapBuffer, x: u32, y: u32) -> [f32; 3] {
195 if x < buffer.width && y < buffer.height {
196 let idx = (y * buffer.width + x) as usize;
197 rgb_to_normal(buffer.pixels[idx])
198 } else {
199 [0.0, 0.0, 1.0]
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn test_default_config() {
209 let cfg = default_normal_map_config(512, 256);
210 assert_eq!(cfg.width, 512);
211 assert_eq!(cfg.height, 256);
212 assert_eq!(cfg.space, NormalMapSpace::TangentSpace);
213 assert!(!cfg.flip_green);
214 assert_eq!(cfg.samples, 1);
215 }
216
217 #[test]
218 fn test_new_buffer() {
219 let buf = new_normal_map_buffer(4, 4);
220 assert_eq!(buf.width, 4);
221 assert_eq!(buf.height, 4);
222 assert_eq!(buf.pixels.len(), 16);
223 }
224
225 #[test]
226 fn test_normal_to_rgb_round_trip() {
227 let n = [0.0f32, 0.0, 1.0];
228 let rgb = normal_to_rgb(n);
229 let back = rgb_to_normal(rgb);
230 assert!((back[2] - 1.0).abs() < 0.02);
231 }
232
233 #[test]
234 fn test_normal_to_rgb_values() {
235 let rgb = normal_to_rgb([0.0, 0.0, 1.0]);
236 assert_eq!(rgb[0], 128);
237 assert_eq!(rgb[1], 128);
238 assert_eq!(rgb[2], 255);
239 }
240
241 #[test]
242 fn test_rgb_to_normal_normalized() {
243 let n = rgb_to_normal([255, 128, 128]);
244 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
245 assert!((len - 1.0).abs() < 0.05);
246 }
247
248 #[test]
249 fn test_flat_normal_map() {
250 let mut buf = new_normal_map_buffer(4, 4);
251 flat_normal_map(&mut buf, [0.0, 0.0, 1.0]);
252 let rgb = normal_to_rgb([0.0, 0.0, 1.0]);
253 for pixel in &buf.pixels {
254 assert_eq!(pixel, &rgb);
255 }
256 }
257
258 #[test]
259 fn test_encode_ppm_starts_with_p6() {
260 let buf = new_normal_map_buffer(2, 2);
261 let ppm = encode_normal_map_ppm(&buf);
262 assert!(ppm.starts_with(b"P6"));
263 }
264
265 #[test]
266 fn test_encode_ppm_size() {
267 let buf = new_normal_map_buffer(3, 3);
268 let ppm = encode_normal_map_ppm(&buf);
269 let header = b"P6\n3 3\n255\n";
270 assert!(ppm.len() >= header.len() + 3 * 3 * 3);
271 }
272
273 #[test]
274 fn test_normal_map_pixel_count() {
275 let buf = new_normal_map_buffer(8, 8);
276 assert_eq!(normal_map_pixel_count(&buf), 64);
277 }
278
279 #[test]
280 fn test_normal_map_size_bytes() {
281 let buf = new_normal_map_buffer(8, 8);
282 assert_eq!(normal_map_size_bytes(&buf), 192);
283 }
284
285 #[test]
286 fn test_set_get_pixel() {
287 let mut buf = new_normal_map_buffer(4, 4);
288 let n = [1.0f32, 0.0, 0.0];
289 set_normal_pixel(&mut buf, 2, 1, n);
290 let got = get_normal_pixel(&buf, 2, 1);
291 assert!((got[0] - 1.0).abs() < 0.05);
292 }
293
294 #[test]
295 fn test_blend_maps() {
296 let a = new_normal_map_buffer(2, 2);
297 let mut b = new_normal_map_buffer(2, 2);
298 flat_normal_map(&mut b, [1.0, 0.0, 0.0]);
299 let blended = blend_normal_maps(&a, &b, 0.5);
300 assert_eq!(blended.pixels.len(), 4);
301 }
302
303 #[test]
304 fn test_object_space_normals_triangle() {
305 let positions = [[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
306 let indices = [0u32, 1, 2];
307 let normals = compute_object_space_normals(&positions, &indices);
308 assert_eq!(normals.len(), 3);
309 for n in &normals {
310 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
311 assert!((len - 1.0).abs() < 0.01);
312 }
313 }
314
315 #[test]
316 fn test_normal_map_from_vertex_normals() {
317 let normals = vec![[0.0f32, 0.0, 1.0]; 4];
318 let uvs = vec![[0.0f32, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
319 let buf = normal_map_from_vertex_normals(&normals, &uvs, 8, 8);
320 assert_eq!(buf.width, 8);
321 assert_eq!(buf.height, 8);
322 }
323
324 #[test]
325 fn test_get_pixel_out_of_bounds() {
326 let buf = new_normal_map_buffer(4, 4);
327 let n = get_normal_pixel(&buf, 10, 10);
328 assert_eq!(n, [0.0, 0.0, 1.0]);
329 }
330}