@2112-lab/central-plant 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/cjs/_virtual/_rollupPluginBabelHelpers.js +432 -1
  2. package/dist/cjs/node_modules/@2112-lab/pathfinder/dist/index.esm.js +1448 -1
  3. package/dist/cjs/node_modules/three/examples/jsm/controls/OrbitControls.js +1853 -1
  4. package/dist/cjs/node_modules/three/examples/jsm/exporters/GLTFExporter.js +3537 -1
  5. package/dist/cjs/node_modules/three/examples/jsm/exporters/OBJExporter.js +305 -1
  6. package/dist/cjs/node_modules/three/examples/jsm/exporters/PLYExporter.js +542 -1
  7. package/dist/cjs/node_modules/three/examples/jsm/exporters/STLExporter.js +218 -1
  8. package/dist/cjs/node_modules/three/examples/jsm/loaders/DRACOLoader.js +683 -1
  9. package/dist/cjs/node_modules/three/examples/jsm/loaders/GLTFLoader.js +4811 -1
  10. package/dist/cjs/node_modules/three/examples/jsm/loaders/RGBELoader.js +480 -1
  11. package/dist/cjs/node_modules/three/examples/jsm/renderers/CSS2DRenderer.js +309 -1
  12. package/dist/cjs/node_modules/three/examples/jsm/utils/BufferGeometryUtils.js +120 -1
  13. package/dist/cjs/src/analysis/analysis.js +560 -1
  14. package/dist/cjs/src/analysis/testing.js +958 -1
  15. package/dist/cjs/src/core/centralPlant.js +1149 -1
  16. package/dist/cjs/src/core/debugLogger.js +175 -1
  17. package/dist/cjs/src/core/mathUtils.js +574 -1
  18. package/dist/cjs/src/core/nameUtils.js +93 -1
  19. package/dist/cjs/src/data/export.js +716 -1
  20. package/dist/cjs/src/data/import.js +380 -1
  21. package/dist/cjs/src/data/numerics.js +522 -1
  22. package/dist/cjs/src/helpers/sceneHelper.js +572 -1
  23. package/dist/cjs/src/index.js +69 -1
  24. package/dist/cjs/src/managers/components/animationManager.js +123 -1
  25. package/dist/cjs/src/managers/components/componentManager.js +332 -1
  26. package/dist/cjs/src/managers/components/pathfindingManager.js +1441 -1
  27. package/dist/cjs/src/managers/controls/TransformControls.js +1063 -1
  28. package/dist/cjs/src/managers/controls/cameraControlsManager.js +79 -1
  29. package/dist/cjs/src/managers/controls/dragDropManager.js +1026 -1
  30. package/dist/cjs/src/managers/controls/keyboardControlsManager.js +395 -1
  31. package/dist/cjs/src/managers/controls/transformControlsManager.js +1807 -1
  32. package/dist/cjs/src/managers/environment/environmentManager.js +714 -1
  33. package/dist/cjs/src/managers/environment/textureConfig.js +229 -1
  34. package/dist/cjs/src/managers/scene/sceneExportManager.js +264 -1
  35. package/dist/cjs/src/managers/scene/sceneInitializationManager.js +346 -1
  36. package/dist/cjs/src/managers/scene/sceneOperationsManager.js +1509 -1
  37. package/dist/cjs/src/managers/scene/sceneTooltipsManager.js +661 -1
  38. package/dist/cjs/src/managers/system/disposalManager.js +444 -1
  39. package/dist/cjs/src/managers/system/hotReloadManager.js +291 -1
  40. package/dist/cjs/src/managers/system/performanceMonitor.js +863 -1
  41. package/dist/cjs/src/rendering/modelPreloader.js +369 -1
  42. package/dist/cjs/src/rendering/rendering2D.js +631 -1
  43. package/dist/cjs/src/rendering/rendering3D.js +685 -1
  44. package/dist/esm/_virtual/_rollupPluginBabelHelpers.js +396 -1
  45. package/dist/esm/node_modules/@2112-lab/pathfinder/dist/index.esm.js +1444 -1
  46. package/dist/esm/node_modules/three/examples/jsm/controls/OrbitControls.js +1849 -1
  47. package/dist/esm/node_modules/three/examples/jsm/exporters/GLTFExporter.js +3533 -1
  48. package/dist/esm/node_modules/three/examples/jsm/exporters/OBJExporter.js +301 -1
  49. package/dist/esm/node_modules/three/examples/jsm/exporters/PLYExporter.js +538 -1
  50. package/dist/esm/node_modules/three/examples/jsm/exporters/STLExporter.js +214 -1
  51. package/dist/esm/node_modules/three/examples/jsm/loaders/DRACOLoader.js +679 -1
  52. package/dist/esm/node_modules/three/examples/jsm/loaders/GLTFLoader.js +4807 -1
  53. package/dist/esm/node_modules/three/examples/jsm/loaders/RGBELoader.js +476 -1
  54. package/dist/esm/node_modules/three/examples/jsm/renderers/CSS2DRenderer.js +304 -1
  55. package/dist/esm/node_modules/three/examples/jsm/utils/BufferGeometryUtils.js +116 -1
  56. package/dist/esm/src/analysis/analysis.js +536 -1
  57. package/dist/esm/src/analysis/testing.js +954 -1
  58. package/dist/esm/src/core/centralPlant.js +1144 -1
  59. package/dist/esm/src/core/debugLogger.js +167 -1
  60. package/dist/esm/src/core/mathUtils.js +570 -1
  61. package/dist/esm/src/core/nameUtils.js +87 -1
  62. package/dist/esm/src/data/export.js +712 -1
  63. package/dist/esm/src/data/import.js +356 -1
  64. package/dist/esm/src/data/numerics.js +518 -1
  65. package/dist/esm/src/helpers/sceneHelper.js +547 -1
  66. package/dist/esm/src/index.js +35 -1
  67. package/dist/esm/src/managers/components/animationManager.js +119 -1
  68. package/dist/esm/src/managers/components/componentManager.js +328 -1
  69. package/dist/esm/src/managers/components/pathfindingManager.js +1417 -1
  70. package/dist/esm/src/managers/controls/TransformControls.js +1057 -1
  71. package/dist/esm/src/managers/controls/cameraControlsManager.js +75 -1
  72. package/dist/esm/src/managers/controls/dragDropManager.js +1002 -1
  73. package/dist/esm/src/managers/controls/keyboardControlsManager.js +371 -1
  74. package/dist/esm/src/managers/controls/transformControlsManager.js +1782 -1
  75. package/dist/esm/src/managers/environment/environmentManager.js +690 -1
  76. package/dist/esm/src/managers/environment/textureConfig.js +202 -1
  77. package/dist/esm/src/managers/scene/sceneExportManager.js +260 -1
  78. package/dist/esm/src/managers/scene/sceneInitializationManager.js +322 -1
  79. package/dist/esm/src/managers/scene/sceneOperationsManager.js +1485 -1
  80. package/dist/esm/src/managers/scene/sceneTooltipsManager.js +637 -1
  81. package/dist/esm/src/managers/system/disposalManager.js +440 -1
  82. package/dist/esm/src/managers/system/hotReloadManager.js +287 -1
  83. package/dist/esm/src/managers/system/performanceMonitor.js +858 -1
  84. package/dist/esm/src/rendering/modelPreloader.js +364 -1
  85. package/dist/esm/src/rendering/rendering2D.js +627 -1
  86. package/dist/esm/src/rendering/rendering3D.js +661 -1
  87. package/package.json +1 -1
@@ -1 +1,3537 @@
1
- "use strict";Object.defineProperty(exports,"t",{value:!0});var t=require("three");const e={POSITION:["byte","byte normalized","unsigned byte","unsigned byte normalized","short","short normalized","unsigned short","unsigned short normalized"],NORMAL:["byte normalized","short normalized"],TANGENT:["byte normalized","short normalized"],TEXCOORD:["byte","byte normalized","unsigned byte","short","short normalized","unsigned short"]};class n{constructor(){this.textureUtils=null,this.pluginCallbacks=[],this.register(function(t){return new F(t)}),this.register(function(t){return new z(t)}),this.register(function(t){return new k(t)}),this.register(function(t){return new D(t)}),this.register(function(t){return new G(t)}),this.register(function(t){return new V(t)}),this.register(function(t){return new U(t)}),this.register(function(t){return new B(t)}),this.register(function(t){return new K(t)}),this.register(function(t){return new j(t)}),this.register(function(t){return new P(t)}),this.register(function(t){return new X(t)}),this.register(function(t){return new q(t)}),this.register(function(t){return new J(t)})}register(t){return-1===this.pluginCallbacks.indexOf(t)&&this.pluginCallbacks.push(t),this}unregister(t){return-1!==this.pluginCallbacks.indexOf(t)&&this.pluginCallbacks.splice(this.pluginCallbacks.indexOf(t),1),this}setTextureUtils(t){return this.textureUtils=t,this}parse(t,e,n,s){const i=new S,r=[];for(let t=0,e=this.pluginCallbacks.length;t<e;t++)r.push(this.pluginCallbacks[t](i));i.setPlugins(r),i.setTextureUtils(this.textureUtils),i.writeAsync(t,e,s).catch(n)}parseAsync(t,e){const n=this;return new Promise(function(s,i){n.parse(t,s,i,e)})}}const s=0,i=1,r=2,o=3,a=4,c=5120,h=5121,u=5122,l=5123,f=5124,w=5125,d=5126,y=34962,m=34963,p=9728,A=9729,T=9984,x=9985,g=9986,b=9987,E=33071,M=33648,_=10497,v="KHR_mesh_quantization",O={};O[t.NearestFilter]=p,O[t.NearestMipmapNearestFilter]=T,O[t.NearestMipmapLinearFilter]=g,O[t.LinearFilter]=A,O[t.LinearMipmapNearestFilter]=x,O[t.LinearMipmapLinearFilter]=b,O[t.ClampToEdgeWrapping]=E,O[t.RepeatWrapping]=_,O[t.MirroredRepeatWrapping]=M;const C={scale:"scale",position:"translation",quaternion:"rotation",morphTargetInfluences:"weights"},R=new t.Color;function I(t,e){return t.length===e.length&&t.every(function(t,n){return t===e[n]})}function N(t){return 4*Math.ceil(t/4)}function H(t,e=0){const n=N(t.byteLength);if(n!==t.byteLength){const s=new Uint8Array(n);if(s.set(new Uint8Array(t)),0!==e)for(let i=t.byteLength;i<n;i++)s[i]=e;return s.buffer}return t}function L(){return"undefined"==typeof document&&"undefined"!=typeof OffscreenCanvas?new OffscreenCanvas(1,1):document.createElement("canvas")}class S{constructor(){this.plugins=[],this.options={},this.pending=[],this.buffers=[],this.byteOffset=0,this.buffers=[],this.nodeMap=new Map,this.skins=[],this.extensionsUsed={},this.extensionsRequired={},this.uids=new Map,this.uid=0,this.json={asset:{version:"2.0",generator:"THREE.GLTFExporter r"+t.REVISION}},this.cache={meshes:new Map,attributes:new Map,attributesNormalized:new Map,materials:new Map,textures:new Map,images:new Map},this.textureUtils=null}setPlugins(t){this.plugins=t}setTextureUtils(t){this.textureUtils=t}async writeAsync(t,e,n={}){this.options=Object.assign({binary:!1,trs:!1,onlyVisible:!0,maxTextureSize:1/0,animations:[],includeCustomExtensions:!1},n),this.options.animations.length>0&&(this.options.trs=!0),await this.processInputAsync(t),await Promise.all(this.pending);const s=this,i=s.buffers,r=s.json;n=s.options;const o=s.extensionsUsed,a=s.extensionsRequired,c=new Blob(i,{type:"application/octet-stream"}),h=Object.keys(o),u=Object.keys(a);if(h.length>0&&(r.extensionsUsed=h),u.length>0&&(r.extensionsRequired=u),r.buffers&&r.buffers.length>0&&(r.buffers[0].byteLength=c.size),!0===n.binary){const t=new FileReader;t.readAsArrayBuffer(c),t.onloadend=function(){const n=H(t.result),s=new DataView(new ArrayBuffer(8));s.setUint32(0,n.byteLength,!0),s.setUint32(4,5130562,!0);const i=H((o=JSON.stringify(r),(new TextEncoder).encode(o).buffer),32);var o;const a=new DataView(new ArrayBuffer(8));a.setUint32(0,i.byteLength,!0),a.setUint32(4,1313821514,!0);const c=new ArrayBuffer(12),h=new DataView(c);h.setUint32(0,1179937895,!0),h.setUint32(4,2,!0);const u=12+a.byteLength+i.byteLength+s.byteLength+n.byteLength;h.setUint32(8,u,!0);const l=new Blob([c,a,i,s,n],{type:"application/octet-stream"}),f=new FileReader;f.readAsArrayBuffer(l),f.onloadend=function(){e(f.result)}}}else if(r.buffers&&r.buffers.length>0){const t=new FileReader;t.readAsDataURL(c),t.onloadend=function(){const n=t.result;r.buffers[0].uri=n,e(r)}}else e(r)}serializeUserData(t,e){if(0===Object.keys(t.userData).length)return;const n=this.options,s=this.extensionsUsed;try{const i=JSON.parse(JSON.stringify(t.userData));if(n.includeCustomExtensions&&i.gltfExtensions){void 0===e.extensions&&(e.extensions={});for(const t in i.gltfExtensions)e.extensions[t]=i.gltfExtensions[t],s[t]=!0;delete i.gltfExtensions}Object.keys(i).length>0&&(e.extras=i)}catch(t){}}getUID(t,e=!1){if(!1===this.uids.has(t)){const e=new Map;e.set(!0,this.uid++),e.set(!1,this.uid++),this.uids.set(t,e)}return this.uids.get(t).get(e)}isNormalizedNormalAttribute(e){if(this.cache.attributesNormalized.has(e))return!1;const n=new t.Vector3;for(let t=0,s=e.count;t<s;t++)if(Math.abs(n.fromBufferAttribute(e,t).length()-1)>5e-4)return!1;return!0}createNormalizedNormalAttribute(e){const n=this.cache;if(n.attributesNormalized.has(e))return n.attributesNormalized.get(e);const s=e.clone(),i=new t.Vector3;for(let t=0,e=s.count;t<e;t++)i.fromBufferAttribute(s,t),0===i.x&&0===i.y&&0===i.z?i.setX(1):i.normalize(),s.setXYZ(t,i.x,i.y,i.z);return n.attributesNormalized.set(e,s),s}applyTextureTransform(t,e){let n=!1;const s={};0===e.offset.x&&0===e.offset.y||(s.offset=e.offset.toArray(),n=!0),0!==e.rotation&&(s.rotation=e.rotation,n=!0),1===e.repeat.x&&1===e.repeat.y||(s.scale=e.repeat.toArray(),n=!0),n&&(t.extensions=t.extensions||{},t.extensions.KHR_texture_transform=s,this.extensionsUsed.KHR_texture_transform=!0)}async buildMetalRoughTextureAsync(e,n){if(e===n)return e;function s(e){return e.colorSpace===t.SRGBColorSpace?function(t){return t<.04045?.0773993808*t:Math.pow(.9478672986*t+.0521327014,2.4)}:function(t){return t}}e instanceof t.CompressedTexture&&(e=await this.decompressTextureAsync(e)),n instanceof t.CompressedTexture&&(n=await this.decompressTextureAsync(n));const i=e?e.image:null,r=n?n.image:null,o=Math.max(i?i.width:0,r?r.width:0),a=Math.max(i?i.height:0,r?r.height:0),c=L();c.width=o,c.height=a;const h=c.getContext("2d",{willReadFrequently:!0});h.fillStyle="#00ffff",h.fillRect(0,0,o,a);const u=h.getImageData(0,0,o,a);if(i){h.drawImage(i,0,0,o,a);const t=s(e),n=h.getImageData(0,0,o,a).data;for(let e=2;e<n.length;e+=4)u.data[e]=256*t(n[e]/256)}if(r){h.drawImage(r,0,0,o,a);const t=s(n),e=h.getImageData(0,0,o,a).data;for(let n=1;n<e.length;n+=4)u.data[n]=256*t(e[n]/256)}h.putImageData(u,0,0);const l=(e||n).clone();return l.source=new t.Source(c),l.colorSpace=t.NoColorSpace,l.channel=(e||n).channel,e&&n&&(e.channel,n.channel),l}async decompressTextureAsync(t,e=1/0){if(null===this.textureUtils)throw new Error("THREE.GLTFExporter: setTextureUtils() must be called to process compressed textures.");return await this.textureUtils.decompress(t,e)}processBuffer(t){const e=this.json,n=this.buffers;return e.buffers||(e.buffers=[{byteLength:0}]),n.push(t),0}processBufferView(e,n,s,i,r){const o=this.json;let a;switch(o.bufferViews||(o.bufferViews=[]),n){case c:case h:a=1;break;case u:case l:a=2;break;default:a=4}let m=e.itemSize*a;r===y&&(m=4*Math.ceil(m/4));const p=N(i*m),A=new DataView(new ArrayBuffer(p));let T=0;for(let r=s;r<s+i;r++){for(let s=0;s<e.itemSize;s++){let i;e.itemSize>4?i=e.array[r*e.itemSize+s]:(0===s?i=e.getX(r):1===s?i=e.getY(r):2===s?i=e.getZ(r):3===s&&(i=e.getW(r)),!0===e.normalized&&(i=t.MathUtils.normalize(i,e.array))),n===d?A.setFloat32(T,i,!0):n===f?A.setInt32(T,i,!0):n===w?A.setUint32(T,i,!0):n===u?A.setInt16(T,i,!0):n===l?A.setUint16(T,i,!0):n===c?A.setInt8(T,i):n===h&&A.setUint8(T,i),T+=a}T%m!==0&&(T+=m-T%m)}const x={buffer:this.processBuffer(A.buffer),byteOffset:this.byteOffset,byteLength:p};void 0!==r&&(x.target=r),r===y&&(x.byteStride=m),this.byteOffset+=p,o.bufferViews.push(x);return{id:o.bufferViews.length-1,byteLength:0}}processBufferViewImage(t){const e=this,n=e.json;return n.bufferViews||(n.bufferViews=[]),new Promise(function(s){const i=new FileReader;i.readAsArrayBuffer(t),i.onloadend=function(){const t=H(i.result),r={buffer:e.processBuffer(t),byteOffset:e.byteOffset,byteLength:t.byteLength};e.byteOffset+=t.byteLength,s(n.bufferViews.push(r)-1)}})}processAccessor(e,n,s,i){const r=this.json;let o;if(e.array.constructor===Float32Array)o=d;else if(e.array.constructor===Int32Array)o=f;else if(e.array.constructor===Uint32Array)o=w;else if(e.array.constructor===Int16Array)o=u;else if(e.array.constructor===Uint16Array)o=l;else if(e.array.constructor===Int8Array)o=c;else{if(e.array.constructor!==Uint8Array)throw new Error("THREE.GLTFExporter: Unsupported bufferAttribute component type: "+e.array.constructor.name);o=h}if(void 0===s&&(s=0),void 0!==i&&i!==1/0||(i=e.count),0===i)return null;const a=function(e,n,s){const i={min:new Array(e.itemSize).fill(Number.POSITIVE_INFINITY),max:new Array(e.itemSize).fill(Number.NEGATIVE_INFINITY)};for(let r=n;r<n+s;r++)for(let n=0;n<e.itemSize;n++){let s;e.itemSize>4?s=e.array[r*e.itemSize+n]:(0===n?s=e.getX(r):1===n?s=e.getY(r):2===n?s=e.getZ(r):3===n&&(s=e.getW(r)),!0===e.normalized&&(s=t.MathUtils.normalize(s,e.array))),i.min[n]=Math.min(i.min[n],s),i.max[n]=Math.max(i.max[n],s)}return i}(e,s,i);let p;void 0!==n&&(p=e===n.index?m:y);const A=this.processBufferView(e,o,s,i,p),T={bufferView:A.id,byteOffset:A.byteOffset,componentType:o,count:i,max:a.max,min:a.min,type:{1:"SCALAR",2:"VEC2",3:"VEC3",4:"VEC4",9:"MAT3",16:"MAT4"}[e.itemSize]};return!0===e.normalized&&(T.normalized=!0),r.accessors||(r.accessors=[]),r.accessors.push(T)-1}processImage(e,n,s,i="image/png"){if(null!==e){const n=this,r=n.cache,o=n.json,a=n.options,c=n.pending;r.images.has(e)||r.images.set(e,{});const h=r.images.get(e),u=i+":flipY/"+s.toString();if(void 0!==h[u])return h[u];o.images||(o.images=[]);const l={mimeType:i},f=L();f.width=Math.min(e.width,a.maxTextureSize),f.height=Math.min(e.height,a.maxTextureSize);const w=f.getContext("2d",{willReadFrequently:!0});if(!0===s&&(w.translate(0,f.height),w.scale(1,-1)),void 0!==e.data){t.RGBAFormat,e.width>a.maxTextureSize||(e.height,a.maxTextureSize);const n=new Uint8ClampedArray(e.height*e.width*4);for(let t=0;t<n.length;t+=4)n[t+0]=e.data[t+0],n[t+1]=e.data[t+1],n[t+2]=e.data[t+2],n[t+3]=e.data[t+3];w.putImageData(new ImageData(n,e.width,e.height),0,0)}else{if(!("undefined"!=typeof HTMLImageElement&&e instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&e instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&e instanceof ImageBitmap||"undefined"!=typeof OffscreenCanvas&&e instanceof OffscreenCanvas))throw new Error("THREE.GLTFExporter: Invalid image type. Use HTMLImageElement, HTMLCanvasElement, ImageBitmap or OffscreenCanvas.");w.drawImage(e,0,0,f.width,f.height)}!0===a.binary?c.push(function(t,e){if(void 0!==t.toBlob)return new Promise(n=>t.toBlob(n,e));let n;return"image/jpeg"===e?n=.92:"image/webp"===e&&(n=.8),t.convertToBlob({type:e,quality:n})}(f,i).then(t=>n.processBufferViewImage(t)).then(t=>{l.bufferView=t})):l.uri=t.ImageUtils.getDataURL(f,i);const d=o.images.push(l)-1;return h[u]=d,d}throw new Error("THREE.GLTFExporter: No valid image data found. Unable to process texture.")}processSampler(t){const e=this.json;e.samplers||(e.samplers=[]);const n={magFilter:O[t.magFilter],minFilter:O[t.minFilter],wrapS:O[t.wrapS],wrapT:O[t.wrapT]};return e.samplers.push(n)-1}async processTextureAsync(e){const n=this.options,s=this.cache,i=this.json;if(s.textures.has(e))return s.textures.get(e);i.textures||(i.textures=[]),e instanceof t.CompressedTexture&&(e=await this.decompressTextureAsync(e,n.maxTextureSize));let r=e.userData.mimeType;"image/webp"===r&&(r="image/png");const o={sampler:this.processSampler(e),source:this.processImage(e.image,e.format,e.flipY,r)};e.name&&(o.name=e.name),await this.i(async function(t){t.writeTexture&&await t.writeTexture(e,o)});const a=i.textures.push(o)-1;return s.textures.set(e,a),a}async processMaterialAsync(e){const n=this.cache,s=this.json;if(n.materials.has(e))return n.materials.get(e);if(e.isShaderMaterial)return null;s.materials||(s.materials=[]);const i={pbrMetallicRoughness:{}};!0!==e.isMeshStandardMaterial&&e.isMeshBasicMaterial;const r=e.color.toArray().concat([e.opacity]);if(I(r,[1,1,1,1])||(i.pbrMetallicRoughness.baseColorFactor=r),e.isMeshStandardMaterial?(i.pbrMetallicRoughness.metallicFactor=e.metalness,i.pbrMetallicRoughness.roughnessFactor=e.roughness):(i.pbrMetallicRoughness.metallicFactor=0,i.pbrMetallicRoughness.roughnessFactor=1),e.metalnessMap||e.roughnessMap){const t=await this.buildMetalRoughTextureAsync(e.metalnessMap,e.roughnessMap),n={index:await this.processTextureAsync(t),texCoord:t.channel};this.applyTextureTransform(n,t),i.pbrMetallicRoughness.metallicRoughnessTexture=n}if(e.map){const t={index:await this.processTextureAsync(e.map),texCoord:e.map.channel};this.applyTextureTransform(t,e.map),i.pbrMetallicRoughness.baseColorTexture=t}if(e.emissive){const t=e.emissive;if(Math.max(t.r,t.g,t.b)>0&&(i.emissiveFactor=e.emissive.toArray()),e.emissiveMap){const t={index:await this.processTextureAsync(e.emissiveMap),texCoord:e.emissiveMap.channel};this.applyTextureTransform(t,e.emissiveMap),i.emissiveTexture=t}}if(e.normalMap){const t={index:await this.processTextureAsync(e.normalMap),texCoord:e.normalMap.channel};e.normalScale&&1!==e.normalScale.x&&(t.scale=e.normalScale.x),this.applyTextureTransform(t,e.normalMap),i.normalTexture=t}if(e.aoMap){const t={index:await this.processTextureAsync(e.aoMap),texCoord:e.aoMap.channel};1!==e.aoMapIntensity&&(t.strength=e.aoMapIntensity),this.applyTextureTransform(t,e.aoMap),i.occlusionTexture=t}e.transparent?i.alphaMode="BLEND":e.alphaTest>0&&(i.alphaMode="MASK",i.alphaCutoff=e.alphaTest),e.side===t.DoubleSide&&(i.doubleSided=!0),""!==e.name&&(i.name=e.name),this.serializeUserData(e,i),await this.i(async function(t){t.writeMaterialAsync&&await t.writeMaterialAsync(e,i)});const o=s.materials.push(i)-1;return n.materials.set(e,o),o}async processMeshAsync(e){const c=this.cache,h=this.json,u=[e.geometry.uuid];if(Array.isArray(e.material))for(let t=0,n=e.material.length;t<n;t++)u.push(e.material[t].uuid);else u.push(e.material.uuid);const l=u.join(":");if(c.meshes.has(l))return c.meshes.get(l);const f=e.geometry;let w;w=e.isLineSegments?i:e.isLineLoop?r:e.isLine?o:e.isPoints?s:e.material.wireframe?i:a;const d={},y={},m=[],p=[],A={uv:"TEXCOORD_0",uv1:"TEXCOORD_1",uv2:"TEXCOORD_2",uv3:"TEXCOORD_3",color:"COLOR_0",skinWeight:"WEIGHTS_0",skinIndex:"JOINTS_0"},T=f.getAttribute("normal");void 0===T||this.isNormalizedNormalAttribute(T)||f.setAttribute("normal",this.createNormalizedNormalAttribute(T));let x=null;for(let e in f.attributes){if("morph"===e.slice(0,5))continue;const s=f.attributes[e];e=A[e]||e.toUpperCase();if(/^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/.test(e)||(e="_"+e),c.attributes.has(this.getUID(s))){y[e]=c.attributes.get(this.getUID(s));continue}x=null;const i=s.array;"JOINTS_0"!==e||i instanceof Uint16Array||i instanceof Uint8Array?(i instanceof Uint32Array||i instanceof Int32Array)&&!e.startsWith("_")&&(x=n.Utils.toFloat32BufferAttribute(s)):x=new t.BufferAttribute(new Uint16Array(i),s.itemSize,s.normalized);const r=this.processAccessor(x||s,f);null!==r&&(e.startsWith("_")||this.detectMeshQuantization(e,s),y[e]=r,c.attributes.set(this.getUID(s),r))}if(void 0!==T&&f.setAttribute("normal",T),0===Object.keys(y).length)return null;if(void 0!==e.morphTargetInfluences&&e.morphTargetInfluences.length>0){const t=[],n=[],s={};if(void 0!==e.morphTargetDictionary)for(const t in e.morphTargetDictionary)s[e.morphTargetDictionary[t]]=t;for(let i=0;i<e.morphTargetInfluences.length;++i){const r={};let o=!1;for(const t in f.morphAttributes){if("position"!==t&&"normal"!==t){o||(o=!0);continue}const e=f.morphAttributes[t][i],n=t.toUpperCase(),s=f.attributes[t];if(c.attributes.has(this.getUID(e,!0))){r[n]=c.attributes.get(this.getUID(e,!0));continue}const a=e.clone();if(!f.morphTargetsRelative)for(let t=0,n=e.count;t<n;t++)for(let n=0;n<e.itemSize;n++)0===n&&a.setX(t,e.getX(t)-s.getX(t)),1===n&&a.setY(t,e.getY(t)-s.getY(t)),2===n&&a.setZ(t,e.getZ(t)-s.getZ(t)),3===n&&a.setW(t,e.getW(t)-s.getW(t));r[n]=this.processAccessor(a,f),c.attributes.set(this.getUID(s,!0),r[n])}p.push(r),t.push(e.morphTargetInfluences[i]),void 0!==e.morphTargetDictionary&&n.push(s[i])}d.weights=t,n.length>0&&(d.extras={},d.extras.targetNames=n)}const g=Array.isArray(e.material);if(g&&0===f.groups.length)return null;let b=!1;if(g&&null===f.index){const t=[];for(let e=0,n=f.attributes.position.count;e<n;e++)t[e]=e;f.setIndex(t),b=!0}const E=g?e.material:[e.material],M=g?f.groups:[{materialIndex:0,start:void 0,count:void 0}];for(let t=0,e=M.length;t<e;t++){const e={mode:w,attributes:y};if(this.serializeUserData(f,e),p.length>0&&(e.targets=p),null!==f.index){let n=this.getUID(f.index);void 0===M[t].start&&void 0===M[t].count||(n+=":"+M[t].start+":"+M[t].count),c.attributes.has(n)?e.indices=c.attributes.get(n):(e.indices=this.processAccessor(f.index,f,M[t].start,M[t].count),c.attributes.set(n,e.indices)),null===e.indices&&delete e.indices}const n=await this.processMaterialAsync(E[M[t].materialIndex]);null!==n&&(e.material=n),m.push(e)}!0===b&&f.setIndex(null),d.primitives=m,h.meshes||(h.meshes=[]),await this.i(function(t){t.writeMesh&&t.writeMesh(e,d)});const _=h.meshes.push(d)-1;return c.meshes.set(l,_),_}detectMeshQuantization(t,n){if(this.extensionsUsed[v])return;let s;switch(n.array.constructor){case Int8Array:s="byte";break;case Uint8Array:s="unsigned byte";break;case Int16Array:s="short";break;case Uint16Array:s="unsigned short";break;default:return}n.normalized&&(s+=" normalized");const i=t.split("_",1)[0];e[i]&&e[i].includes(s)&&(this.extensionsUsed[v]=!0,this.extensionsRequired[v]=!0)}processCamera(e){const n=this.json;n.cameras||(n.cameras=[]);const s=e.isOrthographicCamera,i={type:s?"orthographic":"perspective"};return s?i.orthographic={xmag:2*e.right,ymag:2*e.top,zfar:e.far<=0?.001:e.far,znear:e.near<0?0:e.near}:i.perspective={aspectRatio:e.aspect,yfov:t.MathUtils.degToRad(e.fov),zfar:e.far<=0?.001:e.far,znear:e.near<0?0:e.near},""!==e.name&&(i.name=e.type),n.cameras.push(i)-1}processAnimation(e,s){const i=this.json,r=this.nodeMap;i.animations||(i.animations=[]);const o=(e=n.Utils.mergeMorphTargetTracks(e.clone(),s)).tracks,a=[],c=[];for(let e=0;e<o.length;++e){const n=o[e],i=t.PropertyBinding.parseTrackName(n.name);let h=t.PropertyBinding.findNode(s,i.nodeName);const u=C[i.propertyName];if("bones"===i.objectName&&(h=!0===h.isSkinnedMesh?h.skeleton.getBoneByName(i.objectIndex):void 0),!h||!u)continue;const l=1;let f,w=n.values.length/n.times.length;u===C.morphTargetInfluences&&(w/=h.morphTargetInfluences.length),!0===n.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline?(f="CUBICSPLINE",w/=3):f=n.getInterpolation()===t.InterpolateDiscrete?"STEP":"LINEAR",c.push({input:this.processAccessor(new t.BufferAttribute(n.times,l)),output:this.processAccessor(new t.BufferAttribute(n.values,w)),interpolation:f}),a.push({sampler:c.length-1,target:{node:r.get(h),path:u}})}return i.animations.push({name:e.name||"clip_"+i.animations.length,samplers:c,channels:a}),i.animations.length-1}processSkin(e){const n=this.json,s=this.nodeMap,i=n.nodes[s.get(e)],r=e.skeleton;if(void 0===r)return null;const o=e.skeleton.bones[0];if(void 0===o)return null;const a=[],c=new Float32Array(16*r.bones.length),h=new t.Matrix4;for(let t=0;t<r.bones.length;++t)a.push(s.get(r.bones[t])),h.copy(r.boneInverses[t]),h.multiply(e.bindMatrix).toArray(c,16*t);void 0===n.skins&&(n.skins=[]),n.skins.push({inverseBindMatrices:this.processAccessor(new t.BufferAttribute(c,16)),joints:a,skeleton:s.get(o)});return i.skin=n.skins.length-1}async processNodeAsync(t){const e=this.json,n=this.options,s=this.nodeMap;e.nodes||(e.nodes=[]);const i={};if(n.trs){const e=t.quaternion.toArray(),n=t.position.toArray(),s=t.scale.toArray();I(e,[0,0,0,1])||(i.rotation=e),I(n,[0,0,0])||(i.translation=n),I(s,[1,1,1])||(i.scale=s)}else t.matrixAutoUpdate&&t.updateMatrix(),!1===I(t.matrix.elements,[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1])&&(i.matrix=t.matrix.elements);if(""!==t.name&&(i.name=String(t.name)),this.serializeUserData(t,i),t.isMesh||t.isLine||t.isPoints){const e=await this.processMeshAsync(t);null!==e&&(i.mesh=e)}else t.isCamera&&(i.camera=this.processCamera(t));t.isSkinnedMesh&&this.skins.push(t);const r=e.nodes.push(i)-1;if(s.set(t,r),t.children.length>0){const e=[];for(let s=0,i=t.children.length;s<i;s++){const i=t.children[s];if(i.visible||!1===n.onlyVisible){const t=await this.processNodeAsync(i);null!==t&&e.push(t)}}e.length>0&&(i.children=e)}return await this.i(function(e){e.writeNode&&e.writeNode(t,i)}),r}async processSceneAsync(t){const e=this.json,n=this.options;e.scenes||(e.scenes=[],e.scene=0);const s={};""!==t.name&&(s.name=t.name),e.scenes.push(s);const i=[];for(let e=0,s=t.children.length;e<s;e++){const s=t.children[e];if(s.visible||!1===n.onlyVisible){const t=await this.processNodeAsync(s);null!==t&&i.push(t)}}i.length>0&&(s.nodes=i),this.serializeUserData(t,s)}async processObjectsAsync(e){const n=new t.Scene;n.name="AuxScene";for(let t=0;t<e.length;t++)n.children.push(e[t]);await this.processSceneAsync(n)}async processInputAsync(e){const n=this.options;e=e instanceof Array?e:[e],await this.i(function(t){t.beforeParse&&t.beforeParse(e)});const s=[];for(let n=0;n<e.length;n++)e[n]instanceof t.Scene?await this.processSceneAsync(e[n]):s.push(e[n]);s.length>0&&await this.processObjectsAsync(s);for(let t=0;t<this.skins.length;++t)this.processSkin(this.skins[t]);for(let t=0;t<n.animations.length;++t)this.processAnimation(n.animations[t],e[0]);await this.i(function(t){t.afterParse&&t.afterParse(e)})}async i(t){for(let e=0,n=this.plugins.length;e<n;e++)await t(this.plugins[e])}}class F{constructor(t){this.writer=t,this.name="KHR_lights_punctual"}writeNode(t,e){if(!t.isLight)return;if(!t.isDirectionalLight&&!t.isPointLight&&!t.isSpotLight)return;const n=this.writer,s=n.json,i=n.extensionsUsed,r={};t.name&&(r.name=t.name),r.color=t.color.toArray(),r.intensity=t.intensity,t.isDirectionalLight?r.type="directional":t.isPointLight?(r.type="point",t.distance>0&&(r.range=t.distance)):t.isSpotLight&&(r.type="spot",t.distance>0&&(r.range=t.distance),r.spot={},r.spot.innerConeAngle=(1-t.penumbra)*t.angle,r.spot.outerConeAngle=t.angle),void 0!==t.decay&&t.decay,t.target&&(t.target.parent!==t||0!==t.target.position.x||0!==t.target.position.y||t.target.position.z),i[this.name]||(s.extensions=s.extensions||{},s.extensions[this.name]={lights:[]},i[this.name]=!0);const o=s.extensions[this.name].lights;o.push(r),e.extensions=e.extensions||{},e.extensions[this.name]={light:o.length-1}}}class z{constructor(t){this.writer=t,this.name="KHR_materials_unlit"}async writeMaterialAsync(t,e){if(!t.isMeshBasicMaterial)return;const n=this.writer.extensionsUsed;e.extensions=e.extensions||{},e.extensions[this.name]={},n[this.name]=!0,e.pbrMetallicRoughness.metallicFactor=0,e.pbrMetallicRoughness.roughnessFactor=.9}}class U{constructor(t){this.writer=t,this.name="KHR_materials_clearcoat"}async writeMaterialAsync(t,e){if(!t.isMeshPhysicalMaterial||0===t.clearcoat)return;const n=this.writer,s=n.extensionsUsed,i={};if(i.clearcoatFactor=t.clearcoat,t.clearcoatMap){const e={index:await n.processTextureAsync(t.clearcoatMap),texCoord:t.clearcoatMap.channel};n.applyTextureTransform(e,t.clearcoatMap),i.clearcoatTexture=e}if(i.clearcoatRoughnessFactor=t.clearcoatRoughness,t.clearcoatRoughnessMap){const e={index:await n.processTextureAsync(t.clearcoatRoughnessMap),texCoord:t.clearcoatRoughnessMap.channel};n.applyTextureTransform(e,t.clearcoatRoughnessMap),i.clearcoatRoughnessTexture=e}if(t.clearcoatNormalMap){const e={index:await n.processTextureAsync(t.clearcoatNormalMap),texCoord:t.clearcoatNormalMap.channel};1!==t.clearcoatNormalScale.x&&(e.scale=t.clearcoatNormalScale.x),n.applyTextureTransform(e,t.clearcoatNormalMap),i.clearcoatNormalTexture=e}e.extensions=e.extensions||{},e.extensions[this.name]=i,s[this.name]=!0}}class B{constructor(t){this.writer=t,this.name="KHR_materials_dispersion"}async writeMaterialAsync(t,e){if(!t.isMeshPhysicalMaterial||0===t.dispersion)return;const n=this.writer.extensionsUsed,s={};s.dispersion=t.dispersion,e.extensions=e.extensions||{},e.extensions[this.name]=s,n[this.name]=!0}}class K{constructor(t){this.writer=t,this.name="KHR_materials_iridescence"}async writeMaterialAsync(t,e){if(!t.isMeshPhysicalMaterial||0===t.iridescence)return;const n=this.writer,s=n.extensionsUsed,i={};if(i.iridescenceFactor=t.iridescence,t.iridescenceMap){const e={index:await n.processTextureAsync(t.iridescenceMap),texCoord:t.iridescenceMap.channel};n.applyTextureTransform(e,t.iridescenceMap),i.iridescenceTexture=e}if(i.iridescenceIor=t.iridescenceIOR,i.iridescenceThicknessMinimum=t.iridescenceThicknessRange[0],i.iridescenceThicknessMaximum=t.iridescenceThicknessRange[1],t.iridescenceThicknessMap){const e={index:await n.processTextureAsync(t.iridescenceThicknessMap),texCoord:t.iridescenceThicknessMap.channel};n.applyTextureTransform(e,t.iridescenceThicknessMap),i.iridescenceThicknessTexture=e}e.extensions=e.extensions||{},e.extensions[this.name]=i,s[this.name]=!0}}class k{constructor(t){this.writer=t,this.name="KHR_materials_transmission"}async writeMaterialAsync(t,e){if(!t.isMeshPhysicalMaterial||0===t.transmission)return;const n=this.writer,s=n.extensionsUsed,i={};if(i.transmissionFactor=t.transmission,t.transmissionMap){const e={index:await n.processTextureAsync(t.transmissionMap),texCoord:t.transmissionMap.channel};n.applyTextureTransform(e,t.transmissionMap),i.transmissionTexture=e}e.extensions=e.extensions||{},e.extensions[this.name]=i,s[this.name]=!0}}class D{constructor(t){this.writer=t,this.name="KHR_materials_volume"}async writeMaterialAsync(t,e){if(!t.isMeshPhysicalMaterial||0===t.transmission)return;const n=this.writer,s=n.extensionsUsed,i={};if(i.thicknessFactor=t.thickness,t.thicknessMap){const e={index:await n.processTextureAsync(t.thicknessMap),texCoord:t.thicknessMap.channel};n.applyTextureTransform(e,t.thicknessMap),i.thicknessTexture=e}t.attenuationDistance!==1/0&&(i.attenuationDistance=t.attenuationDistance),i.attenuationColor=t.attenuationColor.toArray(),e.extensions=e.extensions||{},e.extensions[this.name]=i,s[this.name]=!0}}class G{constructor(t){this.writer=t,this.name="KHR_materials_ior"}async writeMaterialAsync(t,e){if(!t.isMeshPhysicalMaterial||1.5===t.ior)return;const n=this.writer.extensionsUsed,s={};s.ior=t.ior,e.extensions=e.extensions||{},e.extensions[this.name]=s,n[this.name]=!0}}class V{constructor(t){this.writer=t,this.name="KHR_materials_specular"}async writeMaterialAsync(t,e){if(!t.isMeshPhysicalMaterial||1===t.specularIntensity&&t.specularColor.equals(R)&&!t.specularIntensityMap&&!t.specularColorMap)return;const n=this.writer,s=n.extensionsUsed,i={};if(t.specularIntensityMap){const e={index:await n.processTextureAsync(t.specularIntensityMap),texCoord:t.specularIntensityMap.channel};n.applyTextureTransform(e,t.specularIntensityMap),i.specularTexture=e}if(t.specularColorMap){const e={index:await n.processTextureAsync(t.specularColorMap),texCoord:t.specularColorMap.channel};n.applyTextureTransform(e,t.specularColorMap),i.specularColorTexture=e}i.specularFactor=t.specularIntensity,i.specularColorFactor=t.specularColor.toArray(),e.extensions=e.extensions||{},e.extensions[this.name]=i,s[this.name]=!0}}class j{constructor(t){this.writer=t,this.name="KHR_materials_sheen"}async writeMaterialAsync(t,e){if(!t.isMeshPhysicalMaterial||0==t.sheen)return;const n=this.writer,s=n.extensionsUsed,i={};if(t.sheenRoughnessMap){const e={index:await n.processTextureAsync(t.sheenRoughnessMap),texCoord:t.sheenRoughnessMap.channel};n.applyTextureTransform(e,t.sheenRoughnessMap),i.sheenRoughnessTexture=e}if(t.sheenColorMap){const e={index:await n.processTextureAsync(t.sheenColorMap),texCoord:t.sheenColorMap.channel};n.applyTextureTransform(e,t.sheenColorMap),i.sheenColorTexture=e}i.sheenRoughnessFactor=t.sheenRoughness,i.sheenColorFactor=t.sheenColor.toArray(),e.extensions=e.extensions||{},e.extensions[this.name]=i,s[this.name]=!0}}class P{constructor(t){this.writer=t,this.name="KHR_materials_anisotropy"}async writeMaterialAsync(t,e){if(!t.isMeshPhysicalMaterial||0==t.anisotropy)return;const n=this.writer,s=n.extensionsUsed,i={};if(t.anisotropyMap){const e={index:await n.processTextureAsync(t.anisotropyMap)};n.applyTextureTransform(e,t.anisotropyMap),i.anisotropyTexture=e}i.anisotropyStrength=t.anisotropy,i.anisotropyRotation=t.anisotropyRotation,e.extensions=e.extensions||{},e.extensions[this.name]=i,s[this.name]=!0}}class X{constructor(t){this.writer=t,this.name="KHR_materials_emissive_strength"}async writeMaterialAsync(t,e){if(!t.isMeshStandardMaterial||1===t.emissiveIntensity)return;const n=this.writer.extensionsUsed,s={};s.emissiveStrength=t.emissiveIntensity,e.extensions=e.extensions||{},e.extensions[this.name]=s,n[this.name]=!0}}class q{constructor(t){this.writer=t,this.name="EXT_materials_bump"}async writeMaterialAsync(t,e){if(!t.isMeshStandardMaterial||1===t.bumpScale&&!t.bumpMap)return;const n=this.writer,s=n.extensionsUsed,i={};if(t.bumpMap){const e={index:await n.processTextureAsync(t.bumpMap),texCoord:t.bumpMap.channel};n.applyTextureTransform(e,t.bumpMap),i.bumpTexture=e}i.bumpFactor=t.bumpScale,e.extensions=e.extensions||{},e.extensions[this.name]=i,s[this.name]=!0}}class J{constructor(t){this.writer=t,this.name="EXT_mesh_gpu_instancing"}writeNode(e,n){if(!e.isInstancedMesh)return;const s=this.writer,i=e,r=new Float32Array(3*i.count),o=new Float32Array(4*i.count),a=new Float32Array(3*i.count),c=new t.Matrix4,h=new t.Vector3,u=new t.Quaternion,l=new t.Vector3;for(let t=0;t<i.count;t++)i.getMatrixAt(t,c),c.decompose(h,u,l),h.toArray(r,3*t),u.toArray(o,4*t),l.toArray(a,3*t);const f={TRANSLATION:s.processAccessor(new t.BufferAttribute(r,3)),ROTATION:s.processAccessor(new t.BufferAttribute(o,4)),SCALE:s.processAccessor(new t.BufferAttribute(a,3))};i.instanceColor&&(f.o=s.processAccessor(i.instanceColor)),n.extensions=n.extensions||{},n.extensions[this.name]={attributes:f},s.extensionsUsed[this.name]=!0,s.extensionsRequired[this.name]=!0}}n.Utils={insertKeyframe:function(t,e){const n=.001,s=t.getValueSize(),i=new t.TimeBufferType(t.times.length+1),r=new t.ValueBufferType(t.values.length+s),o=t.createInterpolant(new t.ValueBufferType(s));let a;if(0===t.times.length){i[0]=e;for(let t=0;t<s;t++)r[t]=0;a=0}else if(e<t.times[0]){if(Math.abs(t.times[0]-e)<n)return 0;i[0]=e,i.set(t.times,1),r.set(o.evaluate(e),0),r.set(t.values,s),a=0}else if(e>t.times[t.times.length-1]){if(Math.abs(t.times[t.times.length-1]-e)<n)return t.times.length-1;i[i.length-1]=e,i.set(t.times,0),r.set(t.values,0),r.set(o.evaluate(e),t.values.length),a=i.length-1}else for(let c=0;c<t.times.length;c++){if(Math.abs(t.times[c]-e)<n)return c;if(t.times[c]<e&&t.times[c+1]>e){i.set(t.times.slice(0,c+1),0),i[c+1]=e,i.set(t.times.slice(c+1),c+2),r.set(t.values.slice(0,(c+1)*s),0),r.set(o.evaluate(e),(c+1)*s),r.set(t.values.slice((c+1)*s),(c+2)*s),a=c+1;break}}return t.times=i,t.values=r,a},mergeMorphTargetTracks:function(e,n){const s=[],i={},r=e.tracks;for(let e=0;e<r.length;++e){let o=r[e];const a=t.PropertyBinding.parseTrackName(o.name),c=t.PropertyBinding.findNode(n,a.nodeName);if("morphTargetInfluences"!==a.propertyName||void 0===a.propertyIndex){s.push(o);continue}if(o.createInterpolant!==o.InterpolantFactoryMethodDiscrete&&o.createInterpolant!==o.InterpolantFactoryMethodLinear){if(o.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline)throw new Error("THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.");o=o.clone(),o.setInterpolation(t.InterpolateLinear)}const h=c.morphTargetInfluences.length,u=c.morphTargetDictionary[a.propertyIndex];if(void 0===u)throw new Error("THREE.GLTFExporter: Morph target name not found: "+a.propertyIndex);let l;if(void 0===i[c.uuid]){l=o.clone();const t=new l.ValueBufferType(h*l.times.length);for(let e=0;e<l.times.length;e++)t[e*h+u]=l.values[e];l.name=(a.nodeName||"")+".morphTargetInfluences",l.values=t,i[c.uuid]=l,s.push(l);continue}const f=o.createInterpolant(new o.ValueBufferType(1));l=i[c.uuid];for(let t=0;t<l.times.length;t++)l.values[t*h+u]=f.evaluate(l.times[t]);for(let t=0;t<o.times.length;t++){const e=this.insertKeyframe(l,o.times[t]);l.values[e*h+u]=o.values[t]}}return e.tracks=s,e},toFloat32BufferAttribute:function(e){const n=new t.BufferAttribute(new Float32Array(e.count*e.itemSize),e.itemSize,!1);if(!e.normalized&&!e.isInterleavedBufferAttribute)return n.array.set(e.array),n;for(let t=0,s=e.count;t<s;t++)for(let s=0;s<e.itemSize;s++)n.setComponent(t,s,e.getComponent(t,s));return n}},exports.GLTFExporter=n;
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var THREE = require('three');
6
+
7
+ /**
8
+ * The KHR_mesh_quantization extension allows these extra attribute component types
9
+ *
10
+ * @see https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md#extending-mesh-attributes
11
+ */
12
+ const KHR_mesh_quantization_ExtraAttrTypes = {
13
+ POSITION: [
14
+ 'byte',
15
+ 'byte normalized',
16
+ 'unsigned byte',
17
+ 'unsigned byte normalized',
18
+ 'short',
19
+ 'short normalized',
20
+ 'unsigned short',
21
+ 'unsigned short normalized',
22
+ ],
23
+ NORMAL: [
24
+ 'byte normalized',
25
+ 'short normalized',
26
+ ],
27
+ TANGENT: [
28
+ 'byte normalized',
29
+ 'short normalized',
30
+ ],
31
+ TEXCOORD: [
32
+ 'byte',
33
+ 'byte normalized',
34
+ 'unsigned byte',
35
+ 'short',
36
+ 'short normalized',
37
+ 'unsigned short',
38
+ ],
39
+ };
40
+
41
+ /**
42
+ * An exporter for `glTF` 2.0.
43
+ *
44
+ * glTF (GL Transmission Format) is an [open format specification]{@link https://github.com/KhronosGroup/glTF/tree/master/specification/2.0}
45
+ * for efficient delivery and loading of 3D content. Assets may be provided either in JSON (.gltf)
46
+ * or binary (.glb) format. External files store textures (.jpg, .png) and additional binary
47
+ * data (.bin). A glTF asset may deliver one or more scenes, including meshes, materials,
48
+ * textures, skins, skeletons, morph targets, animations, lights, and/or cameras.
49
+ *
50
+ * GLTFExporter supports the [glTF 2.0 extensions]{@link https://github.com/KhronosGroup/glTF/tree/master/extensions/}:
51
+ *
52
+ * - KHR_lights_punctual
53
+ * - KHR_materials_clearcoat
54
+ * - KHR_materials_dispersion
55
+ * - KHR_materials_emissive_strength
56
+ * - KHR_materials_ior
57
+ * - KHR_materials_iridescence
58
+ * - KHR_materials_specular
59
+ * - KHR_materials_sheen
60
+ * - KHR_materials_transmission
61
+ * - KHR_materials_unlit
62
+ * - KHR_materials_volume
63
+ * - KHR_mesh_quantization
64
+ * - KHR_texture_transform
65
+ * - EXT_materials_bump
66
+ * - EXT_mesh_gpu_instancing
67
+ *
68
+ * The following glTF 2.0 extension is supported by an external user plugin:
69
+ *
70
+ * - [KHR_materials_variants]{@link https://github.com/takahirox/three-gltf-extensions}
71
+ *
72
+ * ```js
73
+ * const exporter = new GLTFExporter();
74
+ * const data = await exporter.parseAsync( scene, options );
75
+ * ```
76
+ *
77
+ * @three_import import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js';
78
+ */
79
+ class GLTFExporter {
80
+
81
+ /**
82
+ * Constructs a new glTF exporter.
83
+ */
84
+ constructor() {
85
+
86
+ /**
87
+ * A reference to a texture utils module.
88
+ *
89
+ * @type {?(WebGLTextureUtils|WebGPUTextureUtils)}
90
+ * @default null
91
+ */
92
+ this.textureUtils = null;
93
+
94
+ this.pluginCallbacks = [];
95
+
96
+ this.register( function ( writer ) {
97
+
98
+ return new GLTFLightExtension( writer );
99
+
100
+ } );
101
+
102
+ this.register( function ( writer ) {
103
+
104
+ return new GLTFMaterialsUnlitExtension( writer );
105
+
106
+ } );
107
+
108
+ this.register( function ( writer ) {
109
+
110
+ return new GLTFMaterialsTransmissionExtension( writer );
111
+
112
+ } );
113
+
114
+ this.register( function ( writer ) {
115
+
116
+ return new GLTFMaterialsVolumeExtension( writer );
117
+
118
+ } );
119
+
120
+ this.register( function ( writer ) {
121
+
122
+ return new GLTFMaterialsIorExtension( writer );
123
+
124
+ } );
125
+
126
+ this.register( function ( writer ) {
127
+
128
+ return new GLTFMaterialsSpecularExtension( writer );
129
+
130
+ } );
131
+
132
+ this.register( function ( writer ) {
133
+
134
+ return new GLTFMaterialsClearcoatExtension( writer );
135
+
136
+ } );
137
+
138
+ this.register( function ( writer ) {
139
+
140
+ return new GLTFMaterialsDispersionExtension( writer );
141
+
142
+ } );
143
+
144
+ this.register( function ( writer ) {
145
+
146
+ return new GLTFMaterialsIridescenceExtension( writer );
147
+
148
+ } );
149
+
150
+ this.register( function ( writer ) {
151
+
152
+ return new GLTFMaterialsSheenExtension( writer );
153
+
154
+ } );
155
+
156
+ this.register( function ( writer ) {
157
+
158
+ return new GLTFMaterialsAnisotropyExtension( writer );
159
+
160
+ } );
161
+
162
+ this.register( function ( writer ) {
163
+
164
+ return new GLTFMaterialsEmissiveStrengthExtension( writer );
165
+
166
+ } );
167
+
168
+ this.register( function ( writer ) {
169
+
170
+ return new GLTFMaterialsBumpExtension( writer );
171
+
172
+ } );
173
+
174
+ this.register( function ( writer ) {
175
+
176
+ return new GLTFMeshGpuInstancing( writer );
177
+
178
+ } );
179
+
180
+ }
181
+
182
+ /**
183
+ * Registers a plugin callback. This API is internally used to implement the various
184
+ * glTF extensions but can also used by third-party code to add additional logic
185
+ * to the exporter.
186
+ *
187
+ * @param {function(writer:GLTFWriter)} callback - The callback function to register.
188
+ * @return {GLTFExporter} A reference to this exporter.
189
+ */
190
+ register( callback ) {
191
+
192
+ if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) {
193
+
194
+ this.pluginCallbacks.push( callback );
195
+
196
+ }
197
+
198
+ return this;
199
+
200
+ }
201
+
202
+ /**
203
+ * Unregisters a plugin callback.
204
+ *
205
+ * @param {Function} callback - The callback function to unregister.
206
+ * @return {GLTFExporter} A reference to this exporter.
207
+ */
208
+ unregister( callback ) {
209
+
210
+ if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) {
211
+
212
+ this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 );
213
+
214
+ }
215
+
216
+ return this;
217
+
218
+ }
219
+
220
+ /**
221
+ * Sets the texture utils for this exporter. Only relevant when compressed textures have to be exported.
222
+ *
223
+ * Depending on whether you use {@link WebGLRenderer} or {@link WebGPURenderer}, you must inject the
224
+ * corresponding texture utils {@link WebGLTextureUtils} or {@link WebGPUTextureUtils}.
225
+ *
226
+ * @param {WebGLTextureUtils|WebGPUTextureUtils} utils - The texture utils.
227
+ * @return {GLTFExporter} A reference to this exporter.
228
+ */
229
+ setTextureUtils( utils ) {
230
+
231
+ this.textureUtils = utils;
232
+
233
+ return this;
234
+
235
+ }
236
+
237
+ /**
238
+ * Parses the given scenes and generates the glTF output.
239
+ *
240
+ * @param {Scene|Array<Scene>} input - A scene or an array of scenes.
241
+ * @param {GLTFExporter~OnDone} onDone - A callback function that is executed when the export has finished.
242
+ * @param {GLTFExporter~OnError} onError - A callback function that is executed when an error happens.
243
+ * @param {GLTFExporter~Options} options - options
244
+ */
245
+ parse( input, onDone, onError, options ) {
246
+
247
+ const writer = new GLTFWriter();
248
+ const plugins = [];
249
+
250
+ for ( let i = 0, il = this.pluginCallbacks.length; i < il; i ++ ) {
251
+
252
+ plugins.push( this.pluginCallbacks[ i ]( writer ) );
253
+
254
+ }
255
+
256
+ writer.setPlugins( plugins );
257
+ writer.setTextureUtils( this.textureUtils );
258
+ writer.writeAsync( input, onDone, options ).catch( onError );
259
+
260
+ }
261
+
262
+ /**
263
+ * Async version of {@link GLTFExporter#parse}.
264
+ *
265
+ * @param {Scene|Array<Scene>} input - A scene or an array of scenes.
266
+ * @param {GLTFExporter~Options} options - options.
267
+ * @return {Promise<ArrayBuffer|string>} A Promise that resolved with the exported glTF data.
268
+ */
269
+ parseAsync( input, options ) {
270
+
271
+ const scope = this;
272
+
273
+ return new Promise( function ( resolve, reject ) {
274
+
275
+ scope.parse( input, resolve, reject, options );
276
+
277
+ } );
278
+
279
+ }
280
+
281
+ }
282
+
283
+ //------------------------------------------------------------------------------
284
+ // Constants
285
+ //------------------------------------------------------------------------------
286
+
287
+ const WEBGL_CONSTANTS = {
288
+ POINTS: 0x0000,
289
+ LINES: 0x0001,
290
+ LINE_LOOP: 0x0002,
291
+ LINE_STRIP: 0x0003,
292
+ TRIANGLES: 0x0004,
293
+ TRIANGLE_STRIP: 0x0005,
294
+ TRIANGLE_FAN: 0x0006,
295
+
296
+ BYTE: 0x1400,
297
+ UNSIGNED_BYTE: 0x1401,
298
+ SHORT: 0x1402,
299
+ UNSIGNED_SHORT: 0x1403,
300
+ INT: 0x1404,
301
+ UNSIGNED_INT: 0x1405,
302
+ FLOAT: 0x1406,
303
+
304
+ ARRAY_BUFFER: 0x8892,
305
+ ELEMENT_ARRAY_BUFFER: 0x8893,
306
+
307
+ NEAREST: 0x2600,
308
+ LINEAR: 0x2601,
309
+ NEAREST_MIPMAP_NEAREST: 0x2700,
310
+ LINEAR_MIPMAP_NEAREST: 0x2701,
311
+ NEAREST_MIPMAP_LINEAR: 0x2702,
312
+ LINEAR_MIPMAP_LINEAR: 0x2703,
313
+
314
+ CLAMP_TO_EDGE: 33071,
315
+ MIRRORED_REPEAT: 33648,
316
+ REPEAT: 10497
317
+ };
318
+
319
+ const KHR_MESH_QUANTIZATION = 'KHR_mesh_quantization';
320
+
321
+ const THREE_TO_WEBGL = {};
322
+
323
+ THREE_TO_WEBGL[ THREE.NearestFilter ] = WEBGL_CONSTANTS.NEAREST;
324
+ THREE_TO_WEBGL[ THREE.NearestMipmapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST;
325
+ THREE_TO_WEBGL[ THREE.NearestMipmapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR;
326
+ THREE_TO_WEBGL[ THREE.LinearFilter ] = WEBGL_CONSTANTS.LINEAR;
327
+ THREE_TO_WEBGL[ THREE.LinearMipmapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST;
328
+ THREE_TO_WEBGL[ THREE.LinearMipmapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR;
329
+
330
+ THREE_TO_WEBGL[ THREE.ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE;
331
+ THREE_TO_WEBGL[ THREE.RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT;
332
+ THREE_TO_WEBGL[ THREE.MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT;
333
+
334
+ const PATH_PROPERTIES = {
335
+ scale: 'scale',
336
+ position: 'translation',
337
+ quaternion: 'rotation',
338
+ morphTargetInfluences: 'weights'
339
+ };
340
+
341
+ const DEFAULT_SPECULAR_COLOR = new THREE.Color();
342
+
343
+ // GLB constants
344
+ // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification
345
+
346
+ const GLB_HEADER_BYTES = 12;
347
+ const GLB_HEADER_MAGIC = 0x46546C67;
348
+ const GLB_VERSION = 2;
349
+
350
+ const GLB_CHUNK_PREFIX_BYTES = 8;
351
+ const GLB_CHUNK_TYPE_JSON = 0x4E4F534A;
352
+ const GLB_CHUNK_TYPE_BIN = 0x004E4942;
353
+
354
+ //------------------------------------------------------------------------------
355
+ // Utility functions
356
+ //------------------------------------------------------------------------------
357
+
358
+ /**
359
+ * Compare two arrays
360
+ *
361
+ * @private
362
+ * @param {Array} array1 Array 1 to compare
363
+ * @param {Array} array2 Array 2 to compare
364
+ * @return {boolean} Returns true if both arrays are equal
365
+ */
366
+ function equalArray( array1, array2 ) {
367
+
368
+ return ( array1.length === array2.length ) && array1.every( function ( element, index ) {
369
+
370
+ return element === array2[ index ];
371
+
372
+ } );
373
+
374
+ }
375
+
376
+ /**
377
+ * Converts a string to an ArrayBuffer.
378
+ *
379
+ * @private
380
+ * @param {string} text
381
+ * @return {ArrayBuffer}
382
+ */
383
+ function stringToArrayBuffer( text ) {
384
+
385
+ return new TextEncoder().encode( text ).buffer;
386
+
387
+ }
388
+
389
+ /**
390
+ * Is identity matrix
391
+ *
392
+ * @private
393
+ * @param {Matrix4} matrix
394
+ * @returns {boolean} Returns true, if parameter is identity matrix
395
+ */
396
+ function isIdentityMatrix( matrix ) {
397
+
398
+ return equalArray( matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] );
399
+
400
+ }
401
+
402
+ /**
403
+ * Get the min and max vectors from the given attribute
404
+ *
405
+ * @private
406
+ * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count
407
+ * @param {number} start Start index
408
+ * @param {number} count Range to cover
409
+ * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components)
410
+ */
411
+ function getMinMax( attribute, start, count ) {
412
+
413
+ const output = {
414
+
415
+ min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ),
416
+ max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY )
417
+
418
+ };
419
+
420
+ for ( let i = start; i < start + count; i ++ ) {
421
+
422
+ for ( let a = 0; a < attribute.itemSize; a ++ ) {
423
+
424
+ let value;
425
+
426
+ if ( attribute.itemSize > 4 ) {
427
+
428
+ // no support for interleaved data for itemSize > 4
429
+
430
+ value = attribute.array[ i * attribute.itemSize + a ];
431
+
432
+ } else {
433
+
434
+ if ( a === 0 ) value = attribute.getX( i );
435
+ else if ( a === 1 ) value = attribute.getY( i );
436
+ else if ( a === 2 ) value = attribute.getZ( i );
437
+ else if ( a === 3 ) value = attribute.getW( i );
438
+
439
+ if ( attribute.normalized === true ) {
440
+
441
+ value = THREE.MathUtils.normalize( value, attribute.array );
442
+
443
+ }
444
+
445
+ }
446
+
447
+ output.min[ a ] = Math.min( output.min[ a ], value );
448
+ output.max[ a ] = Math.max( output.max[ a ], value );
449
+
450
+ }
451
+
452
+ }
453
+
454
+ return output;
455
+
456
+ }
457
+
458
+ /**
459
+ * Get the required size + padding for a buffer, rounded to the next 4-byte boundary.
460
+ * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment
461
+ *
462
+ * @private
463
+ * @param {number} bufferSize The size the original buffer. Should be an integer.
464
+ * @returns {number} new buffer size with required padding as an integer.
465
+ *
466
+ */
467
+ function getPaddedBufferSize( bufferSize ) {
468
+
469
+ return Math.ceil( bufferSize / 4 ) * 4;
470
+
471
+ }
472
+
473
+ /**
474
+ * Returns a buffer aligned to 4-byte boundary.
475
+ *
476
+ * @private
477
+ * @param {ArrayBuffer} arrayBuffer Buffer to pad
478
+ * @param {number} [paddingByte=0] Should be an integer
479
+ * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer
480
+ */
481
+ function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) {
482
+
483
+ const paddedLength = getPaddedBufferSize( arrayBuffer.byteLength );
484
+
485
+ if ( paddedLength !== arrayBuffer.byteLength ) {
486
+
487
+ const array = new Uint8Array( paddedLength );
488
+ array.set( new Uint8Array( arrayBuffer ) );
489
+
490
+ if ( paddingByte !== 0 ) {
491
+
492
+ for ( let i = arrayBuffer.byteLength; i < paddedLength; i ++ ) {
493
+
494
+ array[ i ] = paddingByte;
495
+
496
+ }
497
+
498
+ }
499
+
500
+ return array.buffer;
501
+
502
+ }
503
+
504
+ return arrayBuffer;
505
+
506
+ }
507
+
508
+ function getCanvas() {
509
+
510
+ if ( typeof document === 'undefined' && typeof OffscreenCanvas !== 'undefined' ) {
511
+
512
+ return new OffscreenCanvas( 1, 1 );
513
+
514
+ }
515
+
516
+ return document.createElement( 'canvas' );
517
+
518
+ }
519
+
520
+ function getToBlobPromise( canvas, mimeType ) {
521
+
522
+ if ( canvas.toBlob !== undefined ) {
523
+
524
+ return new Promise( ( resolve ) => canvas.toBlob( resolve, mimeType ) );
525
+
526
+ }
527
+
528
+ let quality;
529
+
530
+ // Blink's implementation of convertToBlob seems to default to a quality level of 100%
531
+ // Use the Blink default quality levels of toBlob instead so that file sizes are comparable.
532
+ if ( mimeType === 'image/jpeg' ) {
533
+
534
+ quality = 0.92;
535
+
536
+ } else if ( mimeType === 'image/webp' ) {
537
+
538
+ quality = 0.8;
539
+
540
+ }
541
+
542
+ return canvas.convertToBlob( {
543
+
544
+ type: mimeType,
545
+ quality: quality
546
+
547
+ } );
548
+
549
+ }
550
+
551
+ /**
552
+ * Writer
553
+ *
554
+ * @private
555
+ */
556
+ class GLTFWriter {
557
+
558
+ constructor() {
559
+
560
+ this.plugins = [];
561
+
562
+ this.options = {};
563
+ this.pending = [];
564
+ this.buffers = [];
565
+
566
+ this.byteOffset = 0;
567
+ this.buffers = [];
568
+ this.nodeMap = new Map();
569
+ this.skins = [];
570
+
571
+ this.extensionsUsed = {};
572
+ this.extensionsRequired = {};
573
+
574
+ this.uids = new Map();
575
+ this.uid = 0;
576
+
577
+ this.json = {
578
+ asset: {
579
+ version: '2.0',
580
+ generator: 'THREE.GLTFExporter r' + THREE.REVISION
581
+ }
582
+ };
583
+
584
+ this.cache = {
585
+ meshes: new Map(),
586
+ attributes: new Map(),
587
+ attributesNormalized: new Map(),
588
+ materials: new Map(),
589
+ textures: new Map(),
590
+ images: new Map()
591
+ };
592
+
593
+ this.textureUtils = null;
594
+
595
+ }
596
+
597
+ setPlugins( plugins ) {
598
+
599
+ this.plugins = plugins;
600
+
601
+ }
602
+
603
+ setTextureUtils( utils ) {
604
+
605
+ this.textureUtils = utils;
606
+
607
+ }
608
+
609
+ /**
610
+ * Parse scenes and generate GLTF output
611
+ *
612
+ * @param {Scene|Array<Scene>} input Scene or Array of THREE.Scenes
613
+ * @param {Function} onDone Callback on completed
614
+ * @param {Object} options options
615
+ */
616
+ async writeAsync( input, onDone, options = {} ) {
617
+
618
+ this.options = Object.assign( {
619
+ // default options
620
+ binary: false,
621
+ trs: false,
622
+ onlyVisible: true,
623
+ maxTextureSize: Infinity,
624
+ animations: [],
625
+ includeCustomExtensions: false
626
+ }, options );
627
+
628
+ if ( this.options.animations.length > 0 ) {
629
+
630
+ // Only TRS properties, and not matrices, may be targeted by animation.
631
+ this.options.trs = true;
632
+
633
+ }
634
+
635
+ await this.processInputAsync( input );
636
+
637
+ await Promise.all( this.pending );
638
+
639
+ const writer = this;
640
+ const buffers = writer.buffers;
641
+ const json = writer.json;
642
+ options = writer.options;
643
+
644
+ const extensionsUsed = writer.extensionsUsed;
645
+ const extensionsRequired = writer.extensionsRequired;
646
+
647
+ // Merge buffers.
648
+ const blob = new Blob( buffers, { type: 'application/octet-stream' } );
649
+
650
+ // Declare extensions.
651
+ const extensionsUsedList = Object.keys( extensionsUsed );
652
+ const extensionsRequiredList = Object.keys( extensionsRequired );
653
+
654
+ if ( extensionsUsedList.length > 0 ) json.extensionsUsed = extensionsUsedList;
655
+ if ( extensionsRequiredList.length > 0 ) json.extensionsRequired = extensionsRequiredList;
656
+
657
+ // Update bytelength of the single buffer.
658
+ if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size;
659
+
660
+ if ( options.binary === true ) {
661
+
662
+ // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification
663
+
664
+ const reader = new FileReader();
665
+ reader.readAsArrayBuffer( blob );
666
+ reader.onloadend = function () {
667
+
668
+ // Binary chunk.
669
+ const binaryChunk = getPaddedArrayBuffer( reader.result );
670
+ const binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) );
671
+ binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true );
672
+ binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true );
673
+
674
+ // JSON chunk.
675
+ const jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( json ) ), 0x20 );
676
+ const jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) );
677
+ jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true );
678
+ jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true );
679
+
680
+ // GLB header.
681
+ const header = new ArrayBuffer( GLB_HEADER_BYTES );
682
+ const headerView = new DataView( header );
683
+ headerView.setUint32( 0, GLB_HEADER_MAGIC, true );
684
+ headerView.setUint32( 4, GLB_VERSION, true );
685
+ const totalByteLength = GLB_HEADER_BYTES
686
+ + jsonChunkPrefix.byteLength + jsonChunk.byteLength
687
+ + binaryChunkPrefix.byteLength + binaryChunk.byteLength;
688
+ headerView.setUint32( 8, totalByteLength, true );
689
+
690
+ const glbBlob = new Blob( [
691
+ header,
692
+ jsonChunkPrefix,
693
+ jsonChunk,
694
+ binaryChunkPrefix,
695
+ binaryChunk
696
+ ], { type: 'application/octet-stream' } );
697
+
698
+ const glbReader = new FileReader();
699
+ glbReader.readAsArrayBuffer( glbBlob );
700
+ glbReader.onloadend = function () {
701
+
702
+ onDone( glbReader.result );
703
+
704
+ };
705
+
706
+ };
707
+
708
+ } else {
709
+
710
+ if ( json.buffers && json.buffers.length > 0 ) {
711
+
712
+ const reader = new FileReader();
713
+ reader.readAsDataURL( blob );
714
+ reader.onloadend = function () {
715
+
716
+ const base64data = reader.result;
717
+ json.buffers[ 0 ].uri = base64data;
718
+ onDone( json );
719
+
720
+ };
721
+
722
+ } else {
723
+
724
+ onDone( json );
725
+
726
+ }
727
+
728
+ }
729
+
730
+
731
+ }
732
+
733
+ /**
734
+ * Serializes a userData.
735
+ *
736
+ * @param {THREE.Object3D|THREE.Material} object
737
+ * @param {Object} objectDef
738
+ */
739
+ serializeUserData( object, objectDef ) {
740
+
741
+ if ( Object.keys( object.userData ).length === 0 ) return;
742
+
743
+ const options = this.options;
744
+ const extensionsUsed = this.extensionsUsed;
745
+
746
+ try {
747
+
748
+ const json = JSON.parse( JSON.stringify( object.userData ) );
749
+
750
+ if ( options.includeCustomExtensions && json.gltfExtensions ) {
751
+
752
+ if ( objectDef.extensions === undefined ) objectDef.extensions = {};
753
+
754
+ for ( const extensionName in json.gltfExtensions ) {
755
+
756
+ objectDef.extensions[ extensionName ] = json.gltfExtensions[ extensionName ];
757
+ extensionsUsed[ extensionName ] = true;
758
+
759
+ }
760
+
761
+ delete json.gltfExtensions;
762
+
763
+ }
764
+
765
+ if ( Object.keys( json ).length > 0 ) objectDef.extras = json;
766
+
767
+ } catch ( error ) {
768
+
769
+ console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' +
770
+ 'won\'t be serialized because of JSON.stringify error - ' + error.message );
771
+
772
+ }
773
+
774
+ }
775
+
776
+ /**
777
+ * Returns ids for buffer attributes.
778
+ *
779
+ * @param {Object} attribute
780
+ * @param {boolean} [isRelativeCopy=false]
781
+ * @return {number} An integer
782
+ */
783
+ getUID( attribute, isRelativeCopy = false ) {
784
+
785
+ if ( this.uids.has( attribute ) === false ) {
786
+
787
+ const uids = new Map();
788
+
789
+ uids.set( true, this.uid ++ );
790
+ uids.set( false, this.uid ++ );
791
+
792
+ this.uids.set( attribute, uids );
793
+
794
+ }
795
+
796
+ const uids = this.uids.get( attribute );
797
+
798
+ return uids.get( isRelativeCopy );
799
+
800
+ }
801
+
802
+ /**
803
+ * Checks if normal attribute values are normalized.
804
+ *
805
+ * @param {BufferAttribute} normal
806
+ * @returns {boolean}
807
+ */
808
+ isNormalizedNormalAttribute( normal ) {
809
+
810
+ const cache = this.cache;
811
+
812
+ if ( cache.attributesNormalized.has( normal ) ) return false;
813
+
814
+ const v = new THREE.Vector3();
815
+
816
+ for ( let i = 0, il = normal.count; i < il; i ++ ) {
817
+
818
+ // 0.0005 is from glTF-validator
819
+ if ( Math.abs( v.fromBufferAttribute( normal, i ).length() - 1.0 ) > 0.0005 ) return false;
820
+
821
+ }
822
+
823
+ return true;
824
+
825
+ }
826
+
827
+ /**
828
+ * Creates normalized normal buffer attribute.
829
+ *
830
+ * @param {BufferAttribute} normal
831
+ * @returns {BufferAttribute}
832
+ *
833
+ */
834
+ createNormalizedNormalAttribute( normal ) {
835
+
836
+ const cache = this.cache;
837
+
838
+ if ( cache.attributesNormalized.has( normal ) ) return cache.attributesNormalized.get( normal );
839
+
840
+ const attribute = normal.clone();
841
+ const v = new THREE.Vector3();
842
+
843
+ for ( let i = 0, il = attribute.count; i < il; i ++ ) {
844
+
845
+ v.fromBufferAttribute( attribute, i );
846
+
847
+ if ( v.x === 0 && v.y === 0 && v.z === 0 ) {
848
+
849
+ // if values can't be normalized set (1, 0, 0)
850
+ v.setX( 1.0 );
851
+
852
+ } else {
853
+
854
+ v.normalize();
855
+
856
+ }
857
+
858
+ attribute.setXYZ( i, v.x, v.y, v.z );
859
+
860
+ }
861
+
862
+ cache.attributesNormalized.set( normal, attribute );
863
+
864
+ return attribute;
865
+
866
+ }
867
+
868
+ /**
869
+ * Applies a texture transform, if present, to the map definition. Requires
870
+ * the KHR_texture_transform extension.
871
+ *
872
+ * @param {Object} mapDef
873
+ * @param {THREE.Texture} texture
874
+ */
875
+ applyTextureTransform( mapDef, texture ) {
876
+
877
+ let didTransform = false;
878
+ const transformDef = {};
879
+
880
+ if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) {
881
+
882
+ transformDef.offset = texture.offset.toArray();
883
+ didTransform = true;
884
+
885
+ }
886
+
887
+ if ( texture.rotation !== 0 ) {
888
+
889
+ transformDef.rotation = texture.rotation;
890
+ didTransform = true;
891
+
892
+ }
893
+
894
+ if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) {
895
+
896
+ transformDef.scale = texture.repeat.toArray();
897
+ didTransform = true;
898
+
899
+ }
900
+
901
+ if ( didTransform ) {
902
+
903
+ mapDef.extensions = mapDef.extensions || {};
904
+ mapDef.extensions[ 'KHR_texture_transform' ] = transformDef;
905
+ this.extensionsUsed[ 'KHR_texture_transform' ] = true;
906
+
907
+ }
908
+
909
+ }
910
+
911
+ async buildMetalRoughTextureAsync( metalnessMap, roughnessMap ) {
912
+
913
+ if ( metalnessMap === roughnessMap ) return metalnessMap;
914
+
915
+ function getEncodingConversion( map ) {
916
+
917
+ if ( map.colorSpace === THREE.SRGBColorSpace ) {
918
+
919
+ return function SRGBToLinear( c ) {
920
+
921
+ return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 );
922
+
923
+ };
924
+
925
+ }
926
+
927
+ return function LinearToLinear( c ) {
928
+
929
+ return c;
930
+
931
+ };
932
+
933
+ }
934
+
935
+ if ( metalnessMap instanceof THREE.CompressedTexture ) {
936
+
937
+ metalnessMap = await this.decompressTextureAsync( metalnessMap );
938
+
939
+ }
940
+
941
+ if ( roughnessMap instanceof THREE.CompressedTexture ) {
942
+
943
+ roughnessMap = await this.decompressTextureAsync( roughnessMap );
944
+
945
+ }
946
+
947
+ const metalness = metalnessMap ? metalnessMap.image : null;
948
+ const roughness = roughnessMap ? roughnessMap.image : null;
949
+
950
+ const width = Math.max( metalness ? metalness.width : 0, roughness ? roughness.width : 0 );
951
+ const height = Math.max( metalness ? metalness.height : 0, roughness ? roughness.height : 0 );
952
+
953
+ const canvas = getCanvas();
954
+ canvas.width = width;
955
+ canvas.height = height;
956
+
957
+ const context = canvas.getContext( '2d', {
958
+ willReadFrequently: true,
959
+ } );
960
+ context.fillStyle = '#00ffff';
961
+ context.fillRect( 0, 0, width, height );
962
+
963
+ const composite = context.getImageData( 0, 0, width, height );
964
+
965
+ if ( metalness ) {
966
+
967
+ context.drawImage( metalness, 0, 0, width, height );
968
+
969
+ const convert = getEncodingConversion( metalnessMap );
970
+ const data = context.getImageData( 0, 0, width, height ).data;
971
+
972
+ for ( let i = 2; i < data.length; i += 4 ) {
973
+
974
+ composite.data[ i ] = convert( data[ i ] / 256 ) * 256;
975
+
976
+ }
977
+
978
+ }
979
+
980
+ if ( roughness ) {
981
+
982
+ context.drawImage( roughness, 0, 0, width, height );
983
+
984
+ const convert = getEncodingConversion( roughnessMap );
985
+ const data = context.getImageData( 0, 0, width, height ).data;
986
+
987
+ for ( let i = 1; i < data.length; i += 4 ) {
988
+
989
+ composite.data[ i ] = convert( data[ i ] / 256 ) * 256;
990
+
991
+ }
992
+
993
+ }
994
+
995
+ context.putImageData( composite, 0, 0 );
996
+
997
+ //
998
+
999
+ const reference = metalnessMap || roughnessMap;
1000
+
1001
+ const texture = reference.clone();
1002
+
1003
+ texture.source = new THREE.Source( canvas );
1004
+ texture.colorSpace = THREE.NoColorSpace;
1005
+ texture.channel = ( metalnessMap || roughnessMap ).channel;
1006
+
1007
+ if ( metalnessMap && roughnessMap && metalnessMap.channel !== roughnessMap.channel ) {
1008
+
1009
+ console.warn( 'THREE.GLTFExporter: UV channels for metalnessMap and roughnessMap textures must match.' );
1010
+
1011
+ }
1012
+
1013
+ console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' );
1014
+
1015
+ return texture;
1016
+
1017
+ }
1018
+
1019
+
1020
+ async decompressTextureAsync( texture, maxTextureSize = Infinity ) {
1021
+
1022
+ if ( this.textureUtils === null ) {
1023
+
1024
+ throw new Error( 'THREE.GLTFExporter: setTextureUtils() must be called to process compressed textures.' );
1025
+
1026
+ }
1027
+
1028
+ return await this.textureUtils.decompress( texture, maxTextureSize );
1029
+
1030
+ }
1031
+
1032
+ /**
1033
+ * Process a buffer to append to the default one.
1034
+ * @param {ArrayBuffer} buffer
1035
+ * @return {0}
1036
+ */
1037
+ processBuffer( buffer ) {
1038
+
1039
+ const json = this.json;
1040
+ const buffers = this.buffers;
1041
+
1042
+ if ( ! json.buffers ) json.buffers = [ { byteLength: 0 } ];
1043
+
1044
+ // All buffers are merged before export.
1045
+ buffers.push( buffer );
1046
+
1047
+ return 0;
1048
+
1049
+ }
1050
+
1051
+ /**
1052
+ * Process and generate a BufferView
1053
+ * @param {BufferAttribute} attribute
1054
+ * @param {number} componentType
1055
+ * @param {number} start
1056
+ * @param {number} count
1057
+ * @param {number} [target] Target usage of the BufferView
1058
+ * @return {Object}
1059
+ */
1060
+ processBufferView( attribute, componentType, start, count, target ) {
1061
+
1062
+ const json = this.json;
1063
+
1064
+ if ( ! json.bufferViews ) json.bufferViews = [];
1065
+
1066
+ // Create a new dataview and dump the attribute's array into it
1067
+
1068
+ let componentSize;
1069
+
1070
+ switch ( componentType ) {
1071
+
1072
+ case WEBGL_CONSTANTS.BYTE:
1073
+ case WEBGL_CONSTANTS.UNSIGNED_BYTE:
1074
+
1075
+ componentSize = 1;
1076
+
1077
+ break;
1078
+
1079
+ case WEBGL_CONSTANTS.SHORT:
1080
+ case WEBGL_CONSTANTS.UNSIGNED_SHORT:
1081
+
1082
+ componentSize = 2;
1083
+
1084
+ break;
1085
+
1086
+ default:
1087
+
1088
+ componentSize = 4;
1089
+
1090
+ }
1091
+
1092
+ let byteStride = attribute.itemSize * componentSize;
1093
+
1094
+ if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) {
1095
+
1096
+ // Each element of a vertex attribute MUST be aligned to 4-byte boundaries
1097
+ // inside a bufferView
1098
+ byteStride = Math.ceil( byteStride / 4 ) * 4;
1099
+
1100
+ }
1101
+
1102
+ const byteLength = getPaddedBufferSize( count * byteStride );
1103
+ const dataView = new DataView( new ArrayBuffer( byteLength ) );
1104
+ let offset = 0;
1105
+
1106
+ for ( let i = start; i < start + count; i ++ ) {
1107
+
1108
+ for ( let a = 0; a < attribute.itemSize; a ++ ) {
1109
+
1110
+ let value;
1111
+
1112
+ if ( attribute.itemSize > 4 ) {
1113
+
1114
+ // no support for interleaved data for itemSize > 4
1115
+
1116
+ value = attribute.array[ i * attribute.itemSize + a ];
1117
+
1118
+ } else {
1119
+
1120
+ if ( a === 0 ) value = attribute.getX( i );
1121
+ else if ( a === 1 ) value = attribute.getY( i );
1122
+ else if ( a === 2 ) value = attribute.getZ( i );
1123
+ else if ( a === 3 ) value = attribute.getW( i );
1124
+
1125
+ if ( attribute.normalized === true ) {
1126
+
1127
+ value = THREE.MathUtils.normalize( value, attribute.array );
1128
+
1129
+ }
1130
+
1131
+ }
1132
+
1133
+ if ( componentType === WEBGL_CONSTANTS.FLOAT ) {
1134
+
1135
+ dataView.setFloat32( offset, value, true );
1136
+
1137
+ } else if ( componentType === WEBGL_CONSTANTS.INT ) {
1138
+
1139
+ dataView.setInt32( offset, value, true );
1140
+
1141
+ } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) {
1142
+
1143
+ dataView.setUint32( offset, value, true );
1144
+
1145
+ } else if ( componentType === WEBGL_CONSTANTS.SHORT ) {
1146
+
1147
+ dataView.setInt16( offset, value, true );
1148
+
1149
+ } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) {
1150
+
1151
+ dataView.setUint16( offset, value, true );
1152
+
1153
+ } else if ( componentType === WEBGL_CONSTANTS.BYTE ) {
1154
+
1155
+ dataView.setInt8( offset, value );
1156
+
1157
+ } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) {
1158
+
1159
+ dataView.setUint8( offset, value );
1160
+
1161
+ }
1162
+
1163
+ offset += componentSize;
1164
+
1165
+ }
1166
+
1167
+ if ( ( offset % byteStride ) !== 0 ) {
1168
+
1169
+ offset += byteStride - ( offset % byteStride );
1170
+
1171
+ }
1172
+
1173
+ }
1174
+
1175
+ const bufferViewDef = {
1176
+
1177
+ buffer: this.processBuffer( dataView.buffer ),
1178
+ byteOffset: this.byteOffset,
1179
+ byteLength: byteLength
1180
+
1181
+ };
1182
+
1183
+ if ( target !== undefined ) bufferViewDef.target = target;
1184
+
1185
+ if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) {
1186
+
1187
+ // Only define byteStride for vertex attributes.
1188
+ bufferViewDef.byteStride = byteStride;
1189
+
1190
+ }
1191
+
1192
+ this.byteOffset += byteLength;
1193
+
1194
+ json.bufferViews.push( bufferViewDef );
1195
+
1196
+ // @TODO Merge bufferViews where possible.
1197
+ const output = {
1198
+
1199
+ id: json.bufferViews.length - 1,
1200
+ byteLength: 0
1201
+
1202
+ };
1203
+
1204
+ return output;
1205
+
1206
+ }
1207
+
1208
+ /**
1209
+ * Process and generate a BufferView from an image Blob.
1210
+ * @param {Blob} blob
1211
+ * @return {Promise<number>} An integer
1212
+ */
1213
+ processBufferViewImage( blob ) {
1214
+
1215
+ const writer = this;
1216
+ const json = writer.json;
1217
+
1218
+ if ( ! json.bufferViews ) json.bufferViews = [];
1219
+
1220
+ return new Promise( function ( resolve ) {
1221
+
1222
+ const reader = new FileReader();
1223
+ reader.readAsArrayBuffer( blob );
1224
+ reader.onloadend = function () {
1225
+
1226
+ const buffer = getPaddedArrayBuffer( reader.result );
1227
+
1228
+ const bufferViewDef = {
1229
+ buffer: writer.processBuffer( buffer ),
1230
+ byteOffset: writer.byteOffset,
1231
+ byteLength: buffer.byteLength
1232
+ };
1233
+
1234
+ writer.byteOffset += buffer.byteLength;
1235
+ resolve( json.bufferViews.push( bufferViewDef ) - 1 );
1236
+
1237
+ };
1238
+
1239
+ } );
1240
+
1241
+ }
1242
+
1243
+ /**
1244
+ * Process attribute to generate an accessor
1245
+ * @param {BufferAttribute} attribute Attribute to process
1246
+ * @param {?BufferGeometry} [geometry] Geometry used for truncated draw range
1247
+ * @param {number} [start=0]
1248
+ * @param {number} [count=Infinity]
1249
+ * @return {?number} Index of the processed accessor on the "accessors" array
1250
+ */
1251
+ processAccessor( attribute, geometry, start, count ) {
1252
+
1253
+ const json = this.json;
1254
+
1255
+ const types = {
1256
+
1257
+ 1: 'SCALAR',
1258
+ 2: 'VEC2',
1259
+ 3: 'VEC3',
1260
+ 4: 'VEC4',
1261
+ 9: 'MAT3',
1262
+ 16: 'MAT4'
1263
+
1264
+ };
1265
+
1266
+ let componentType;
1267
+
1268
+ // Detect the component type of the attribute array
1269
+ if ( attribute.array.constructor === Float32Array ) {
1270
+
1271
+ componentType = WEBGL_CONSTANTS.FLOAT;
1272
+
1273
+ } else if ( attribute.array.constructor === Int32Array ) {
1274
+
1275
+ componentType = WEBGL_CONSTANTS.INT;
1276
+
1277
+ } else if ( attribute.array.constructor === Uint32Array ) {
1278
+
1279
+ componentType = WEBGL_CONSTANTS.UNSIGNED_INT;
1280
+
1281
+ } else if ( attribute.array.constructor === Int16Array ) {
1282
+
1283
+ componentType = WEBGL_CONSTANTS.SHORT;
1284
+
1285
+ } else if ( attribute.array.constructor === Uint16Array ) {
1286
+
1287
+ componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT;
1288
+
1289
+ } else if ( attribute.array.constructor === Int8Array ) {
1290
+
1291
+ componentType = WEBGL_CONSTANTS.BYTE;
1292
+
1293
+ } else if ( attribute.array.constructor === Uint8Array ) {
1294
+
1295
+ componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE;
1296
+
1297
+ } else {
1298
+
1299
+ throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type: ' + attribute.array.constructor.name );
1300
+
1301
+ }
1302
+
1303
+ if ( start === undefined ) start = 0;
1304
+ if ( count === undefined || count === Infinity ) count = attribute.count;
1305
+
1306
+ // Skip creating an accessor if the attribute doesn't have data to export
1307
+ if ( count === 0 ) return null;
1308
+
1309
+ const minMax = getMinMax( attribute, start, count );
1310
+ let bufferViewTarget;
1311
+
1312
+ // If geometry isn't provided, don't infer the target usage of the bufferView. For
1313
+ // animation samplers, target must not be set.
1314
+ if ( geometry !== undefined ) {
1315
+
1316
+ bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER;
1317
+
1318
+ }
1319
+
1320
+ const bufferView = this.processBufferView( attribute, componentType, start, count, bufferViewTarget );
1321
+
1322
+ const accessorDef = {
1323
+
1324
+ bufferView: bufferView.id,
1325
+ byteOffset: bufferView.byteOffset,
1326
+ componentType: componentType,
1327
+ count: count,
1328
+ max: minMax.max,
1329
+ min: minMax.min,
1330
+ type: types[ attribute.itemSize ]
1331
+
1332
+ };
1333
+
1334
+ if ( attribute.normalized === true ) accessorDef.normalized = true;
1335
+ if ( ! json.accessors ) json.accessors = [];
1336
+
1337
+ return json.accessors.push( accessorDef ) - 1;
1338
+
1339
+ }
1340
+
1341
+ /**
1342
+ * Process image
1343
+ * @param {Image} image to process
1344
+ * @param {number} format Identifier of the format (RGBAFormat)
1345
+ * @param {boolean} flipY before writing out the image
1346
+ * @param {string} mimeType export format
1347
+ * @return {number} Index of the processed texture in the "images" array
1348
+ */
1349
+ processImage( image, format, flipY, mimeType = 'image/png' ) {
1350
+
1351
+ if ( image !== null ) {
1352
+
1353
+ const writer = this;
1354
+ const cache = writer.cache;
1355
+ const json = writer.json;
1356
+ const options = writer.options;
1357
+ const pending = writer.pending;
1358
+
1359
+ if ( ! cache.images.has( image ) ) cache.images.set( image, {} );
1360
+
1361
+ const cachedImages = cache.images.get( image );
1362
+
1363
+ const key = mimeType + ':flipY/' + flipY.toString();
1364
+
1365
+ if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ];
1366
+
1367
+ if ( ! json.images ) json.images = [];
1368
+
1369
+ const imageDef = { mimeType: mimeType };
1370
+
1371
+ const canvas = getCanvas();
1372
+
1373
+ canvas.width = Math.min( image.width, options.maxTextureSize );
1374
+ canvas.height = Math.min( image.height, options.maxTextureSize );
1375
+
1376
+ const ctx = canvas.getContext( '2d', {
1377
+ willReadFrequently: true,
1378
+ } );
1379
+
1380
+ if ( flipY === true ) {
1381
+
1382
+ ctx.translate( 0, canvas.height );
1383
+ ctx.scale( 1, - 1 );
1384
+
1385
+ }
1386
+
1387
+ if ( image.data !== undefined ) { // THREE.DataTexture
1388
+
1389
+ if ( format !== THREE.RGBAFormat ) {
1390
+
1391
+ console.error( 'GLTFExporter: Only RGBAFormat is supported.', format );
1392
+
1393
+ }
1394
+
1395
+ if ( image.width > options.maxTextureSize || image.height > options.maxTextureSize ) {
1396
+
1397
+ console.warn( 'GLTFExporter: Image size is bigger than maxTextureSize', image );
1398
+
1399
+ }
1400
+
1401
+ const data = new Uint8ClampedArray( image.height * image.width * 4 );
1402
+
1403
+ for ( let i = 0; i < data.length; i += 4 ) {
1404
+
1405
+ data[ i + 0 ] = image.data[ i + 0 ];
1406
+ data[ i + 1 ] = image.data[ i + 1 ];
1407
+ data[ i + 2 ] = image.data[ i + 2 ];
1408
+ data[ i + 3 ] = image.data[ i + 3 ];
1409
+
1410
+ }
1411
+
1412
+ ctx.putImageData( new ImageData( data, image.width, image.height ), 0, 0 );
1413
+
1414
+ } else {
1415
+
1416
+ if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
1417
+ ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
1418
+ ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ||
1419
+ ( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) ) {
1420
+
1421
+ ctx.drawImage( image, 0, 0, canvas.width, canvas.height );
1422
+
1423
+ } else {
1424
+
1425
+ throw new Error( 'THREE.GLTFExporter: Invalid image type. Use HTMLImageElement, HTMLCanvasElement, ImageBitmap or OffscreenCanvas.' );
1426
+
1427
+ }
1428
+
1429
+ }
1430
+
1431
+ if ( options.binary === true ) {
1432
+
1433
+ pending.push(
1434
+
1435
+ getToBlobPromise( canvas, mimeType )
1436
+ .then( blob => writer.processBufferViewImage( blob ) )
1437
+ .then( bufferViewIndex => {
1438
+
1439
+ imageDef.bufferView = bufferViewIndex;
1440
+
1441
+ } )
1442
+
1443
+ );
1444
+
1445
+ } else {
1446
+
1447
+ imageDef.uri = THREE.ImageUtils.getDataURL( canvas, mimeType );
1448
+
1449
+ }
1450
+
1451
+ const index = json.images.push( imageDef ) - 1;
1452
+ cachedImages[ key ] = index;
1453
+ return index;
1454
+
1455
+ } else {
1456
+
1457
+ throw new Error( 'THREE.GLTFExporter: No valid image data found. Unable to process texture.' );
1458
+
1459
+ }
1460
+
1461
+ }
1462
+
1463
+ /**
1464
+ * Process sampler
1465
+ * @param {Texture} map Texture to process
1466
+ * @return {number} Index of the processed texture in the "samplers" array
1467
+ */
1468
+ processSampler( map ) {
1469
+
1470
+ const json = this.json;
1471
+
1472
+ if ( ! json.samplers ) json.samplers = [];
1473
+
1474
+ const samplerDef = {
1475
+ magFilter: THREE_TO_WEBGL[ map.magFilter ],
1476
+ minFilter: THREE_TO_WEBGL[ map.minFilter ],
1477
+ wrapS: THREE_TO_WEBGL[ map.wrapS ],
1478
+ wrapT: THREE_TO_WEBGL[ map.wrapT ]
1479
+ };
1480
+
1481
+ return json.samplers.push( samplerDef ) - 1;
1482
+
1483
+ }
1484
+
1485
+ /**
1486
+ * Process texture
1487
+ * @param {Texture} map Map to process
1488
+ * @return {Promise<number>} Index of the processed texture in the "textures" array
1489
+ */
1490
+ async processTextureAsync( map ) {
1491
+
1492
+ const writer = this;
1493
+ const options = writer.options;
1494
+ const cache = this.cache;
1495
+ const json = this.json;
1496
+
1497
+ if ( cache.textures.has( map ) ) return cache.textures.get( map );
1498
+
1499
+ if ( ! json.textures ) json.textures = [];
1500
+
1501
+ // make non-readable textures (e.g. CompressedTexture) readable by blitting them into a new texture
1502
+ if ( map instanceof THREE.CompressedTexture ) {
1503
+
1504
+ map = await this.decompressTextureAsync( map, options.maxTextureSize );
1505
+
1506
+ }
1507
+
1508
+ let mimeType = map.userData.mimeType;
1509
+
1510
+ if ( mimeType === 'image/webp' ) mimeType = 'image/png';
1511
+
1512
+ const textureDef = {
1513
+ sampler: this.processSampler( map ),
1514
+ source: this.processImage( map.image, map.format, map.flipY, mimeType )
1515
+ };
1516
+
1517
+ if ( map.name ) textureDef.name = map.name;
1518
+
1519
+ await this._invokeAllAsync( async function ( ext ) {
1520
+
1521
+ ext.writeTexture && await ext.writeTexture( map, textureDef );
1522
+
1523
+ } );
1524
+
1525
+ const index = json.textures.push( textureDef ) - 1;
1526
+ cache.textures.set( map, index );
1527
+ return index;
1528
+
1529
+ }
1530
+
1531
+ /**
1532
+ * Process material
1533
+ * @param {THREE.Material} material Material to process
1534
+ * @return {Promise<number|null>} Index of the processed material in the "materials" array
1535
+ */
1536
+ async processMaterialAsync( material ) {
1537
+
1538
+ const cache = this.cache;
1539
+ const json = this.json;
1540
+
1541
+ if ( cache.materials.has( material ) ) return cache.materials.get( material );
1542
+
1543
+ if ( material.isShaderMaterial ) {
1544
+
1545
+ console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' );
1546
+ return null;
1547
+
1548
+ }
1549
+
1550
+ if ( ! json.materials ) json.materials = [];
1551
+
1552
+ // @QUESTION Should we avoid including any attribute that has the default value?
1553
+ const materialDef = { pbrMetallicRoughness: {} };
1554
+
1555
+ if ( material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true ) {
1556
+
1557
+ console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' );
1558
+
1559
+ }
1560
+
1561
+ // pbrMetallicRoughness.baseColorFactor
1562
+ const color = material.color.toArray().concat( [ material.opacity ] );
1563
+
1564
+ if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) {
1565
+
1566
+ materialDef.pbrMetallicRoughness.baseColorFactor = color;
1567
+
1568
+ }
1569
+
1570
+ if ( material.isMeshStandardMaterial ) {
1571
+
1572
+ materialDef.pbrMetallicRoughness.metallicFactor = material.metalness;
1573
+ materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness;
1574
+
1575
+ } else {
1576
+
1577
+ materialDef.pbrMetallicRoughness.metallicFactor = 0;
1578
+ materialDef.pbrMetallicRoughness.roughnessFactor = 1;
1579
+
1580
+ }
1581
+
1582
+ // pbrMetallicRoughness.metallicRoughnessTexture
1583
+ if ( material.metalnessMap || material.roughnessMap ) {
1584
+
1585
+ const metalRoughTexture = await this.buildMetalRoughTextureAsync( material.metalnessMap, material.roughnessMap );
1586
+
1587
+ const metalRoughMapDef = {
1588
+ index: await this.processTextureAsync( metalRoughTexture ),
1589
+ texCoord: metalRoughTexture.channel
1590
+ };
1591
+ this.applyTextureTransform( metalRoughMapDef, metalRoughTexture );
1592
+ materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef;
1593
+
1594
+ }
1595
+
1596
+ // pbrMetallicRoughness.baseColorTexture
1597
+ if ( material.map ) {
1598
+
1599
+ const baseColorMapDef = {
1600
+ index: await this.processTextureAsync( material.map ),
1601
+ texCoord: material.map.channel
1602
+ };
1603
+ this.applyTextureTransform( baseColorMapDef, material.map );
1604
+ materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef;
1605
+
1606
+ }
1607
+
1608
+ if ( material.emissive ) {
1609
+
1610
+ const emissive = material.emissive;
1611
+ const maxEmissiveComponent = Math.max( emissive.r, emissive.g, emissive.b );
1612
+
1613
+ if ( maxEmissiveComponent > 0 ) {
1614
+
1615
+ materialDef.emissiveFactor = material.emissive.toArray();
1616
+
1617
+ }
1618
+
1619
+ // emissiveTexture
1620
+ if ( material.emissiveMap ) {
1621
+
1622
+ const emissiveMapDef = {
1623
+ index: await this.processTextureAsync( material.emissiveMap ),
1624
+ texCoord: material.emissiveMap.channel
1625
+ };
1626
+ this.applyTextureTransform( emissiveMapDef, material.emissiveMap );
1627
+ materialDef.emissiveTexture = emissiveMapDef;
1628
+
1629
+ }
1630
+
1631
+ }
1632
+
1633
+ // normalTexture
1634
+ if ( material.normalMap ) {
1635
+
1636
+ const normalMapDef = {
1637
+ index: await this.processTextureAsync( material.normalMap ),
1638
+ texCoord: material.normalMap.channel
1639
+ };
1640
+
1641
+ if ( material.normalScale && material.normalScale.x !== 1 ) {
1642
+
1643
+ // glTF normal scale is univariate. Ignore `y`, which may be flipped.
1644
+ // Context: https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
1645
+ normalMapDef.scale = material.normalScale.x;
1646
+
1647
+ }
1648
+
1649
+ this.applyTextureTransform( normalMapDef, material.normalMap );
1650
+ materialDef.normalTexture = normalMapDef;
1651
+
1652
+ }
1653
+
1654
+ // occlusionTexture
1655
+ if ( material.aoMap ) {
1656
+
1657
+ const occlusionMapDef = {
1658
+ index: await this.processTextureAsync( material.aoMap ),
1659
+ texCoord: material.aoMap.channel
1660
+ };
1661
+
1662
+ if ( material.aoMapIntensity !== 1.0 ) {
1663
+
1664
+ occlusionMapDef.strength = material.aoMapIntensity;
1665
+
1666
+ }
1667
+
1668
+ this.applyTextureTransform( occlusionMapDef, material.aoMap );
1669
+ materialDef.occlusionTexture = occlusionMapDef;
1670
+
1671
+ }
1672
+
1673
+ // alphaMode
1674
+ if ( material.transparent ) {
1675
+
1676
+ materialDef.alphaMode = 'BLEND';
1677
+
1678
+ } else {
1679
+
1680
+ if ( material.alphaTest > 0.0 ) {
1681
+
1682
+ materialDef.alphaMode = 'MASK';
1683
+ materialDef.alphaCutoff = material.alphaTest;
1684
+
1685
+ }
1686
+
1687
+ }
1688
+
1689
+ // doubleSided
1690
+ if ( material.side === THREE.DoubleSide ) materialDef.doubleSided = true;
1691
+ if ( material.name !== '' ) materialDef.name = material.name;
1692
+
1693
+ this.serializeUserData( material, materialDef );
1694
+
1695
+ await this._invokeAllAsync( async function ( ext ) {
1696
+
1697
+ ext.writeMaterialAsync && await ext.writeMaterialAsync( material, materialDef );
1698
+
1699
+ } );
1700
+
1701
+ const index = json.materials.push( materialDef ) - 1;
1702
+ cache.materials.set( material, index );
1703
+ return index;
1704
+
1705
+ }
1706
+
1707
+ /**
1708
+ * Process mesh
1709
+ * @param {THREE.Mesh} mesh Mesh to process
1710
+ * @return {Promise<number|null>} Index of the processed mesh in the "meshes" array
1711
+ */
1712
+ async processMeshAsync( mesh ) {
1713
+
1714
+ const cache = this.cache;
1715
+ const json = this.json;
1716
+
1717
+ const meshCacheKeyParts = [ mesh.geometry.uuid ];
1718
+
1719
+ if ( Array.isArray( mesh.material ) ) {
1720
+
1721
+ for ( let i = 0, l = mesh.material.length; i < l; i ++ ) {
1722
+
1723
+ meshCacheKeyParts.push( mesh.material[ i ].uuid );
1724
+
1725
+ }
1726
+
1727
+ } else {
1728
+
1729
+ meshCacheKeyParts.push( mesh.material.uuid );
1730
+
1731
+ }
1732
+
1733
+ const meshCacheKey = meshCacheKeyParts.join( ':' );
1734
+
1735
+ if ( cache.meshes.has( meshCacheKey ) ) return cache.meshes.get( meshCacheKey );
1736
+
1737
+ const geometry = mesh.geometry;
1738
+
1739
+ let mode;
1740
+
1741
+ // Use the correct mode
1742
+ if ( mesh.isLineSegments ) {
1743
+
1744
+ mode = WEBGL_CONSTANTS.LINES;
1745
+
1746
+ } else if ( mesh.isLineLoop ) {
1747
+
1748
+ mode = WEBGL_CONSTANTS.LINE_LOOP;
1749
+
1750
+ } else if ( mesh.isLine ) {
1751
+
1752
+ mode = WEBGL_CONSTANTS.LINE_STRIP;
1753
+
1754
+ } else if ( mesh.isPoints ) {
1755
+
1756
+ mode = WEBGL_CONSTANTS.POINTS;
1757
+
1758
+ } else {
1759
+
1760
+ mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES;
1761
+
1762
+ }
1763
+
1764
+ const meshDef = {};
1765
+ const attributes = {};
1766
+ const primitives = [];
1767
+ const targets = [];
1768
+
1769
+ // Conversion between attributes names in threejs and gltf spec
1770
+ const nameConversion = {
1771
+ uv: 'TEXCOORD_0',
1772
+ uv1: 'TEXCOORD_1',
1773
+ uv2: 'TEXCOORD_2',
1774
+ uv3: 'TEXCOORD_3',
1775
+ color: 'COLOR_0',
1776
+ skinWeight: 'WEIGHTS_0',
1777
+ skinIndex: 'JOINTS_0'
1778
+ };
1779
+
1780
+ const originalNormal = geometry.getAttribute( 'normal' );
1781
+
1782
+ if ( originalNormal !== undefined && ! this.isNormalizedNormalAttribute( originalNormal ) ) {
1783
+
1784
+ console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' );
1785
+
1786
+ geometry.setAttribute( 'normal', this.createNormalizedNormalAttribute( originalNormal ) );
1787
+
1788
+ }
1789
+
1790
+ // @QUESTION Detect if .vertexColors = true?
1791
+ // For every attribute create an accessor
1792
+ let modifiedAttribute = null;
1793
+
1794
+ for ( let attributeName in geometry.attributes ) {
1795
+
1796
+ // Ignore morph target attributes, which are exported later.
1797
+ if ( attributeName.slice( 0, 5 ) === 'morph' ) continue;
1798
+
1799
+ const attribute = geometry.attributes[ attributeName ];
1800
+ attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase();
1801
+
1802
+ // Prefix all geometry attributes except the ones specifically
1803
+ // listed in the spec; non-spec attributes are considered custom.
1804
+ const validVertexAttributes =
1805
+ /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/;
1806
+
1807
+ if ( ! validVertexAttributes.test( attributeName ) ) attributeName = '_' + attributeName;
1808
+
1809
+ if ( cache.attributes.has( this.getUID( attribute ) ) ) {
1810
+
1811
+ attributes[ attributeName ] = cache.attributes.get( this.getUID( attribute ) );
1812
+ continue;
1813
+
1814
+ }
1815
+
1816
+ // Enforce glTF vertex attribute requirements:
1817
+ // - JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT
1818
+ // - Only custom attributes may be INT or UNSIGNED_INT
1819
+ modifiedAttribute = null;
1820
+ const array = attribute.array;
1821
+
1822
+ if ( attributeName === 'JOINTS_0' &&
1823
+ ! ( array instanceof Uint16Array ) &&
1824
+ ! ( array instanceof Uint8Array ) ) {
1825
+
1826
+ console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' );
1827
+ modifiedAttribute = new THREE.BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized );
1828
+
1829
+ } else if ( ( array instanceof Uint32Array || array instanceof Int32Array ) && ! attributeName.startsWith( '_' ) ) {
1830
+
1831
+ console.warn( `GLTFExporter: Attribute "${ attributeName }" converted to type FLOAT.` );
1832
+ modifiedAttribute = GLTFExporter.Utils.toFloat32BufferAttribute( attribute );
1833
+
1834
+ }
1835
+
1836
+ const accessor = this.processAccessor( modifiedAttribute || attribute, geometry );
1837
+
1838
+ if ( accessor !== null ) {
1839
+
1840
+ if ( ! attributeName.startsWith( '_' ) ) {
1841
+
1842
+ this.detectMeshQuantization( attributeName, attribute );
1843
+
1844
+ }
1845
+
1846
+ attributes[ attributeName ] = accessor;
1847
+ cache.attributes.set( this.getUID( attribute ), accessor );
1848
+
1849
+ }
1850
+
1851
+ }
1852
+
1853
+ if ( originalNormal !== undefined ) geometry.setAttribute( 'normal', originalNormal );
1854
+
1855
+ // Skip if no exportable attributes found
1856
+ if ( Object.keys( attributes ).length === 0 ) return null;
1857
+
1858
+ // Morph targets
1859
+ if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) {
1860
+
1861
+ const weights = [];
1862
+ const targetNames = [];
1863
+ const reverseDictionary = {};
1864
+
1865
+ if ( mesh.morphTargetDictionary !== undefined ) {
1866
+
1867
+ for ( const key in mesh.morphTargetDictionary ) {
1868
+
1869
+ reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key;
1870
+
1871
+ }
1872
+
1873
+ }
1874
+
1875
+ for ( let i = 0; i < mesh.morphTargetInfluences.length; ++ i ) {
1876
+
1877
+ const target = {};
1878
+ let warned = false;
1879
+
1880
+ for ( const attributeName in geometry.morphAttributes ) {
1881
+
1882
+ // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT.
1883
+ // Three.js doesn't support TANGENT yet.
1884
+
1885
+ if ( attributeName !== 'position' && attributeName !== 'normal' ) {
1886
+
1887
+ if ( ! warned ) {
1888
+
1889
+ console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' );
1890
+ warned = true;
1891
+
1892
+ }
1893
+
1894
+ continue;
1895
+
1896
+ }
1897
+
1898
+ const attribute = geometry.morphAttributes[ attributeName ][ i ];
1899
+ const gltfAttributeName = attributeName.toUpperCase();
1900
+
1901
+ // Three.js morph attribute has absolute values while the one of glTF has relative values.
1902
+ //
1903
+ // glTF 2.0 Specification:
1904
+ // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets
1905
+
1906
+ const baseAttribute = geometry.attributes[ attributeName ];
1907
+
1908
+ if ( cache.attributes.has( this.getUID( attribute, true ) ) ) {
1909
+
1910
+ target[ gltfAttributeName ] = cache.attributes.get( this.getUID( attribute, true ) );
1911
+ continue;
1912
+
1913
+ }
1914
+
1915
+ // Clones attribute not to override
1916
+ const relativeAttribute = attribute.clone();
1917
+
1918
+ if ( ! geometry.morphTargetsRelative ) {
1919
+
1920
+ for ( let j = 0, jl = attribute.count; j < jl; j ++ ) {
1921
+
1922
+ for ( let a = 0; a < attribute.itemSize; a ++ ) {
1923
+
1924
+ if ( a === 0 ) relativeAttribute.setX( j, attribute.getX( j ) - baseAttribute.getX( j ) );
1925
+ if ( a === 1 ) relativeAttribute.setY( j, attribute.getY( j ) - baseAttribute.getY( j ) );
1926
+ if ( a === 2 ) relativeAttribute.setZ( j, attribute.getZ( j ) - baseAttribute.getZ( j ) );
1927
+ if ( a === 3 ) relativeAttribute.setW( j, attribute.getW( j ) - baseAttribute.getW( j ) );
1928
+
1929
+ }
1930
+
1931
+ }
1932
+
1933
+ }
1934
+
1935
+ target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry );
1936
+ cache.attributes.set( this.getUID( baseAttribute, true ), target[ gltfAttributeName ] );
1937
+
1938
+ }
1939
+
1940
+ targets.push( target );
1941
+
1942
+ weights.push( mesh.morphTargetInfluences[ i ] );
1943
+
1944
+ if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] );
1945
+
1946
+ }
1947
+
1948
+ meshDef.weights = weights;
1949
+
1950
+ if ( targetNames.length > 0 ) {
1951
+
1952
+ meshDef.extras = {};
1953
+ meshDef.extras.targetNames = targetNames;
1954
+
1955
+ }
1956
+
1957
+ }
1958
+
1959
+ const isMultiMaterial = Array.isArray( mesh.material );
1960
+
1961
+ if ( isMultiMaterial && geometry.groups.length === 0 ) return null;
1962
+
1963
+ let didForceIndices = false;
1964
+
1965
+ if ( isMultiMaterial && geometry.index === null ) {
1966
+
1967
+ const indices = [];
1968
+
1969
+ for ( let i = 0, il = geometry.attributes.position.count; i < il; i ++ ) {
1970
+
1971
+ indices[ i ] = i;
1972
+
1973
+ }
1974
+
1975
+ geometry.setIndex( indices );
1976
+
1977
+ didForceIndices = true;
1978
+
1979
+ }
1980
+
1981
+ const materials = isMultiMaterial ? mesh.material : [ mesh.material ];
1982
+ const groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ];
1983
+
1984
+ for ( let i = 0, il = groups.length; i < il; i ++ ) {
1985
+
1986
+ const primitive = {
1987
+ mode: mode,
1988
+ attributes: attributes,
1989
+ };
1990
+
1991
+ this.serializeUserData( geometry, primitive );
1992
+
1993
+ if ( targets.length > 0 ) primitive.targets = targets;
1994
+
1995
+ if ( geometry.index !== null ) {
1996
+
1997
+ let cacheKey = this.getUID( geometry.index );
1998
+
1999
+ if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) {
2000
+
2001
+ cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count;
2002
+
2003
+ }
2004
+
2005
+ if ( cache.attributes.has( cacheKey ) ) {
2006
+
2007
+ primitive.indices = cache.attributes.get( cacheKey );
2008
+
2009
+ } else {
2010
+
2011
+ primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count );
2012
+ cache.attributes.set( cacheKey, primitive.indices );
2013
+
2014
+ }
2015
+
2016
+ if ( primitive.indices === null ) delete primitive.indices;
2017
+
2018
+ }
2019
+
2020
+ const material = await this.processMaterialAsync( materials[ groups[ i ].materialIndex ] );
2021
+
2022
+ if ( material !== null ) primitive.material = material;
2023
+
2024
+ primitives.push( primitive );
2025
+
2026
+ }
2027
+
2028
+ if ( didForceIndices === true ) {
2029
+
2030
+ geometry.setIndex( null );
2031
+
2032
+ }
2033
+
2034
+ meshDef.primitives = primitives;
2035
+
2036
+ if ( ! json.meshes ) json.meshes = [];
2037
+
2038
+ await this._invokeAllAsync( function ( ext ) {
2039
+
2040
+ ext.writeMesh && ext.writeMesh( mesh, meshDef );
2041
+
2042
+ } );
2043
+
2044
+ const index = json.meshes.push( meshDef ) - 1;
2045
+ cache.meshes.set( meshCacheKey, index );
2046
+ return index;
2047
+
2048
+ }
2049
+
2050
+ /**
2051
+ * If a vertex attribute with a
2052
+ * [non-standard data type](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview)
2053
+ * is used, it is checked whether it is a valid data type according to the
2054
+ * [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md)
2055
+ * extension.
2056
+ * In this case the extension is automatically added to the list of used extensions.
2057
+ *
2058
+ * @param {string} attributeName
2059
+ * @param {THREE.BufferAttribute} attribute
2060
+ */
2061
+ detectMeshQuantization( attributeName, attribute ) {
2062
+
2063
+ if ( this.extensionsUsed[ KHR_MESH_QUANTIZATION ] ) return;
2064
+
2065
+ let attrType = undefined;
2066
+
2067
+ switch ( attribute.array.constructor ) {
2068
+
2069
+ case Int8Array:
2070
+
2071
+ attrType = 'byte';
2072
+
2073
+ break;
2074
+
2075
+ case Uint8Array:
2076
+
2077
+ attrType = 'unsigned byte';
2078
+
2079
+ break;
2080
+
2081
+ case Int16Array:
2082
+
2083
+ attrType = 'short';
2084
+
2085
+ break;
2086
+
2087
+ case Uint16Array:
2088
+
2089
+ attrType = 'unsigned short';
2090
+
2091
+ break;
2092
+
2093
+ default:
2094
+
2095
+ return;
2096
+
2097
+ }
2098
+
2099
+ if ( attribute.normalized ) attrType += ' normalized';
2100
+
2101
+ const attrNamePrefix = attributeName.split( '_', 1 )[ 0 ];
2102
+
2103
+ if ( KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ] && KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ].includes( attrType ) ) {
2104
+
2105
+ this.extensionsUsed[ KHR_MESH_QUANTIZATION ] = true;
2106
+ this.extensionsRequired[ KHR_MESH_QUANTIZATION ] = true;
2107
+
2108
+ }
2109
+
2110
+ }
2111
+
2112
+ /**
2113
+ * Process camera
2114
+ * @param {THREE.Camera} camera Camera to process
2115
+ * @return {number} Index of the processed mesh in the "camera" array
2116
+ */
2117
+ processCamera( camera ) {
2118
+
2119
+ const json = this.json;
2120
+
2121
+ if ( ! json.cameras ) json.cameras = [];
2122
+
2123
+ const isOrtho = camera.isOrthographicCamera;
2124
+
2125
+ const cameraDef = {
2126
+ type: isOrtho ? 'orthographic' : 'perspective'
2127
+ };
2128
+
2129
+ if ( isOrtho ) {
2130
+
2131
+ cameraDef.orthographic = {
2132
+ xmag: camera.right * 2,
2133
+ ymag: camera.top * 2,
2134
+ zfar: camera.far <= 0 ? 0.001 : camera.far,
2135
+ znear: camera.near < 0 ? 0 : camera.near
2136
+ };
2137
+
2138
+ } else {
2139
+
2140
+ cameraDef.perspective = {
2141
+ aspectRatio: camera.aspect,
2142
+ yfov: THREE.MathUtils.degToRad( camera.fov ),
2143
+ zfar: camera.far <= 0 ? 0.001 : camera.far,
2144
+ znear: camera.near < 0 ? 0 : camera.near
2145
+ };
2146
+
2147
+ }
2148
+
2149
+ // Question: Is saving "type" as name intentional?
2150
+ if ( camera.name !== '' ) cameraDef.name = camera.type;
2151
+
2152
+ return json.cameras.push( cameraDef ) - 1;
2153
+
2154
+ }
2155
+
2156
+ /**
2157
+ * Creates glTF animation entry from AnimationClip object.
2158
+ *
2159
+ * Status:
2160
+ * - Only properties listed in PATH_PROPERTIES may be animated.
2161
+ *
2162
+ * @param {THREE.AnimationClip} clip
2163
+ * @param {THREE.Object3D} root
2164
+ * @return {number|null}
2165
+ */
2166
+ processAnimation( clip, root ) {
2167
+
2168
+ const json = this.json;
2169
+ const nodeMap = this.nodeMap;
2170
+
2171
+ if ( ! json.animations ) json.animations = [];
2172
+
2173
+ clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root );
2174
+
2175
+ const tracks = clip.tracks;
2176
+ const channels = [];
2177
+ const samplers = [];
2178
+
2179
+ for ( let i = 0; i < tracks.length; ++ i ) {
2180
+
2181
+ const track = tracks[ i ];
2182
+ const trackBinding = THREE.PropertyBinding.parseTrackName( track.name );
2183
+ let trackNode = THREE.PropertyBinding.findNode( root, trackBinding.nodeName );
2184
+ const trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ];
2185
+
2186
+ if ( trackBinding.objectName === 'bones' ) {
2187
+
2188
+ if ( trackNode.isSkinnedMesh === true ) {
2189
+
2190
+ trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex );
2191
+
2192
+ } else {
2193
+
2194
+ trackNode = undefined;
2195
+
2196
+ }
2197
+
2198
+ }
2199
+
2200
+ if ( ! trackNode || ! trackProperty ) {
2201
+
2202
+ console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name );
2203
+ continue;
2204
+
2205
+ }
2206
+
2207
+ const inputItemSize = 1;
2208
+ let outputItemSize = track.values.length / track.times.length;
2209
+
2210
+ if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) {
2211
+
2212
+ outputItemSize /= trackNode.morphTargetInfluences.length;
2213
+
2214
+ }
2215
+
2216
+ let interpolation;
2217
+
2218
+ // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE
2219
+
2220
+ // Detecting glTF cubic spline interpolant by checking factory method's special property
2221
+ // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return
2222
+ // valid value from .getInterpolation().
2223
+ if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) {
2224
+
2225
+ interpolation = 'CUBICSPLINE';
2226
+
2227
+ // itemSize of CUBICSPLINE keyframe is 9
2228
+ // (VEC3 * 3: inTangent, splineVertex, and outTangent)
2229
+ // but needs to be stored as VEC3 so dividing by 3 here.
2230
+ outputItemSize /= 3;
2231
+
2232
+ } else if ( track.getInterpolation() === THREE.InterpolateDiscrete ) {
2233
+
2234
+ interpolation = 'STEP';
2235
+
2236
+ } else {
2237
+
2238
+ interpolation = 'LINEAR';
2239
+
2240
+ }
2241
+
2242
+ samplers.push( {
2243
+ input: this.processAccessor( new THREE.BufferAttribute( track.times, inputItemSize ) ),
2244
+ output: this.processAccessor( new THREE.BufferAttribute( track.values, outputItemSize ) ),
2245
+ interpolation: interpolation
2246
+ } );
2247
+
2248
+ channels.push( {
2249
+ sampler: samplers.length - 1,
2250
+ target: {
2251
+ node: nodeMap.get( trackNode ),
2252
+ path: trackProperty
2253
+ }
2254
+ } );
2255
+
2256
+ }
2257
+
2258
+ json.animations.push( {
2259
+ name: clip.name || 'clip_' + json.animations.length,
2260
+ samplers: samplers,
2261
+ channels: channels
2262
+ } );
2263
+
2264
+ return json.animations.length - 1;
2265
+
2266
+ }
2267
+
2268
+ /**
2269
+ * @param {THREE.Object3D} object
2270
+ * @return {number|null}
2271
+ */
2272
+ processSkin( object ) {
2273
+
2274
+ const json = this.json;
2275
+ const nodeMap = this.nodeMap;
2276
+
2277
+ const node = json.nodes[ nodeMap.get( object ) ];
2278
+
2279
+ const skeleton = object.skeleton;
2280
+
2281
+ if ( skeleton === undefined ) return null;
2282
+
2283
+ const rootJoint = object.skeleton.bones[ 0 ];
2284
+
2285
+ if ( rootJoint === undefined ) return null;
2286
+
2287
+ const joints = [];
2288
+ const inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 );
2289
+ const temporaryBoneInverse = new THREE.Matrix4();
2290
+
2291
+ for ( let i = 0; i < skeleton.bones.length; ++ i ) {
2292
+
2293
+ joints.push( nodeMap.get( skeleton.bones[ i ] ) );
2294
+ temporaryBoneInverse.copy( skeleton.boneInverses[ i ] );
2295
+ temporaryBoneInverse.multiply( object.bindMatrix ).toArray( inverseBindMatrices, i * 16 );
2296
+
2297
+ }
2298
+
2299
+ if ( json.skins === undefined ) json.skins = [];
2300
+
2301
+ json.skins.push( {
2302
+ inverseBindMatrices: this.processAccessor( new THREE.BufferAttribute( inverseBindMatrices, 16 ) ),
2303
+ joints: joints,
2304
+ skeleton: nodeMap.get( rootJoint )
2305
+ } );
2306
+
2307
+ const skinIndex = node.skin = json.skins.length - 1;
2308
+
2309
+ return skinIndex;
2310
+
2311
+ }
2312
+
2313
+ /**
2314
+ * Process Object3D node
2315
+ * @param {THREE.Object3D} object Object3D to processNodeAsync
2316
+ * @return {Promise<number>} Index of the node in the nodes list
2317
+ */
2318
+ async processNodeAsync( object ) {
2319
+
2320
+ const json = this.json;
2321
+ const options = this.options;
2322
+ const nodeMap = this.nodeMap;
2323
+
2324
+ if ( ! json.nodes ) json.nodes = [];
2325
+
2326
+ const nodeDef = {};
2327
+
2328
+ if ( options.trs ) {
2329
+
2330
+ const rotation = object.quaternion.toArray();
2331
+ const position = object.position.toArray();
2332
+ const scale = object.scale.toArray();
2333
+
2334
+ if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) {
2335
+
2336
+ nodeDef.rotation = rotation;
2337
+
2338
+ }
2339
+
2340
+ if ( ! equalArray( position, [ 0, 0, 0 ] ) ) {
2341
+
2342
+ nodeDef.translation = position;
2343
+
2344
+ }
2345
+
2346
+ if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) {
2347
+
2348
+ nodeDef.scale = scale;
2349
+
2350
+ }
2351
+
2352
+ } else {
2353
+
2354
+ if ( object.matrixAutoUpdate ) {
2355
+
2356
+ object.updateMatrix();
2357
+
2358
+ }
2359
+
2360
+ if ( isIdentityMatrix( object.matrix ) === false ) {
2361
+
2362
+ nodeDef.matrix = object.matrix.elements;
2363
+
2364
+ }
2365
+
2366
+ }
2367
+
2368
+ // We don't export empty strings name because it represents no-name in Three.js.
2369
+ if ( object.name !== '' ) nodeDef.name = String( object.name );
2370
+
2371
+ this.serializeUserData( object, nodeDef );
2372
+
2373
+ if ( object.isMesh || object.isLine || object.isPoints ) {
2374
+
2375
+ const meshIndex = await this.processMeshAsync( object );
2376
+
2377
+ if ( meshIndex !== null ) nodeDef.mesh = meshIndex;
2378
+
2379
+ } else if ( object.isCamera ) {
2380
+
2381
+ nodeDef.camera = this.processCamera( object );
2382
+
2383
+ }
2384
+
2385
+ if ( object.isSkinnedMesh ) this.skins.push( object );
2386
+
2387
+ const nodeIndex = json.nodes.push( nodeDef ) - 1;
2388
+ nodeMap.set( object, nodeIndex );
2389
+
2390
+ if ( object.children.length > 0 ) {
2391
+
2392
+ const children = [];
2393
+
2394
+ for ( let i = 0, l = object.children.length; i < l; i ++ ) {
2395
+
2396
+ const child = object.children[ i ];
2397
+
2398
+ if ( child.visible || options.onlyVisible === false ) {
2399
+
2400
+ const childNodeIndex = await this.processNodeAsync( child );
2401
+
2402
+ if ( childNodeIndex !== null ) children.push( childNodeIndex );
2403
+
2404
+ }
2405
+
2406
+ }
2407
+
2408
+ if ( children.length > 0 ) nodeDef.children = children;
2409
+
2410
+ }
2411
+
2412
+ await this._invokeAllAsync( function ( ext ) {
2413
+
2414
+ ext.writeNode && ext.writeNode( object, nodeDef );
2415
+
2416
+ } );
2417
+
2418
+ return nodeIndex;
2419
+
2420
+ }
2421
+
2422
+ /**
2423
+ * Process Scene
2424
+ * @param {Scene} scene Scene to process
2425
+ */
2426
+ async processSceneAsync( scene ) {
2427
+
2428
+ const json = this.json;
2429
+ const options = this.options;
2430
+
2431
+ if ( ! json.scenes ) {
2432
+
2433
+ json.scenes = [];
2434
+ json.scene = 0;
2435
+
2436
+ }
2437
+
2438
+ const sceneDef = {};
2439
+
2440
+ if ( scene.name !== '' ) sceneDef.name = scene.name;
2441
+
2442
+ json.scenes.push( sceneDef );
2443
+
2444
+ const nodes = [];
2445
+
2446
+ for ( let i = 0, l = scene.children.length; i < l; i ++ ) {
2447
+
2448
+ const child = scene.children[ i ];
2449
+
2450
+ if ( child.visible || options.onlyVisible === false ) {
2451
+
2452
+ const nodeIndex = await this.processNodeAsync( child );
2453
+
2454
+ if ( nodeIndex !== null ) nodes.push( nodeIndex );
2455
+
2456
+ }
2457
+
2458
+ }
2459
+
2460
+ if ( nodes.length > 0 ) sceneDef.nodes = nodes;
2461
+
2462
+ this.serializeUserData( scene, sceneDef );
2463
+
2464
+ }
2465
+
2466
+ /**
2467
+ * Creates a Scene to hold a list of objects and parse it
2468
+ * @param {Array<THREE.Object3D>} objects List of objects to process
2469
+ */
2470
+ async processObjectsAsync( objects ) {
2471
+
2472
+ const scene = new THREE.Scene();
2473
+ scene.name = 'AuxScene';
2474
+
2475
+ for ( let i = 0; i < objects.length; i ++ ) {
2476
+
2477
+ // We push directly to children instead of calling `add` to prevent
2478
+ // modify the .parent and break its original scene and hierarchy
2479
+ scene.children.push( objects[ i ] );
2480
+
2481
+ }
2482
+
2483
+ await this.processSceneAsync( scene );
2484
+
2485
+ }
2486
+
2487
+ /**
2488
+ * @param {THREE.Object3D|Array<THREE.Object3D>} input
2489
+ */
2490
+ async processInputAsync( input ) {
2491
+
2492
+ const options = this.options;
2493
+
2494
+ input = input instanceof Array ? input : [ input ];
2495
+
2496
+ await this._invokeAllAsync( function ( ext ) {
2497
+
2498
+ ext.beforeParse && ext.beforeParse( input );
2499
+
2500
+ } );
2501
+
2502
+ const objectsWithoutScene = [];
2503
+
2504
+ for ( let i = 0; i < input.length; i ++ ) {
2505
+
2506
+ if ( input[ i ] instanceof THREE.Scene ) {
2507
+
2508
+ await this.processSceneAsync( input[ i ] );
2509
+
2510
+ } else {
2511
+
2512
+ objectsWithoutScene.push( input[ i ] );
2513
+
2514
+ }
2515
+
2516
+ }
2517
+
2518
+ if ( objectsWithoutScene.length > 0 ) {
2519
+
2520
+ await this.processObjectsAsync( objectsWithoutScene );
2521
+
2522
+ }
2523
+
2524
+ for ( let i = 0; i < this.skins.length; ++ i ) {
2525
+
2526
+ this.processSkin( this.skins[ i ] );
2527
+
2528
+ }
2529
+
2530
+ for ( let i = 0; i < options.animations.length; ++ i ) {
2531
+
2532
+ this.processAnimation( options.animations[ i ], input[ 0 ] );
2533
+
2534
+ }
2535
+
2536
+ await this._invokeAllAsync( function ( ext ) {
2537
+
2538
+ ext.afterParse && ext.afterParse( input );
2539
+
2540
+ } );
2541
+
2542
+ }
2543
+
2544
+ async _invokeAllAsync( func ) {
2545
+
2546
+ for ( let i = 0, il = this.plugins.length; i < il; i ++ ) {
2547
+
2548
+ await func( this.plugins[ i ] );
2549
+
2550
+ }
2551
+
2552
+ }
2553
+
2554
+ }
2555
+
2556
+ /**
2557
+ * Punctual Lights Extension
2558
+ *
2559
+ * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
2560
+ *
2561
+ * @private
2562
+ */
2563
+ class GLTFLightExtension {
2564
+
2565
+ constructor( writer ) {
2566
+
2567
+ this.writer = writer;
2568
+ this.name = 'KHR_lights_punctual';
2569
+
2570
+ }
2571
+
2572
+ writeNode( light, nodeDef ) {
2573
+
2574
+ if ( ! light.isLight ) return;
2575
+
2576
+ if ( ! light.isDirectionalLight && ! light.isPointLight && ! light.isSpotLight ) {
2577
+
2578
+ console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light );
2579
+ return;
2580
+
2581
+ }
2582
+
2583
+ const writer = this.writer;
2584
+ const json = writer.json;
2585
+ const extensionsUsed = writer.extensionsUsed;
2586
+
2587
+ const lightDef = {};
2588
+
2589
+ if ( light.name ) lightDef.name = light.name;
2590
+
2591
+ lightDef.color = light.color.toArray();
2592
+
2593
+ lightDef.intensity = light.intensity;
2594
+
2595
+ if ( light.isDirectionalLight ) {
2596
+
2597
+ lightDef.type = 'directional';
2598
+
2599
+ } else if ( light.isPointLight ) {
2600
+
2601
+ lightDef.type = 'point';
2602
+
2603
+ if ( light.distance > 0 ) lightDef.range = light.distance;
2604
+
2605
+ } else if ( light.isSpotLight ) {
2606
+
2607
+ lightDef.type = 'spot';
2608
+
2609
+ if ( light.distance > 0 ) lightDef.range = light.distance;
2610
+
2611
+ lightDef.spot = {};
2612
+ lightDef.spot.innerConeAngle = ( 1.0 - light.penumbra ) * light.angle;
2613
+ lightDef.spot.outerConeAngle = light.angle;
2614
+
2615
+ }
2616
+
2617
+ if ( light.decay !== undefined && light.decay !== 2 ) {
2618
+
2619
+ console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, '
2620
+ + 'and expects light.decay=2.' );
2621
+
2622
+ }
2623
+
2624
+ if ( light.target
2625
+ && ( light.target.parent !== light
2626
+ || light.target.position.x !== 0
2627
+ || light.target.position.y !== 0
2628
+ || light.target.position.z !== - 1 ) ) {
2629
+
2630
+ console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, '
2631
+ + 'make light.target a child of the light with position 0,0,-1.' );
2632
+
2633
+ }
2634
+
2635
+ if ( ! extensionsUsed[ this.name ] ) {
2636
+
2637
+ json.extensions = json.extensions || {};
2638
+ json.extensions[ this.name ] = { lights: [] };
2639
+ extensionsUsed[ this.name ] = true;
2640
+
2641
+ }
2642
+
2643
+ const lights = json.extensions[ this.name ].lights;
2644
+ lights.push( lightDef );
2645
+
2646
+ nodeDef.extensions = nodeDef.extensions || {};
2647
+ nodeDef.extensions[ this.name ] = { light: lights.length - 1 };
2648
+
2649
+ }
2650
+
2651
+ }
2652
+
2653
+ /**
2654
+ * Unlit Materials Extension
2655
+ *
2656
+ * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit
2657
+ *
2658
+ * @private
2659
+ */
2660
+ class GLTFMaterialsUnlitExtension {
2661
+
2662
+ constructor( writer ) {
2663
+
2664
+ this.writer = writer;
2665
+ this.name = 'KHR_materials_unlit';
2666
+
2667
+ }
2668
+
2669
+ async writeMaterialAsync( material, materialDef ) {
2670
+
2671
+ if ( ! material.isMeshBasicMaterial ) return;
2672
+
2673
+ const writer = this.writer;
2674
+ const extensionsUsed = writer.extensionsUsed;
2675
+
2676
+ materialDef.extensions = materialDef.extensions || {};
2677
+ materialDef.extensions[ this.name ] = {};
2678
+
2679
+ extensionsUsed[ this.name ] = true;
2680
+
2681
+ materialDef.pbrMetallicRoughness.metallicFactor = 0.0;
2682
+ materialDef.pbrMetallicRoughness.roughnessFactor = 0.9;
2683
+
2684
+ }
2685
+
2686
+ }
2687
+
2688
+ /**
2689
+ * Clearcoat Materials Extension
2690
+ *
2691
+ * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat
2692
+ *
2693
+ * @private
2694
+ */
2695
+ class GLTFMaterialsClearcoatExtension {
2696
+
2697
+ constructor( writer ) {
2698
+
2699
+ this.writer = writer;
2700
+ this.name = 'KHR_materials_clearcoat';
2701
+
2702
+ }
2703
+
2704
+ async writeMaterialAsync( material, materialDef ) {
2705
+
2706
+ if ( ! material.isMeshPhysicalMaterial || material.clearcoat === 0 ) return;
2707
+
2708
+ const writer = this.writer;
2709
+ const extensionsUsed = writer.extensionsUsed;
2710
+
2711
+ const extensionDef = {};
2712
+
2713
+ extensionDef.clearcoatFactor = material.clearcoat;
2714
+
2715
+ if ( material.clearcoatMap ) {
2716
+
2717
+ const clearcoatMapDef = {
2718
+ index: await writer.processTextureAsync( material.clearcoatMap ),
2719
+ texCoord: material.clearcoatMap.channel
2720
+ };
2721
+ writer.applyTextureTransform( clearcoatMapDef, material.clearcoatMap );
2722
+ extensionDef.clearcoatTexture = clearcoatMapDef;
2723
+
2724
+ }
2725
+
2726
+ extensionDef.clearcoatRoughnessFactor = material.clearcoatRoughness;
2727
+
2728
+ if ( material.clearcoatRoughnessMap ) {
2729
+
2730
+ const clearcoatRoughnessMapDef = {
2731
+ index: await writer.processTextureAsync( material.clearcoatRoughnessMap ),
2732
+ texCoord: material.clearcoatRoughnessMap.channel
2733
+ };
2734
+ writer.applyTextureTransform( clearcoatRoughnessMapDef, material.clearcoatRoughnessMap );
2735
+ extensionDef.clearcoatRoughnessTexture = clearcoatRoughnessMapDef;
2736
+
2737
+ }
2738
+
2739
+ if ( material.clearcoatNormalMap ) {
2740
+
2741
+ const clearcoatNormalMapDef = {
2742
+ index: await writer.processTextureAsync( material.clearcoatNormalMap ),
2743
+ texCoord: material.clearcoatNormalMap.channel
2744
+ };
2745
+
2746
+ if ( material.clearcoatNormalScale.x !== 1 ) clearcoatNormalMapDef.scale = material.clearcoatNormalScale.x;
2747
+
2748
+ writer.applyTextureTransform( clearcoatNormalMapDef, material.clearcoatNormalMap );
2749
+ extensionDef.clearcoatNormalTexture = clearcoatNormalMapDef;
2750
+
2751
+ }
2752
+
2753
+ materialDef.extensions = materialDef.extensions || {};
2754
+ materialDef.extensions[ this.name ] = extensionDef;
2755
+
2756
+ extensionsUsed[ this.name ] = true;
2757
+
2758
+
2759
+ }
2760
+
2761
+ }
2762
+
2763
+ /**
2764
+ * Materials dispersion Extension
2765
+ *
2766
+ * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_dispersion
2767
+ *
2768
+ * @private
2769
+ */
2770
+ class GLTFMaterialsDispersionExtension {
2771
+
2772
+ constructor( writer ) {
2773
+
2774
+ this.writer = writer;
2775
+ this.name = 'KHR_materials_dispersion';
2776
+
2777
+ }
2778
+
2779
+ async writeMaterialAsync( material, materialDef ) {
2780
+
2781
+ if ( ! material.isMeshPhysicalMaterial || material.dispersion === 0 ) return;
2782
+
2783
+ const writer = this.writer;
2784
+ const extensionsUsed = writer.extensionsUsed;
2785
+
2786
+ const extensionDef = {};
2787
+
2788
+ extensionDef.dispersion = material.dispersion;
2789
+
2790
+ materialDef.extensions = materialDef.extensions || {};
2791
+ materialDef.extensions[ this.name ] = extensionDef;
2792
+
2793
+ extensionsUsed[ this.name ] = true;
2794
+
2795
+ }
2796
+
2797
+ }
2798
+
2799
+ /**
2800
+ * Iridescence Materials Extension
2801
+ *
2802
+ * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence
2803
+ *
2804
+ * @private
2805
+ */
2806
+ class GLTFMaterialsIridescenceExtension {
2807
+
2808
+ constructor( writer ) {
2809
+
2810
+ this.writer = writer;
2811
+ this.name = 'KHR_materials_iridescence';
2812
+
2813
+ }
2814
+
2815
+ async writeMaterialAsync( material, materialDef ) {
2816
+
2817
+ if ( ! material.isMeshPhysicalMaterial || material.iridescence === 0 ) return;
2818
+
2819
+ const writer = this.writer;
2820
+ const extensionsUsed = writer.extensionsUsed;
2821
+
2822
+ const extensionDef = {};
2823
+
2824
+ extensionDef.iridescenceFactor = material.iridescence;
2825
+
2826
+ if ( material.iridescenceMap ) {
2827
+
2828
+ const iridescenceMapDef = {
2829
+ index: await writer.processTextureAsync( material.iridescenceMap ),
2830
+ texCoord: material.iridescenceMap.channel
2831
+ };
2832
+ writer.applyTextureTransform( iridescenceMapDef, material.iridescenceMap );
2833
+ extensionDef.iridescenceTexture = iridescenceMapDef;
2834
+
2835
+ }
2836
+
2837
+ extensionDef.iridescenceIor = material.iridescenceIOR;
2838
+ extensionDef.iridescenceThicknessMinimum = material.iridescenceThicknessRange[ 0 ];
2839
+ extensionDef.iridescenceThicknessMaximum = material.iridescenceThicknessRange[ 1 ];
2840
+
2841
+ if ( material.iridescenceThicknessMap ) {
2842
+
2843
+ const iridescenceThicknessMapDef = {
2844
+ index: await writer.processTextureAsync( material.iridescenceThicknessMap ),
2845
+ texCoord: material.iridescenceThicknessMap.channel
2846
+ };
2847
+ writer.applyTextureTransform( iridescenceThicknessMapDef, material.iridescenceThicknessMap );
2848
+ extensionDef.iridescenceThicknessTexture = iridescenceThicknessMapDef;
2849
+
2850
+ }
2851
+
2852
+ materialDef.extensions = materialDef.extensions || {};
2853
+ materialDef.extensions[ this.name ] = extensionDef;
2854
+
2855
+ extensionsUsed[ this.name ] = true;
2856
+
2857
+ }
2858
+
2859
+ }
2860
+
2861
+ /**
2862
+ * Transmission Materials Extension
2863
+ *
2864
+ * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission
2865
+ *
2866
+ * @private
2867
+ */
2868
+ class GLTFMaterialsTransmissionExtension {
2869
+
2870
+ constructor( writer ) {
2871
+
2872
+ this.writer = writer;
2873
+ this.name = 'KHR_materials_transmission';
2874
+
2875
+ }
2876
+
2877
+ async writeMaterialAsync( material, materialDef ) {
2878
+
2879
+ if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return;
2880
+
2881
+ const writer = this.writer;
2882
+ const extensionsUsed = writer.extensionsUsed;
2883
+
2884
+ const extensionDef = {};
2885
+
2886
+ extensionDef.transmissionFactor = material.transmission;
2887
+
2888
+ if ( material.transmissionMap ) {
2889
+
2890
+ const transmissionMapDef = {
2891
+ index: await writer.processTextureAsync( material.transmissionMap ),
2892
+ texCoord: material.transmissionMap.channel
2893
+ };
2894
+ writer.applyTextureTransform( transmissionMapDef, material.transmissionMap );
2895
+ extensionDef.transmissionTexture = transmissionMapDef;
2896
+
2897
+ }
2898
+
2899
+ materialDef.extensions = materialDef.extensions || {};
2900
+ materialDef.extensions[ this.name ] = extensionDef;
2901
+
2902
+ extensionsUsed[ this.name ] = true;
2903
+
2904
+ }
2905
+
2906
+ }
2907
+
2908
+ /**
2909
+ * Materials Volume Extension
2910
+ *
2911
+ * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume
2912
+ *
2913
+ * @private
2914
+ */
2915
+ class GLTFMaterialsVolumeExtension {
2916
+
2917
+ constructor( writer ) {
2918
+
2919
+ this.writer = writer;
2920
+ this.name = 'KHR_materials_volume';
2921
+
2922
+ }
2923
+
2924
+ async writeMaterialAsync( material, materialDef ) {
2925
+
2926
+ if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return;
2927
+
2928
+ const writer = this.writer;
2929
+ const extensionsUsed = writer.extensionsUsed;
2930
+
2931
+ const extensionDef = {};
2932
+
2933
+ extensionDef.thicknessFactor = material.thickness;
2934
+
2935
+ if ( material.thicknessMap ) {
2936
+
2937
+ const thicknessMapDef = {
2938
+ index: await writer.processTextureAsync( material.thicknessMap ),
2939
+ texCoord: material.thicknessMap.channel
2940
+ };
2941
+ writer.applyTextureTransform( thicknessMapDef, material.thicknessMap );
2942
+ extensionDef.thicknessTexture = thicknessMapDef;
2943
+
2944
+ }
2945
+
2946
+ if ( material.attenuationDistance !== Infinity ) {
2947
+
2948
+ extensionDef.attenuationDistance = material.attenuationDistance;
2949
+
2950
+ }
2951
+
2952
+ extensionDef.attenuationColor = material.attenuationColor.toArray();
2953
+
2954
+ materialDef.extensions = materialDef.extensions || {};
2955
+ materialDef.extensions[ this.name ] = extensionDef;
2956
+
2957
+ extensionsUsed[ this.name ] = true;
2958
+
2959
+ }
2960
+
2961
+ }
2962
+
2963
+ /**
2964
+ * Materials ior Extension
2965
+ *
2966
+ * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior
2967
+ *
2968
+ * @private
2969
+ */
2970
+ class GLTFMaterialsIorExtension {
2971
+
2972
+ constructor( writer ) {
2973
+
2974
+ this.writer = writer;
2975
+ this.name = 'KHR_materials_ior';
2976
+
2977
+ }
2978
+
2979
+ async writeMaterialAsync( material, materialDef ) {
2980
+
2981
+ if ( ! material.isMeshPhysicalMaterial || material.ior === 1.5 ) return;
2982
+
2983
+ const writer = this.writer;
2984
+ const extensionsUsed = writer.extensionsUsed;
2985
+
2986
+ const extensionDef = {};
2987
+
2988
+ extensionDef.ior = material.ior;
2989
+
2990
+ materialDef.extensions = materialDef.extensions || {};
2991
+ materialDef.extensions[ this.name ] = extensionDef;
2992
+
2993
+ extensionsUsed[ this.name ] = true;
2994
+
2995
+ }
2996
+
2997
+ }
2998
+
2999
+ /**
3000
+ * Materials specular Extension
3001
+ *
3002
+ * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular
3003
+ *
3004
+ * @private
3005
+ */
3006
+ class GLTFMaterialsSpecularExtension {
3007
+
3008
+ constructor( writer ) {
3009
+
3010
+ this.writer = writer;
3011
+ this.name = 'KHR_materials_specular';
3012
+
3013
+ }
3014
+
3015
+ async writeMaterialAsync( material, materialDef ) {
3016
+
3017
+ if ( ! material.isMeshPhysicalMaterial || ( material.specularIntensity === 1.0 &&
3018
+ material.specularColor.equals( DEFAULT_SPECULAR_COLOR ) &&
3019
+ ! material.specularIntensityMap && ! material.specularColorMap ) ) return;
3020
+
3021
+ const writer = this.writer;
3022
+ const extensionsUsed = writer.extensionsUsed;
3023
+
3024
+ const extensionDef = {};
3025
+
3026
+ if ( material.specularIntensityMap ) {
3027
+
3028
+ const specularIntensityMapDef = {
3029
+ index: await writer.processTextureAsync( material.specularIntensityMap ),
3030
+ texCoord: material.specularIntensityMap.channel
3031
+ };
3032
+ writer.applyTextureTransform( specularIntensityMapDef, material.specularIntensityMap );
3033
+ extensionDef.specularTexture = specularIntensityMapDef;
3034
+
3035
+ }
3036
+
3037
+ if ( material.specularColorMap ) {
3038
+
3039
+ const specularColorMapDef = {
3040
+ index: await writer.processTextureAsync( material.specularColorMap ),
3041
+ texCoord: material.specularColorMap.channel
3042
+ };
3043
+ writer.applyTextureTransform( specularColorMapDef, material.specularColorMap );
3044
+ extensionDef.specularColorTexture = specularColorMapDef;
3045
+
3046
+ }
3047
+
3048
+ extensionDef.specularFactor = material.specularIntensity;
3049
+ extensionDef.specularColorFactor = material.specularColor.toArray();
3050
+
3051
+ materialDef.extensions = materialDef.extensions || {};
3052
+ materialDef.extensions[ this.name ] = extensionDef;
3053
+
3054
+ extensionsUsed[ this.name ] = true;
3055
+
3056
+ }
3057
+
3058
+ }
3059
+
3060
+ /**
3061
+ * Sheen Materials Extension
3062
+ *
3063
+ * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen
3064
+ *
3065
+ * @private
3066
+ */
3067
+ class GLTFMaterialsSheenExtension {
3068
+
3069
+ constructor( writer ) {
3070
+
3071
+ this.writer = writer;
3072
+ this.name = 'KHR_materials_sheen';
3073
+
3074
+ }
3075
+
3076
+ async writeMaterialAsync( material, materialDef ) {
3077
+
3078
+ if ( ! material.isMeshPhysicalMaterial || material.sheen == 0.0 ) return;
3079
+
3080
+ const writer = this.writer;
3081
+ const extensionsUsed = writer.extensionsUsed;
3082
+
3083
+ const extensionDef = {};
3084
+
3085
+ if ( material.sheenRoughnessMap ) {
3086
+
3087
+ const sheenRoughnessMapDef = {
3088
+ index: await writer.processTextureAsync( material.sheenRoughnessMap ),
3089
+ texCoord: material.sheenRoughnessMap.channel
3090
+ };
3091
+ writer.applyTextureTransform( sheenRoughnessMapDef, material.sheenRoughnessMap );
3092
+ extensionDef.sheenRoughnessTexture = sheenRoughnessMapDef;
3093
+
3094
+ }
3095
+
3096
+ if ( material.sheenColorMap ) {
3097
+
3098
+ const sheenColorMapDef = {
3099
+ index: await writer.processTextureAsync( material.sheenColorMap ),
3100
+ texCoord: material.sheenColorMap.channel
3101
+ };
3102
+ writer.applyTextureTransform( sheenColorMapDef, material.sheenColorMap );
3103
+ extensionDef.sheenColorTexture = sheenColorMapDef;
3104
+
3105
+ }
3106
+
3107
+ extensionDef.sheenRoughnessFactor = material.sheenRoughness;
3108
+ extensionDef.sheenColorFactor = material.sheenColor.toArray();
3109
+
3110
+ materialDef.extensions = materialDef.extensions || {};
3111
+ materialDef.extensions[ this.name ] = extensionDef;
3112
+
3113
+ extensionsUsed[ this.name ] = true;
3114
+
3115
+ }
3116
+
3117
+ }
3118
+
3119
+ /**
3120
+ * Anisotropy Materials Extension
3121
+ *
3122
+ * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_anisotropy
3123
+ *
3124
+ * @private
3125
+ */
3126
+ class GLTFMaterialsAnisotropyExtension {
3127
+
3128
+ constructor( writer ) {
3129
+
3130
+ this.writer = writer;
3131
+ this.name = 'KHR_materials_anisotropy';
3132
+
3133
+ }
3134
+
3135
+ async writeMaterialAsync( material, materialDef ) {
3136
+
3137
+ if ( ! material.isMeshPhysicalMaterial || material.anisotropy == 0.0 ) return;
3138
+
3139
+ const writer = this.writer;
3140
+ const extensionsUsed = writer.extensionsUsed;
3141
+
3142
+ const extensionDef = {};
3143
+
3144
+ if ( material.anisotropyMap ) {
3145
+
3146
+ const anisotropyMapDef = { index: await writer.processTextureAsync( material.anisotropyMap ) };
3147
+ writer.applyTextureTransform( anisotropyMapDef, material.anisotropyMap );
3148
+ extensionDef.anisotropyTexture = anisotropyMapDef;
3149
+
3150
+ }
3151
+
3152
+ extensionDef.anisotropyStrength = material.anisotropy;
3153
+ extensionDef.anisotropyRotation = material.anisotropyRotation;
3154
+
3155
+ materialDef.extensions = materialDef.extensions || {};
3156
+ materialDef.extensions[ this.name ] = extensionDef;
3157
+
3158
+ extensionsUsed[ this.name ] = true;
3159
+
3160
+ }
3161
+
3162
+ }
3163
+
3164
+ /**
3165
+ * Materials Emissive Strength Extension
3166
+ *
3167
+ * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md
3168
+ *
3169
+ * @private
3170
+ */
3171
+ class GLTFMaterialsEmissiveStrengthExtension {
3172
+
3173
+ constructor( writer ) {
3174
+
3175
+ this.writer = writer;
3176
+ this.name = 'KHR_materials_emissive_strength';
3177
+
3178
+ }
3179
+
3180
+ async writeMaterialAsync( material, materialDef ) {
3181
+
3182
+ if ( ! material.isMeshStandardMaterial || material.emissiveIntensity === 1.0 ) return;
3183
+
3184
+ const writer = this.writer;
3185
+ const extensionsUsed = writer.extensionsUsed;
3186
+
3187
+ const extensionDef = {};
3188
+
3189
+ extensionDef.emissiveStrength = material.emissiveIntensity;
3190
+
3191
+ materialDef.extensions = materialDef.extensions || {};
3192
+ materialDef.extensions[ this.name ] = extensionDef;
3193
+
3194
+ extensionsUsed[ this.name ] = true;
3195
+
3196
+ }
3197
+
3198
+ }
3199
+
3200
+
3201
+ /**
3202
+ * Materials bump Extension
3203
+ *
3204
+ * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/EXT_materials_bump
3205
+ *
3206
+ * @private
3207
+ */
3208
+ class GLTFMaterialsBumpExtension {
3209
+
3210
+ constructor( writer ) {
3211
+
3212
+ this.writer = writer;
3213
+ this.name = 'EXT_materials_bump';
3214
+
3215
+ }
3216
+
3217
+ async writeMaterialAsync( material, materialDef ) {
3218
+
3219
+ if ( ! material.isMeshStandardMaterial || (
3220
+ material.bumpScale === 1 &&
3221
+ ! material.bumpMap ) ) return;
3222
+
3223
+ const writer = this.writer;
3224
+ const extensionsUsed = writer.extensionsUsed;
3225
+
3226
+ const extensionDef = {};
3227
+
3228
+ if ( material.bumpMap ) {
3229
+
3230
+ const bumpMapDef = {
3231
+ index: await writer.processTextureAsync( material.bumpMap ),
3232
+ texCoord: material.bumpMap.channel
3233
+ };
3234
+ writer.applyTextureTransform( bumpMapDef, material.bumpMap );
3235
+ extensionDef.bumpTexture = bumpMapDef;
3236
+
3237
+ }
3238
+
3239
+ extensionDef.bumpFactor = material.bumpScale;
3240
+
3241
+ materialDef.extensions = materialDef.extensions || {};
3242
+ materialDef.extensions[ this.name ] = extensionDef;
3243
+
3244
+ extensionsUsed[ this.name ] = true;
3245
+
3246
+ }
3247
+
3248
+ }
3249
+
3250
+ /**
3251
+ * GPU Instancing Extension
3252
+ *
3253
+ * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing
3254
+ *
3255
+ * @private
3256
+ */
3257
+ class GLTFMeshGpuInstancing {
3258
+
3259
+ constructor( writer ) {
3260
+
3261
+ this.writer = writer;
3262
+ this.name = 'EXT_mesh_gpu_instancing';
3263
+
3264
+ }
3265
+
3266
+ writeNode( object, nodeDef ) {
3267
+
3268
+ if ( ! object.isInstancedMesh ) return;
3269
+
3270
+ const writer = this.writer;
3271
+
3272
+ const mesh = object;
3273
+
3274
+ const translationAttr = new Float32Array( mesh.count * 3 );
3275
+ const rotationAttr = new Float32Array( mesh.count * 4 );
3276
+ const scaleAttr = new Float32Array( mesh.count * 3 );
3277
+
3278
+ const matrix = new THREE.Matrix4();
3279
+ const position = new THREE.Vector3();
3280
+ const quaternion = new THREE.Quaternion();
3281
+ const scale = new THREE.Vector3();
3282
+
3283
+ for ( let i = 0; i < mesh.count; i ++ ) {
3284
+
3285
+ mesh.getMatrixAt( i, matrix );
3286
+ matrix.decompose( position, quaternion, scale );
3287
+
3288
+ position.toArray( translationAttr, i * 3 );
3289
+ quaternion.toArray( rotationAttr, i * 4 );
3290
+ scale.toArray( scaleAttr, i * 3 );
3291
+
3292
+ }
3293
+
3294
+ const attributes = {
3295
+ TRANSLATION: writer.processAccessor( new THREE.BufferAttribute( translationAttr, 3 ) ),
3296
+ ROTATION: writer.processAccessor( new THREE.BufferAttribute( rotationAttr, 4 ) ),
3297
+ SCALE: writer.processAccessor( new THREE.BufferAttribute( scaleAttr, 3 ) ),
3298
+ };
3299
+
3300
+ if ( mesh.instanceColor )
3301
+ attributes._COLOR_0 = writer.processAccessor( mesh.instanceColor );
3302
+
3303
+ nodeDef.extensions = nodeDef.extensions || {};
3304
+ nodeDef.extensions[ this.name ] = { attributes };
3305
+
3306
+ writer.extensionsUsed[ this.name ] = true;
3307
+ writer.extensionsRequired[ this.name ] = true;
3308
+
3309
+ }
3310
+
3311
+ }
3312
+
3313
+ /**
3314
+ * Static utility functions
3315
+ *
3316
+ * @private
3317
+ */
3318
+ GLTFExporter.Utils = {
3319
+
3320
+ insertKeyframe: function ( track, time ) {
3321
+
3322
+ const tolerance = 0.001; // 1ms
3323
+ const valueSize = track.getValueSize();
3324
+
3325
+ const times = new track.TimeBufferType( track.times.length + 1 );
3326
+ const values = new track.ValueBufferType( track.values.length + valueSize );
3327
+ const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) );
3328
+
3329
+ let index;
3330
+
3331
+ if ( track.times.length === 0 ) {
3332
+
3333
+ times[ 0 ] = time;
3334
+
3335
+ for ( let i = 0; i < valueSize; i ++ ) {
3336
+
3337
+ values[ i ] = 0;
3338
+
3339
+ }
3340
+
3341
+ index = 0;
3342
+
3343
+ } else if ( time < track.times[ 0 ] ) {
3344
+
3345
+ if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0;
3346
+
3347
+ times[ 0 ] = time;
3348
+ times.set( track.times, 1 );
3349
+
3350
+ values.set( interpolant.evaluate( time ), 0 );
3351
+ values.set( track.values, valueSize );
3352
+
3353
+ index = 0;
3354
+
3355
+ } else if ( time > track.times[ track.times.length - 1 ] ) {
3356
+
3357
+ if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) {
3358
+
3359
+ return track.times.length - 1;
3360
+
3361
+ }
3362
+
3363
+ times[ times.length - 1 ] = time;
3364
+ times.set( track.times, 0 );
3365
+
3366
+ values.set( track.values, 0 );
3367
+ values.set( interpolant.evaluate( time ), track.values.length );
3368
+
3369
+ index = times.length - 1;
3370
+
3371
+ } else {
3372
+
3373
+ for ( let i = 0; i < track.times.length; i ++ ) {
3374
+
3375
+ if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i;
3376
+
3377
+ if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) {
3378
+
3379
+ times.set( track.times.slice( 0, i + 1 ), 0 );
3380
+ times[ i + 1 ] = time;
3381
+ times.set( track.times.slice( i + 1 ), i + 2 );
3382
+
3383
+ values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 );
3384
+ values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize );
3385
+ values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize );
3386
+
3387
+ index = i + 1;
3388
+
3389
+ break;
3390
+
3391
+ }
3392
+
3393
+ }
3394
+
3395
+ }
3396
+
3397
+ track.times = times;
3398
+ track.values = values;
3399
+
3400
+ return index;
3401
+
3402
+ },
3403
+
3404
+ mergeMorphTargetTracks: function ( clip, root ) {
3405
+
3406
+ const tracks = [];
3407
+ const mergedTracks = {};
3408
+ const sourceTracks = clip.tracks;
3409
+
3410
+ for ( let i = 0; i < sourceTracks.length; ++ i ) {
3411
+
3412
+ let sourceTrack = sourceTracks[ i ];
3413
+ const sourceTrackBinding = THREE.PropertyBinding.parseTrackName( sourceTrack.name );
3414
+ const sourceTrackNode = THREE.PropertyBinding.findNode( root, sourceTrackBinding.nodeName );
3415
+
3416
+ if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) {
3417
+
3418
+ // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is.
3419
+ tracks.push( sourceTrack );
3420
+ continue;
3421
+
3422
+ }
3423
+
3424
+ if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete
3425
+ && sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) {
3426
+
3427
+ if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
3428
+
3429
+ // This should never happen, because glTF morph target animations
3430
+ // affect all targets already.
3431
+ throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' );
3432
+
3433
+ }
3434
+
3435
+ console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' );
3436
+
3437
+ sourceTrack = sourceTrack.clone();
3438
+ sourceTrack.setInterpolation( THREE.InterpolateLinear );
3439
+
3440
+ }
3441
+
3442
+ const targetCount = sourceTrackNode.morphTargetInfluences.length;
3443
+ const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ];
3444
+
3445
+ if ( targetIndex === undefined ) {
3446
+
3447
+ throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex );
3448
+
3449
+ }
3450
+
3451
+ let mergedTrack;
3452
+
3453
+ // If this is the first time we've seen this object, create a new
3454
+ // track to store merged keyframe data for each morph target.
3455
+ if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) {
3456
+
3457
+ mergedTrack = sourceTrack.clone();
3458
+
3459
+ const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length );
3460
+
3461
+ for ( let j = 0; j < mergedTrack.times.length; j ++ ) {
3462
+
3463
+ values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ];
3464
+
3465
+ }
3466
+
3467
+ // We need to take into consideration the intended target node
3468
+ // of our original un-merged morphTarget animation.
3469
+ mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences';
3470
+ mergedTrack.values = values;
3471
+
3472
+ mergedTracks[ sourceTrackNode.uuid ] = mergedTrack;
3473
+ tracks.push( mergedTrack );
3474
+
3475
+ continue;
3476
+
3477
+ }
3478
+
3479
+ const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) );
3480
+
3481
+ mergedTrack = mergedTracks[ sourceTrackNode.uuid ];
3482
+
3483
+ // For every existing keyframe of the merged track, write a (possibly
3484
+ // interpolated) value from the source track.
3485
+ for ( let j = 0; j < mergedTrack.times.length; j ++ ) {
3486
+
3487
+ mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] );
3488
+
3489
+ }
3490
+
3491
+ // For every existing keyframe of the source track, write a (possibly
3492
+ // new) keyframe to the merged track. Values from the previous loop may
3493
+ // be written again, but keyframes are de-duplicated.
3494
+ for ( let j = 0; j < sourceTrack.times.length; j ++ ) {
3495
+
3496
+ const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] );
3497
+ mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ];
3498
+
3499
+ }
3500
+
3501
+ }
3502
+
3503
+ clip.tracks = tracks;
3504
+
3505
+ return clip;
3506
+
3507
+ },
3508
+
3509
+ toFloat32BufferAttribute: function ( srcAttribute ) {
3510
+
3511
+ const dstAttribute = new THREE.BufferAttribute( new Float32Array( srcAttribute.count * srcAttribute.itemSize ), srcAttribute.itemSize, false );
3512
+
3513
+ if ( ! srcAttribute.normalized && ! srcAttribute.isInterleavedBufferAttribute ) {
3514
+
3515
+ dstAttribute.array.set( srcAttribute.array );
3516
+
3517
+ return dstAttribute;
3518
+
3519
+ }
3520
+
3521
+ for ( let i = 0, il = srcAttribute.count; i < il; i ++ ) {
3522
+
3523
+ for ( let j = 0; j < srcAttribute.itemSize; j ++ ) {
3524
+
3525
+ dstAttribute.setComponent( i, j, srcAttribute.getComponent( i, j ) );
3526
+
3527
+ }
3528
+
3529
+ }
3530
+
3531
+ return dstAttribute;
3532
+
3533
+ }
3534
+
3535
+ };
3536
+
3537
+ exports.GLTFExporter = GLTFExporter;