import assert from 'node:assert/strict' // Streaming exporter supports standard embedded-raster textures, not extension image sources. export function verifyMaterialClosure(json, label = 'GLB') { const materials = json.materials || [], textures = json.textures || [], images = json.images || [], samplers = json.samplers || [] const usedMaterials = new Set(), usedTextures = new Set(), usedImages = new Set(), usedSamplers = new Set() function reference(index, values, kind, used) { assert.ok(Number.isInteger(index) && index >= 0 && index < values.length, `${label}: invalid ${kind} index ${index}`) used.add(index) } for (const mesh of json.meshes || []) for (const primitive of mesh.primitives) { reference(primitive.material, materials, 'material', usedMaterials) } function visit(value) { for (const [key, child] of Object.entries(value)) { if (!child || typeof child !== 'object') continue if (key.endsWith('Texture')) reference(child.index, textures, 'texture', usedTextures) else visit(child) } } for (const index of usedMaterials) visit(materials[index]) for (const index of usedTextures) { const texture = textures[index] reference(texture.source, images, 'image', usedImages) if (texture.sampler !== undefined) reference(texture.sampler, samplers, 'sampler', usedSamplers) } for (const [kind, used, values] of [['materials', usedMaterials, materials], ['textures', usedTextures, textures], ['images', usedImages, images], ['samplers', usedSamplers, samplers]]) { assert.equal(used.size, values.length, `${label}: unused ${kind}`) } }