| 401 | // ============================================================ |
| 402 | |
| 403 | fn load_scene(path: &Path) -> Result<Scene, String> { |
| 404 | let (document, buffers, images) = |
| 405 | gltf::import(path).map_err(|e| format!("gltf import failed: {e}"))?; |
| 406 | |
| 407 | // Pre-decode all images. glTF's `images` array is parallel to the |
| 408 | // `textures` array; each `Texture` references one image by index. |
| 409 | // We store the decoded RGBA8 buffers and let `textures` become the |
| 410 | // lookup index used by materials. |
| 411 | let decoded_images: Vec<Texture> = images |
| 412 | .into_iter() |
| 413 | .map(|img| { |
| 414 | // `gltf::image::Data` gives us pre-decoded pixels in the |
| 415 | // format described by `img.format`. Some glTFs ship as |
| 416 | // R8G8B8 without alpha; pad to RGBA8 so the sampler only |
| 417 | // has one pixel format to handle. |
| 418 | let pixels = match img.format { |
| 419 | gltf::image::Format::R8G8B8A8 => img.pixels.clone(), |
| 420 | gltf::image::Format::R8G8B8 => { |
| 421 | let mut p = Vec::with_capacity((img.width * img.height * 4) as usize); |
| 422 | for rgb in img.pixels.chunks_exact(3) { |
| 423 | p.push(rgb[0]); |
| 424 | p.push(rgb[1]); |
| 425 | p.push(rgb[2]); |
| 426 | p.push(255); |
| 427 | } |
| 428 | p |
| 429 | } |
| 430 | // Other formats (R8, R8G8, and 16-bit variants) are not |
| 431 | // used by the glTF samples we care about yet. Fall back |
| 432 | // to white so a bad texture doesn't nuke the whole |
| 433 | // render — we'd rather see the model. |
| 434 | _ => vec![255u8; (img.width * img.height * 4) as usize], |
| 435 | }; |
| 436 | Texture { |
| 437 | pixels, |
| 438 | width: img.width, |
| 439 | height: img.height, |
| 440 | } |
| 441 | }) |
| 442 | .collect(); |
| 443 | |
| 444 | // `textures` in glTF is an array of (image, sampler) pairs. We flatten |
| 445 | // to just "image index this texture uses" — samplers aren't honored |
| 446 | // yet (Phase 3+ concern). |
| 447 | let texture_to_image: Vec<u32> = document |
| 448 | .textures() |
| 449 | .map(|t| t.source().index() as u32) |
| 450 | .collect(); |
| 451 | |
| 452 | let mut materials: Vec<Material> = document |
| 453 | .materials() |
| 454 | .map(|m| { |
| 455 | let pbr = m.pbr_metallic_roughness(); |
| 456 | let base_color_texture = pbr.base_color_texture().and_then(|info| { |
| 457 | let tex_idx = info.texture().index(); |
| 458 | texture_to_image.get(tex_idx).copied() |
| 459 | }); |
| 460 | let metallic_roughness_texture = |