| 32 | } |
| 33 | |
| 34 | pub fn load_glb<P: AsRef<Path>>(path: P) -> Result<MeshData, String> { |
| 35 | let bytes = std::fs::read(path.as_ref()) |
| 36 | .map_err(|e| format!("read {}: {e}", path.as_ref().display()))?; |
| 37 | let g = gltf::Gltf::from_slice(&bytes).map_err(|e| format!("parse gltf: {e}"))?; |
| 38 | |
| 39 | // GLB: a single embedded BIN buffer. |
| 40 | let bin = g |
| 41 | .blob |
| 42 | .as_ref() |
| 43 | .ok_or("expected GLB with embedded BIN buffer")?; |
| 44 | |
| 45 | let mut positions = Vec::<[f32; 3]>::new(); |
| 46 | let mut normals = Vec::<[f32; 3]>::new(); |
| 47 | let mut indices = Vec::<u32>::new(); |
| 48 | let mut aabb_min = [f32::INFINITY; 3]; |
| 49 | let mut aabb_max = [f32::NEG_INFINITY; 3]; |
| 50 | |
| 51 | for mesh in g.meshes() { |
| 52 | for prim in mesh.primitives() { |
| 53 | let reader = prim.reader(|buf| { |
| 54 | if buf.index() == 0 { |
| 55 | Some(bin.as_slice()) |
| 56 | } else { |
| 57 | None |
| 58 | } |
| 59 | }); |
| 60 | |
| 61 | let base = positions.len() as u32; |
| 62 | |
| 63 | let pos_iter = reader.read_positions().ok_or("primitive missing POSITION")?; |
| 64 | for p in pos_iter { |
| 65 | aabb_min[0] = aabb_min[0].min(p[0]); |
| 66 | aabb_min[1] = aabb_min[1].min(p[1]); |
| 67 | aabb_min[2] = aabb_min[2].min(p[2]); |
| 68 | aabb_max[0] = aabb_max[0].max(p[0]); |
| 69 | aabb_max[1] = aabb_max[1].max(p[1]); |
| 70 | aabb_max[2] = aabb_max[2].max(p[2]); |
| 71 | positions.push(p); |
| 72 | } |
| 73 | |
| 74 | // Normals: optional in spec; if missing, fill with +Y so the |
| 75 | // shader still gets something sensible (lambert will look |
| 76 | // flat but not crash). |
| 77 | if let Some(n_iter) = reader.read_normals() { |
| 78 | normals.extend(n_iter); |
| 79 | } else { |
| 80 | normals.resize(positions.len(), [0.0, 1.0, 0.0]); |
| 81 | } |
| 82 | // Make sure normals length matches positions length even |
| 83 | // when the primitive supplies fewer than expected. |
| 84 | normals.resize(positions.len(), [0.0, 1.0, 0.0]); |
| 85 | |
| 86 | if let Some(idx_iter) = reader.read_indices() { |
| 87 | for i in idx_iter.into_u32() { |
| 88 | indices.push(base + i); |
| 89 | } |
| 90 | } else { |
| 91 | // Non-indexed: emit sequential indices for this prim. |