(
prim: &JVal, root: &JVal,
accessors: &[JVal], buf_views: &[JVal], bin_bytes: &[u8],
)
| 359 | } |
| 360 | |
| 361 | fn parse_primitive( |
| 362 | prim: &JVal, root: &JVal, |
| 363 | accessors: &[JVal], buf_views: &[JVal], bin_bytes: &[u8], |
| 364 | ) -> Option<Primitive> { |
| 365 | let attrs = prim.get("attributes")?; |
| 366 | let pos_acc = attrs.get("POSITION")?.num()? as usize; |
| 367 | let nrm_acc = attrs.get("NORMAL").and_then(|v| v.num().map(|n| n as usize)); |
| 368 | let uv_acc = attrs.get("TEXCOORD_0").and_then(|v| v.num().map(|n| n as usize)); |
| 369 | let j0_acc = attrs.get("JOINTS_0").and_then(|v| v.num().map(|n| n as usize)); |
| 370 | let w0_acc = attrs.get("WEIGHTS_0").and_then(|v| v.num().map(|n| n as usize)); |
| 371 | let idx_acc = prim.get("indices").and_then(|v| v.num().map(|n| n as usize)); |
| 372 | let mat_idx = prim.get("material").and_then(|v| v.num().map(|n| n as usize)); |
| 373 | |
| 374 | let positions = read_f32_vec(accessors, buf_views, bin_bytes, pos_acc, 3)?; |
| 375 | let normals = nrm_acc |
| 376 | .and_then(|a| read_f32_vec(accessors, buf_views, bin_bytes, a, 3)) |
| 377 | .unwrap_or_default(); |
| 378 | let uvs = uv_acc |
| 379 | .and_then(|a| read_f32_vec(accessors, buf_views, bin_bytes, a, 2)) |
| 380 | .unwrap_or_default(); |
| 381 | // Indexless primitives (POSITION-only triangle soups) — synthesize |
| 382 | // sequential indices so the SceneKit SCNGeometryElement path stays |
| 383 | // uniform. Fox.glb uses this layout. |
| 384 | let indices = match idx_acc { |
| 385 | Some(a) => read_index_vec(accessors, buf_views, bin_bytes, a)?, |
| 386 | None => (0..(positions.len() / 3) as u32).collect(), |
| 387 | }; |
| 388 | // JOINTS_0 is typically unsigned byte or short vec4 per vertex. |
| 389 | let joint_indices = j0_acc |
| 390 | .and_then(|a| read_joint_indices(accessors, buf_views, bin_bytes, a)) |
| 391 | .unwrap_or_default(); |
| 392 | // WEIGHTS_0 is f32 vec4 per vertex (spec allows normalized u8/u16 but |
| 393 | // current Khronos samples use float; stick with float for v1). |
| 394 | let weights = w0_acc |
| 395 | .and_then(|a| read_f32_vec(accessors, buf_views, bin_bytes, a, 4)) |
| 396 | .unwrap_or_default(); |
| 397 | |
| 398 | let mut color = [1.0f32; 4]; |
| 399 | let mut metallic = 1.0f32; |
| 400 | let mut roughness = 1.0f32; |
| 401 | let mut tex_base_color = 0u32; |
| 402 | let mut tex_normal = 0u32; |
| 403 | let mut tex_metallic_roughness = 0u32; |
| 404 | let mut tex_emissive = 0u32; |
| 405 | let mut tex_occlusion = 0u32; |
| 406 | |
| 407 | if let Some(mi) = mat_idx { |
| 408 | if let Some(mats) = root.get("materials").and_then(|v| v.arr()) { |
| 409 | if let Some(mat) = mats.get(mi) { |
| 410 | if let Some(pbr) = mat.get("pbrMetallicRoughness") { |
| 411 | if let Some(bc) = pbr.get("baseColorFactor").and_then(|v| v.arr()) { |
| 412 | for (i, n) in bc.iter().take(4).enumerate() { |
| 413 | if let Some(f) = n.num() { color[i] = f as f32; } |
| 414 | } |
| 415 | } |
| 416 | if let Some(m) = pbr.get("metallicFactor").and_then(|v| v.num()) { |
| 417 | metallic = m as f32; |
| 418 | } |
no test coverage detected