instance-buffer-codec.mjs 2.8 KB

1234567891011121314151617181920212223242526272829303132333435
  1. import assert from 'node:assert/strict'
  2. import {MeshoptEncoder,MeshoptDecoder} from 'meshoptimizer'
  3. import {parseGlb,packGlb} from './instance-repeated-meshes.mjs'
  4. await Promise.all([MeshoptEncoder.ready,MeshoptDecoder.ready])
  5. export function compressInstanceBuffers(bytes){
  6. const {json,bin}=parseGlb(bytes),dracoViews=new Set(json.meshes.flatMap(m=>m.primitives.map(p=>p.extensions?.KHR_draco_mesh_compression?.bufferView)).filter(i=>i!==undefined))
  7. const chunks=[];let size=0,fallback=0
  8. for(let i=0;i<json.bufferViews.length;i++){
  9. const view=json.bufferViews[i],data=bin.subarray(view.byteOffset||0,(view.byteOffset||0)+view.byteLength),a=json.accessors.find(a=>a.bufferView===i)
  10. const pad=(-size)&3;if(pad){chunks.push(Buffer.alloc(pad));size+=pad}
  11. if(dracoViews.has(i)||!a){view.byteOffset=size;view.buffer=0;chunks.push(data);size+=data.length;continue}
  12. assert.equal(a.componentType===5125||a.componentType===5126,true);assert.equal(a.byteOffset||0,0)
  13. const stride={SCALAR:4,VEC2:8,VEC3:12,VEC4:16}[a.type],count=data.length/stride,mode=a.componentType===5125?'INDICES':'ATTRIBUTES'
  14. assert.equal(count,a.count)
  15. const encoded=Buffer.from(MeshoptEncoder.encodeGltfBuffer(data,count,stride,mode))
  16. view.extensions={EXT_meshopt_compression:{buffer:0,byteOffset:size,byteLength:encoded.length,byteStride:stride,count,mode,filter:'NONE'}}
  17. view.buffer=1;view.byteOffset=fallback;fallback+=data.length
  18. chunks.push(encoded);size+=encoded.length
  19. }
  20. json.extensionsUsed=[...new Set([...(json.extensionsUsed||[]),'EXT_meshopt_compression'])]
  21. json.extensionsRequired=[...new Set([...(json.extensionsRequired||[]),'EXT_meshopt_compression'])]
  22. json.buffers=[{byteLength:size},{byteLength:fallback,extensions:{EXT_meshopt_compression:{fallback:true}}}]
  23. return packGlb(json,Buffer.concat(chunks))
  24. }
  25. export function unpackInstanceBuffers(bytes){
  26. const {json,bin}=parseGlb(bytes),chunks=[];let size=0
  27. for(const view of json.bufferViews){const pad=(-size)&3;if(pad){chunks.push(Buffer.alloc(pad));size+=pad}
  28. const ext=view.extensions?.EXT_meshopt_compression;let data
  29. if(ext){data=Buffer.alloc(ext.count*ext.byteStride);MeshoptDecoder.decodeGltfBuffer(data,ext.count,ext.byteStride,bin.subarray(ext.byteOffset,ext.byteOffset+ext.byteLength),ext.mode,ext.filter);delete view.extensions}
  30. else data=bin.subarray(view.byteOffset||0,(view.byteOffset||0)+view.byteLength)
  31. view.byteOffset=size;view.buffer=0;chunks.push(data);size+=data.length
  32. }
  33. json.buffers=[{byteLength:size}];json.extensionsUsed=(json.extensionsUsed||[]).filter(e=>e!=='EXT_meshopt_compression');json.extensionsRequired=(json.extensionsRequired||[]).filter(e=>e!=='EXT_meshopt_compression')
  34. return {json,bin:Buffer.concat(chunks)}
  35. }