1mod chunk_gen;
16mod extrude;
17pub mod glass_quad;
18mod heightfield;
19mod primitives;
20mod room;
21mod skybox;
22mod terrain;
23mod voxel;
24pub mod water_grid;
25
26pub use extrude::build_extrude;
27pub use heightfield::{HeightfieldField, build_heightfield_from_pixels};
28pub use primitives::{build_box, build_cylinder, build_plane, build_sphere};
29pub use room::build_room_geometry;
30pub use skybox::build_skybox;
31pub use terrain::build_terrain;
32
33use crate::math::vec3::{vec3_add, vec3_normalise};
34use alloc::format;
35use alloc::string::String;
36use alloc::vec;
37use alloc::vec::Vec;
38
39pub use chunk_gen::{ChunkBlockType, ChunkGenerator};
40pub use voxel::{PaletteSlot, build_voxel_mesh};
43
44pub type Vert = ([f32; 3], [f32; 3], [f32; 3], [f32; 2]);
49
50fn payload_joint_to_def(
52 j: crate::gfx::mesh_payload::PayloadJoint,
53) -> crate::components::SkeletonJoint {
54 crate::components::SkeletonJoint {
55 name: j.name,
56 parent: j.parent,
57 translation: j.translation,
58 rotation_deg: j.rotation_deg,
59 scale: j.scale,
60 }
61}
62
63pub fn payload_joints_to_defs(
67 joints: Vec<crate::gfx::mesh_payload::PayloadJoint>,
68) -> Vec<crate::components::SkeletonJoint> {
69 joints.into_iter().map(payload_joint_to_def).collect()
70}
71
72pub fn build_chunk_mesh(
79 dim: [u32; 3],
80 block_size: f32,
81 blocks: &[u32],
82 palette: &[ChunkBlockType],
83) -> Result<(Vec<crate::gfx::mesh_payload::Vertex>, Vec<u16>), String> {
84 let slots: Vec<Option<PaletteSlot>> = palette
85 .iter()
86 .map(|b| {
87 if b.solid {
88 Some(PaletteSlot {
89 uv_top: b.uv_top,
90 uv_bottom: b.uv_bottom,
91 uv_side: b.uv_side,
92 })
93 } else {
94 None
95 }
96 })
97 .collect();
98 let (verts, indices) = build_voxel_mesh(dim, block_size, blocks, &slots)?;
99 let tangents = compute_tangents(&verts, &indices);
100 let vertices = verts
101 .into_iter()
102 .zip(tangents)
103 .map(
104 |((pos, normal, color, uv), tangent)| crate::gfx::mesh_payload::Vertex {
105 pos,
106 normal,
107 tangent,
108 color,
109 uv,
110 },
111 )
112 .collect();
113 Ok((vertices, indices))
114}
115
116pub fn build_chunk_impostor_mesh(
135 dim: [u32; 3],
136 block_size: f32,
137 step: u32,
138 heights: &[i32],
139 top_uv: [f32; 4],
140 side_uv: [f32; 4],
141) -> Result<(Vec<crate::gfx::mesh_payload::Vertex>, Vec<u16>), String> {
142 let step = step.max(1);
143 let [dx, _dy, dz] = dim;
144 let nx = dx.div_ceil(step);
145 let nz = dz.div_ceil(step);
146 let cols = (nx + 1) as usize;
147 let expected = ((nx + 1) * (nz + 1)) as usize;
148 if heights.len() != expected {
149 return Err(format!(
150 "impostor mesh: expected {} height samples for a {}x{} coarse grid, got {}",
151 expected,
152 nx + 1,
153 nz + 1,
154 heights.len()
155 ));
156 }
157 let bs = block_size;
158 let cx = |gx: u32| ((gx * step).min(dx) as f32) * bs;
162 let cz = |gz: u32| ((gz * step).min(dz) as f32) * bs;
163 let surf_y = |gx: u32, gz: u32| ((heights[gz as usize * cols + gx as usize] + 1) as f32) * bs;
166
167 type RawVerts = Vec<([f32; 3], [f32; 3], [f32; 3], [f32; 2])>;
168 let mut verts: RawVerts = Vec::new();
169 let mut indices: Vec<u16> = Vec::new();
170 let color = [0.75f32, 0.74, 0.72];
171
172 let mut emit_quad = |corners: [[f32; 3]; 4], normal: [f32; 3], uv_rect: [f32; 4]| {
174 if verts.len() + 4 > u16::MAX as usize {
175 return;
176 }
177 let base = verts.len() as u16;
178 let [u0, v0, u1, v1] = uv_rect;
179 let uvs = [[u0, v0], [u1, v0], [u1, v1], [u0, v1]];
180 for (i, p) in corners.iter().enumerate() {
181 verts.push((*p, normal, color, uvs[i]));
182 }
183 indices.extend_from_slice(&[base, base + 1, base + 2, base + 2, base + 3, base]);
184 };
185
186 let n_up = [0.0, 1.0, 0.0];
190 for gz in 0..nz {
191 for gx in 0..nx {
192 emit_quad(
193 [
194 [cx(gx), surf_y(gx, gz + 1), cz(gz + 1)],
195 [cx(gx + 1), surf_y(gx + 1, gz + 1), cz(gz + 1)],
196 [cx(gx + 1), surf_y(gx + 1, gz), cz(gz)],
197 [cx(gx), surf_y(gx, gz), cz(gz)],
198 ],
199 n_up,
200 top_uv,
201 );
202 }
203 }
204
205 let x_max = (dx as f32) * bs;
209 let z_max = (dz as f32) * bs;
210 for gx in 0..nx {
211 emit_quad(
213 [
214 [cx(gx + 1), 0.0, 0.0],
215 [cx(gx), 0.0, 0.0],
216 [cx(gx), surf_y(gx, 0), 0.0],
217 [cx(gx + 1), surf_y(gx + 1, 0), 0.0],
218 ],
219 [0.0, 0.0, -1.0],
220 side_uv,
221 );
222 emit_quad(
224 [
225 [cx(gx), 0.0, z_max],
226 [cx(gx + 1), 0.0, z_max],
227 [cx(gx + 1), surf_y(gx + 1, nz), z_max],
228 [cx(gx), surf_y(gx, nz), z_max],
229 ],
230 [0.0, 0.0, 1.0],
231 side_uv,
232 );
233 }
234 for gz in 0..nz {
235 emit_quad(
237 [
238 [0.0, 0.0, cz(gz)],
239 [0.0, 0.0, cz(gz + 1)],
240 [0.0, surf_y(0, gz + 1), cz(gz + 1)],
241 [0.0, surf_y(0, gz), cz(gz)],
242 ],
243 [-1.0, 0.0, 0.0],
244 side_uv,
245 );
246 emit_quad(
248 [
249 [x_max, 0.0, cz(gz + 1)],
250 [x_max, 0.0, cz(gz)],
251 [x_max, surf_y(nx, gz), cz(gz)],
252 [x_max, surf_y(nx, gz + 1), cz(gz + 1)],
253 ],
254 [1.0, 0.0, 0.0],
255 side_uv,
256 );
257 }
258
259 let tangents = compute_tangents(&verts, &indices);
260 let vertices = verts
261 .into_iter()
262 .zip(tangents)
263 .map(
264 |((pos, normal, color, uv), tangent)| crate::gfx::mesh_payload::Vertex {
265 pos,
266 normal,
267 tangent,
268 color,
269 uv,
270 },
271 )
272 .collect();
273 Ok((vertices, indices))
274}
275
276pub fn compute_tangents(vertices: &[Vert], indices: &[u16]) -> Vec<[f32; 3]> {
285 let n = vertices.len();
286 let mut accum: Vec<[f32; 3]> = vec![[0.0; 3]; n];
287
288 let tris = indices.len() / 3;
289 for t in 0..tris {
290 let ia = indices[t * 3] as usize;
291 let ib = indices[t * 3 + 1] as usize;
292 let ic = indices[t * 3 + 2] as usize;
293 if ia >= n || ib >= n || ic >= n {
294 continue;
295 }
296 let (pa, _, _, uva) = vertices[ia];
297 let (pb, _, _, uvb) = vertices[ib];
298 let (pc, _, _, uvc) = vertices[ic];
299
300 let e1 = [pb[0] - pa[0], pb[1] - pa[1], pb[2] - pa[2]];
301 let e2 = [pc[0] - pa[0], pc[1] - pa[1], pc[2] - pa[2]];
302 let du1 = uvb[0] - uva[0];
303 let dv1 = uvb[1] - uva[1];
304 let du2 = uvc[0] - uva[0];
305 let dv2 = uvc[1] - uva[1];
306
307 let denom = du1 * dv2 - du2 * dv1;
308 let tangent = if denom.abs() < 1e-8 {
309 arbitrary_tangent(vertices[ia].1)
310 } else {
311 let r = 1.0 / denom;
312 [
313 (e1[0] * dv2 - e2[0] * dv1) * r,
314 (e1[1] * dv2 - e2[1] * dv1) * r,
315 (e1[2] * dv2 - e2[2] * dv1) * r,
316 ]
317 };
318
319 vec3_add(&mut accum[ia], tangent);
320 vec3_add(&mut accum[ib], tangent);
321 vec3_add(&mut accum[ic], tangent);
322 }
323
324 vertices
325 .iter()
326 .zip(accum)
327 .map(|((_, normal, _, _), raw)| {
328 let dot = raw[0] * normal[0] + raw[1] * normal[1] + raw[2] * normal[2];
329 let t = [
330 raw[0] - dot * normal[0],
331 raw[1] - dot * normal[1],
332 raw[2] - dot * normal[2],
333 ];
334 vec3_normalise(t)
335 })
336 .collect()
337}
338
339fn arbitrary_tangent(normal: [f32; 3]) -> [f32; 3] {
341 let up = if normal[0].abs() <= normal[1].abs() && normal[0].abs() <= normal[2].abs() {
342 [1.0f32, 0.0, 0.0]
343 } else if normal[1].abs() <= normal[2].abs() {
344 [0.0, 1.0, 0.0]
345 } else {
346 [0.0, 0.0, 1.0]
347 };
348 let t = [
349 up[1] * normal[2] - up[2] * normal[1],
350 up[2] * normal[0] - up[0] * normal[2],
351 up[0] * normal[1] - up[1] * normal[0],
352 ];
353 vec3_normalise(t)
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use alloc::string::ToString;
360
361 fn flat_heights(dim: [u32; 3], step: u32, h: i32) -> Vec<i32> {
363 let nx = dim[0].div_ceil(step);
364 let nz = dim[2].div_ceil(step);
365 vec![h; ((nx + 1) * (nz + 1)) as usize]
366 }
367
368 #[test]
369 fn impostor_mesh_counts_match_cells_plus_skirt() {
370 let dim = [8, 8, 8];
371 let step = 4;
372 let heights = flat_heights(dim, step, 3);
373 let uv = [0.0, 0.0, 1.0, 1.0];
374 let (v, i) = build_chunk_impostor_mesh(dim, 1.0, step, &heights, uv, uv).expect("impostor");
375 assert_eq!(v.len(), 12 * 4);
377 assert_eq!(i.len(), 12 * 6);
378 }
379
380 #[test]
381 fn impostor_top_surface_sits_above_the_surface_block() {
382 let dim = [8, 4, 8];
383 let bs = 2.0;
384 let heights = flat_heights(dim, 4, 1);
385 let uv = [0.0, 0.0, 1.0, 1.0];
386 let (v, _) = build_chunk_impostor_mesh(dim, bs, 4, &heights, uv, uv).expect("impostor");
387 let want = 2.0 * bs;
389 assert!(v.iter().any(|vert| (vert.pos[1] - want).abs() < 1e-4));
390 }
391
392 #[test]
393 fn impostor_spans_the_full_chunk_footprint() {
394 let dim = [8, 4, 8];
395 let bs = 2.0;
396 let heights = flat_heights(dim, 4, 1);
397 let uv = [0.0, 0.0, 1.0, 1.0];
398 let (v, _) = build_chunk_impostor_mesh(dim, bs, 4, &heights, uv, uv).expect("impostor");
399 let max_x = v.iter().map(|vert| vert.pos[0]).fold(0.0f32, f32::max);
400 let max_z = v.iter().map(|vert| vert.pos[2]).fold(0.0f32, f32::max);
401 assert!((max_x - (dim[0] as f32 * bs)).abs() < 1e-4);
402 assert!((max_z - (dim[2] as f32 * bs)).abs() < 1e-4);
403 }
404
405 #[test]
406 fn impostor_rejects_a_mismatched_height_grid() {
407 let dim = [8, 8, 8];
408 let uv = [0.0, 0.0, 1.0, 1.0];
409 let bad = vec![0; 3];
410 assert!(build_chunk_impostor_mesh(dim, 1.0, 4, &bad, uv, uv).is_err());
411 }
412
413 #[test]
414 fn impostor_with_step_exceeding_chunk_collapses_to_one_cell() {
415 let dim = [8, 8, 8];
416 let uv = [0.0, 0.0, 1.0, 1.0];
417 let heights = flat_heights(dim, 32, 2);
418 let (v, i) = build_chunk_impostor_mesh(dim, 1.0, 32, &heights, uv, uv).expect("impostor");
419 assert_eq!(v.len(), 5 * 4);
421 assert_eq!(i.len(), 5 * 6);
422 }
423
424 #[test]
425 fn degenerate_uvs_still_produce_unit_tangents() {
426 let verts: Vec<Vert> = vec![
427 ([0.0, 0.0, 0.0], [0.0, 0.0, 1.0], [1.0; 3], [0.0, 0.0]),
428 ([1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [1.0; 3], [0.0, 0.0]),
429 ([0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [1.0; 3], [0.0, 0.0]),
430 ];
431 let tangents = compute_tangents(&verts, &[0, 1, 2]);
432 for (t, v) in tangents.iter().zip(&verts) {
433 let len = (t[0] * t[0] + t[1] * t[1] + t[2] * t[2]).sqrt();
434 assert!((len - 1.0).abs() < 1e-5);
435 let dot = t[0] * v.1[0] + t[1] * v.1[1] + t[2] * v.1[2];
436 assert!(dot.abs() < 1e-5);
437 }
438 }
439
440 #[test]
441 fn out_of_range_indices_are_skipped_by_the_tangent_pass() {
442 let verts: Vec<Vert> = vec![([0.0; 3], [0.0, 0.0, 1.0], [1.0; 3], [0.0, 0.0])];
443 let tangents = compute_tangents(&verts, &[0, 1, 2]);
445 assert_eq!(tangents, vec![[0.0, 1.0, 0.0]]);
446 }
447
448 #[test]
449 fn arbitrary_tangent_is_unit_and_perpendicular() {
450 for normal in [
451 [1.0f32, 0.0, 0.0],
452 [0.0, 1.0, 0.0],
453 [0.0, 0.0, 1.0],
454 [0.6, 0.5, 0.3],
455 ] {
456 let t = arbitrary_tangent(normal);
457 let len = (t[0] * t[0] + t[1] * t[1] + t[2] * t[2]).sqrt();
458 assert!((len - 1.0).abs() < 1e-5, "normal {normal:?}");
459 let dot = t[0] * normal[0] + t[1] * normal[1] + t[2] * normal[2];
460 assert!(dot.abs() < 1e-5, "normal {normal:?}");
461 }
462 }
463
464 #[test]
465 fn chunk_mesh_respects_solid_flags() {
466 let bt = |solid: bool| ChunkBlockType {
467 solid,
468 uv_top: [0.0, 0.0, 1.0, 1.0],
469 uv_bottom: [0.0, 0.0, 1.0, 1.0],
470 uv_side: [0.0, 0.0, 1.0, 1.0],
471 };
472 let (verts, indices) = build_chunk_mesh([1, 1, 1], 1.0, &[0], &[bt(true)]).unwrap();
473 assert_eq!(verts.len(), 24);
474 assert_eq!(indices.len(), 36);
475 let (verts, indices) = build_chunk_mesh([1, 1, 1], 1.0, &[0], &[bt(false)]).unwrap();
476 assert!(verts.is_empty() && indices.is_empty());
477 let (verts, _) = build_chunk_mesh([2, 1, 1], 1.0, &[0, 0], &[bt(true)]).unwrap();
479 assert_eq!(verts.len(), 10 * 4);
480 }
481
482 #[test]
483 fn payload_joints_convert_back_to_joint_defs() {
484 let pj = crate::gfx::mesh_payload::PayloadJoint {
485 name: "hip".to_string(),
486 parent: 2,
487 translation: [1.0, 2.0, 3.0],
488 rotation_deg: [4.0, 5.0, 6.0],
489 scale: [7.0, 8.0, 9.0],
490 };
491 let defs = payload_joints_to_defs(vec![pj]);
492 assert_eq!(defs.len(), 1);
493 assert_eq!(defs[0].name, "hip");
494 assert_eq!(defs[0].parent, 2);
495 assert_eq!(defs[0].translation, [1.0, 2.0, 3.0]);
496 assert_eq!(defs[0].rotation_deg, [4.0, 5.0, 6.0]);
497 assert_eq!(defs[0].scale, [7.0, 8.0, 9.0]);
498 }
499}