verify-material-closure.mjs 1.7 KB

12345678910111213141516171819202122232425262728293031
  1. import assert from 'node:assert/strict'
  2. // Streaming exporter supports standard embedded-raster textures, not extension image sources.
  3. export function verifyMaterialClosure(json, label = 'GLB') {
  4. const materials = json.materials || [], textures = json.textures || [], images = json.images || [], samplers = json.samplers || []
  5. const usedMaterials = new Set(), usedTextures = new Set(), usedImages = new Set(), usedSamplers = new Set()
  6. function reference(index, values, kind, used) {
  7. assert.ok(Number.isInteger(index) && index >= 0 && index < values.length, `${label}: invalid ${kind} index ${index}`)
  8. used.add(index)
  9. }
  10. for (const mesh of json.meshes || []) for (const primitive of mesh.primitives) {
  11. reference(primitive.material, materials, 'material', usedMaterials)
  12. }
  13. function visit(value) {
  14. for (const [key, child] of Object.entries(value)) {
  15. if (!child || typeof child !== 'object') continue
  16. if (key.endsWith('Texture')) reference(child.index, textures, 'texture', usedTextures)
  17. else visit(child)
  18. }
  19. }
  20. for (const index of usedMaterials) visit(materials[index])
  21. for (const index of usedTextures) {
  22. const texture = textures[index]
  23. reference(texture.source, images, 'image', usedImages)
  24. if (texture.sampler !== undefined) reference(texture.sampler, samplers, 'sampler', usedSamplers)
  25. }
  26. for (const [kind, used, values] of [['materials', usedMaterials, materials], ['textures', usedTextures, textures],
  27. ['images', usedImages, images], ['samplers', usedSamplers, samplers]]) {
  28. assert.equal(used.size, values.length, `${label}: unused ${kind}`)
  29. }
  30. }