@needle-tools/gltf-progressive 4.0.0-alpha → 4.0.0-alpha.1
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.
- package/gltf-progressive.js +1568 -0
- package/gltf-progressive.min.js +10 -0
- package/gltf-progressive.umd.cjs +10 -0
- package/lib/extension.d.ts +142 -0
- package/lib/extension.js +1133 -0
- package/lib/extension.model.d.ts +33 -0
- package/lib/extension.model.js +1 -0
- package/lib/index.d.ts +22 -0
- package/lib/index.js +87 -0
- package/lib/loaders.d.ts +41 -0
- package/lib/loaders.js +162 -0
- package/lib/lods.debug.d.ts +4 -0
- package/lib/lods.debug.js +43 -0
- package/lib/lods.manager.d.ts +165 -0
- package/lib/lods.manager.js +749 -0
- package/lib/lods.promise.d.ts +68 -0
- package/lib/lods.promise.js +108 -0
- package/lib/plugins/index.d.ts +2 -0
- package/lib/plugins/index.js +1 -0
- package/lib/plugins/modelviewer.d.ts +1 -0
- package/lib/plugins/modelviewer.js +223 -0
- package/lib/plugins/plugin.d.ts +23 -0
- package/lib/plugins/plugin.js +5 -0
- package/lib/utils.d.ts +30 -0
- package/lib/utils.internal.d.ts +68 -0
- package/lib/utils.internal.js +239 -0
- package/lib/utils.js +82 -0
- package/lib/version.d.ts +1 -0
- package/lib/version.js +4 -0
- package/lib/worker/gltf-progressive.worker.js +165 -0
- package/lib/worker/loader.mainthread.d.ts +45 -0
- package/lib/worker/loader.mainthread.js +192 -0
- package/package.json +4 -17
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import{RedFormat as Ue,RedIntegerFormat as ze,RGFormat as qe,RGIntegerFormat as Ve,RGBFormat as Xe,RGBAFormat as Ke,RGBAIntegerFormat as He,BufferGeometry as W,Mesh as U,Box3 as ie,Vector3 as A,Sphere as Me,CompressedTexture as Ye,Texture as $,Matrix3 as Qe,InterleavedBuffer as Je,InterleavedBufferAttribute as Ze,BufferAttribute as et,TextureLoader as tt,Matrix4 as De,Timer as rt,MeshStandardMaterial as st}from"three";import{GLTFLoader as ae}from"three/examples/jsm/loaders/GLTFLoader.js";import{MeshoptDecoder as ot}from"three/examples/jsm/libs/meshopt_decoder.module.js";import{DRACOLoader as nt}from"three/examples/jsm/loaders/DRACOLoader.js";import{KTX2Loader as it}from"three/examples/jsm/loaders/KTX2Loader.js";const Oe="";globalThis.GLTF_PROGRESSIVE_VERSION=Oe,console.debug("[gltf-progressive] version -");let k="https://www.gstatic.com/draco/versioned/decoders/1.5.7/",z="https://cdn.needle.tools/static/three/0.179.1/basis2/";const at=k,lt=z,Se=new URL(k+"draco_decoder.js");Se.searchParams.append("range","true"),fetch(Se,{method:"GET",headers:{Range:"bytes=0-1"}}).catch(i=>{console.debug(`Failed to fetch remote Draco decoder from ${k} (offline: ${typeof navigator<"u"?navigator.onLine:"unknown"})`),k===at&&Te("./include/draco/"),z===lt&&Pe("./include/ktx2/")}).finally(()=>{Ce()});const ut=()=>({dracoDecoderPath:k,ktx2TranscoderPath:z});function Te(i){k=i,T&&T[ue]!=k?(console.debug("Updating Draco decoder path to "+i),T[ue]=k,T.setDecoderPath(k),T.preload()):console.debug("Setting Draco decoder path to "+i)}function Pe(i){z=i,R&&R.transcoderPath!=z?(console.debug("Updating KTX2 transcoder path to "+i),R.setTranscoderPath(z),R.init()):console.debug("Setting KTX2 transcoder path to "+i)}function ee(i){return Ce(),i?R.detectSupport(i):i!==null&&console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures might fail"),{dracoLoader:T,ktx2Loader:R,meshoptDecoder:te}}function le(i){i.dracoLoader||i.setDRACOLoader(T),i.ktx2Loader||i.setKTX2Loader(R),i.meshoptDecoder||i.setMeshoptDecoder(te)}const ue=Symbol("dracoDecoderPath");let T,te,R;function Ce(){T||(T=new nt,T[ue]=k,T.setDecoderPath(k),T.setDecoderConfig({type:"js"}),T.preload()),R||(R=new it,R.setTranscoderPath(z),R.init()),te||(te=ot)}const ce=new WeakMap;function de(i,t){let e=ce.get(i);e?e=Object.assign(e,t):e=t,ce.set(i,e)}const ct=ae.prototype.load;function dt(...i){const t=ce.get(this);let e=i[0];const r=new URL(e,window.location.href);if(r.hostname.endsWith("needle.tools")){const s=t?.progressive!==void 0?t.progressive:!0,o=t?.usecase?t.usecase:"default";s?this.requestHeader.Accept=`*/*;progressive=allowed;usecase=${o}`:this.requestHeader.Accept=`*/*;usecase=${o}`,e=r.toString()}return i[0]=e,ct?.call(this,...i)}ae.prototype.load=dt;function Ae(i){return i!=null&&i.data!=null}function he(i){const t=i.source?.data;return t!=null&&typeof t=="object"?t:null}function ht(i){const t=i.image;return t!=null&&typeof t=="object"?t:null}function ge(i){const t=ht(i),e=he(i);return{width:t?.width||e?.width||0,height:t?.height||e?.height||0}}j("debugprogressive");function j(i){if(typeof window>"u")return!1;const t=new URL(window.location.href).searchParams.get(i);return t==null||t==="0"||t==="false"?!1:t===""?!0:t}function gt(i,t){if(t===void 0||i===void 0||t.startsWith("./")||t.startsWith("http")||t.startsWith("data:")||t.startsWith("blob:"))return t;const e=i.lastIndexOf("/");if(e>=0){const r=i.substring(0,e+1);for(;r.endsWith("/")&&t.startsWith("/");)t=t.substring(1);return r+t}return t}function fe(){return re!==void 0||(re=/iPhone|iPad|iPod|Android|IEMobile/i.test(navigator.userAgent),j("debugprogressive")&&console.log("[glTF Progressive]: isMobileDevice",re)),re}let re;function ke(){if(typeof window>"u")return!1;const i=new URL(window.location.href),t=i.hostname==="localhost"||/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(i.hostname);return i.hostname==="127.0.0.1"||t}class ft{constructor(t,e={}){this.maxConcurrent=t,this.debug=e.debug??!1,window.requestAnimationFrame(this.tick)}_running=new Map;_queue=[];debug=!1;tick=()=>{this.internalUpdate(),setTimeout(this.tick,10)};slot(t){return this.debug&&console.debug(`[PromiseQueue]: Requesting slot for key ${t}, running: ${this._running.size}, waiting: ${this._queue.length}`),new Promise(e=>{this._queue.push({key:t,resolve:e})})}add(t,e){this._running.has(t)||(this._running.set(t,e),e.finally(()=>{this._running.delete(t),this.debug&&console.debug(`[PromiseQueue]: Promise finished now running: ${this._running.size}, waiting: ${this._queue.length}. (finished ${t})`)}),this.debug&&console.debug(`[PromiseQueue]: Added new promise, now running: ${this._running.size}, waiting: ${this._queue.length}. (added ${t})`))}internalUpdate(){const t=this.maxConcurrent-this._running.size;for(let e=0;e<t&&this._queue.length>0;e++){this.debug&&console.debug(`[PromiseQueue]: Running ${this._running.size} promises, waiting for ${this._queue.length} more.`);const{key:r,resolve:s}=this._queue.shift();s({use:o=>this.add(r,o)})}}}function mt(i){const t=i.image,e=t?.width??0,r=t?.height??0,s=t?.depth??1,o=Math.floor(Math.log2(Math.max(e,r,s)))+1,n=pt(i);return e*r*s*n*(1-Math.pow(.25,o))/(1-.25)}function pt(i){let t=4;const e=i.format;e===Ue||e===ze?t=1:e===qe||e===Ve?t=2:e===Xe||e===1029?t=3:(e===Ke||e===He)&&(t=4);let r=1;const s=i.type;return s===1009||s===1010?r=1:s===1011||s===1012?r=2:s===1013||s===1014||s===1015?r=4:s===1016&&(r=2),t*r}const xt=typeof window>"u"&&typeof document>"u",me=Symbol("needle:raycast-mesh");function V(i){return i?.[me]instanceof W?i[me]:null}function Re(i,t){if((i.type==="Mesh"||i.type==="SkinnedMesh")&&!V(i)){const e=yt(t);e.userData={isRaycastMesh:!0},i[me]=e}}function Ie(i=!0){if(i){if(K)return;const t=K=U.prototype.raycast;U.prototype.raycast=function(e,r){const s=this,o=V(s);let n;o&&s.isMesh&&(n=s.geometry,s.geometry=o),t.call(this,e,r),n&&(s.geometry=n)}}else{if(!K)return;U.prototype.raycast=K,K=null}}let K=null;function yt(i){const t=new W;for(const e in i.attributes)t.setAttribute(e,i.getAttribute(e));return t.setIndex(i.getIndex()),t}const N=new Array,h=j("debugprogressive");let H,q=-1;if(h){let i=function(){q+=1,q>=t&&(q=-1),console.log(`Toggle LOD level [${q}]`)},t=6;window.addEventListener("keyup",e=>{e.key==="p"&&i(),e.key==="w"&&(H=!H,console.log(`Toggle wireframe [${H}]`));const r=parseInt(e.key);!isNaN(r)&&r>=0&&(q=r,console.log(`Set LOD level to [${q}]`))})}function Ee(i){if(h&&H!==void 0)if(Array.isArray(i))for(const t of i)Ee(t);else i&&"wireframe"in i&&(i.wireframe=H===!0)}const Y=new Array;let wt=0;const vt=fe()?2:10;function Lt(i){if(Y.length<vt){const e=Y.length;h&&console.warn(`[Worker] Creating new worker #${e}`);const r=be.createWorker(i||{});return Y.push(r),r}const t=wt++%Y.length;return Y[t]}class be{constructor(t,e){this.worker=t,this._debug=e.debug??!1,t.onmessage=r=>{const s=r.data;switch(this._debug&&console.log("[Worker] EVENT",s),s.type){case"loaded-gltf":for(const o of this._running)if(o.url===s.result.url){_t(s.result),o.resolve(s.result);const n=o.url;n.startsWith("blob:")&&URL.revokeObjectURL(n)}}},t.onerror=r=>{console.error("[Worker] Error in gltf-progressive worker:",r)},t.postMessage({type:"init"})}static async createWorker(t){const e=new Worker(new URL("/assets/gltf-progressive.worker-CDSrhw-p.js",import.meta.url),{type:"module"});return new be(e,t)}_running=[];_webglRenderer=null;async load(t,e){const r=ut();let s=e?.renderer;s||(this._webglRenderer??=(async()=>{const{WebGLRenderer:l}=await import("three");return new l})(),s=await this._webglRenderer);const o=ee(s).ktx2Loader.workerConfig;t instanceof URL?t=t.toString():t.startsWith("file:")?t=URL.createObjectURL(new Blob([t])):!t.startsWith("blob:")&&!t.startsWith("http:")&&!t.startsWith("https:")&&(t=new URL(t,window.location.href).toString());const n={type:"load",url:t,dracoDecoderPath:r.dracoDecoderPath,ktx2TranscoderPath:r.ktx2TranscoderPath,ktx2LoaderConfig:o};return this._debug&&console.debug("[Worker] Sending load request",n),this.worker.postMessage(n),new Promise(l=>{this._running.push({url:t.toString(),resolve:l})})}_debug=!1}function _t(i){for(const t of i.geometries){const e=t.geometry,r=new W;if(r.name=e.name||"",e.index){const s=e.index;r.setIndex(pe(s))}for(const s in e.attributes){const o=e.attributes[s],n=pe(o);r.setAttribute(s,n)}if(e.morphAttributes)for(const s in e.morphAttributes){const o=e.morphAttributes[s].map(n=>pe(n));r.morphAttributes[s]=o}if(r.morphTargetsRelative=e.morphTargetsRelative??!1,r.boundingBox=new ie,r.boundingBox.min=new A(e.boundingBox?.min.x,e.boundingBox?.min.y,e.boundingBox?.min.z),r.boundingBox.max=new A(e.boundingBox?.max.x,e.boundingBox?.max.y,e.boundingBox?.max.z),r.boundingSphere=new Me(new A(e.boundingSphere?.center.x,e.boundingSphere?.center.y,e.boundingSphere?.center.z),e.boundingSphere?.radius),e.groups)for(const s of e.groups)r.addGroup(s.start,s.count,s.materialIndex);e.userData&&(r.userData=e.userData),t.geometry=r}for(const t of i.textures){const e=t.texture;let r=null;if(e.isCompressedTexture){const s=e.mipmaps,{width:o,height:n}=ge(e);r=new Ye(s,o,n,e.format,e.type,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.anisotropy,e.colorSpace)}else r=new $(e.image,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.format,e.type,e.anisotropy,e.colorSpace),r.mipmaps=e.mipmaps,r.channel=e.channel,r.source.data=e.source.data,r.flipY=e.flipY,r.premultiplyAlpha=e.premultiplyAlpha,r.unpackAlignment=e.unpackAlignment,r.matrix=new Qe(...e.matrix.elements);if(!r){console.error("[Worker] Failed to create new texture from received data. Texture is not a CompressedTexture or Texture.");continue}t.texture=r}return i}function pe(i){let t=i;if("isInterleavedBufferAttribute"in i&&i.isInterleavedBufferAttribute){const e=i.data,r=e.array,s=new Je(r,e.stride);t=new Ze(s,i.itemSize,r.byteOffset,i.normalized),t.offset=i.offset}else"isBufferAttribute"in i&&i.isBufferAttribute&&(t=new et(i.array,i.itemSize,i.normalized),t.usage=i.usage,t.gpuType=i.gpuType,t.updateRanges=i.updateRanges);return t}const bt=j("gltf-progressive-worker");j("gltf-progressive-reduce-mipmaps");const X=j("gltf-progressive-gc"),xe=Symbol("needle-progressive-texture"),B="NEEDLE_progressive";class f{get name(){return B}static getMeshLODExtension(t){const e=this.getAssignedLODInformation(t);return e?.key?this.lodInfos.get(e.key):null}static getPrimitiveIndex(t){return this.getAssignedLODInformation(t)?.index??-1}static getMaterialMinMaxLODsCount(t,e){const r=this,s="LODS:minmax",o=t[s];if(o!=null)return o;if(e||(e={min_count:1/0,max_count:0,lods:[]}),Array.isArray(t)){for(const l of t)this.getMaterialMinMaxLODsCount(l,e);return t[s]=e,e}if(h==="verbose"&&console.log("getMaterialMinMaxLODsCount",t),t.type==="ShaderMaterial"||t.type==="RawShaderMaterial"){const l=t;for(const a of Object.keys(l.uniforms)){const u=l.uniforms[a].value;u?.isTexture===!0&&n(u,e)}}else if(t.isMaterial)for(const l of Object.keys(t)){const a=t[l];a?.isTexture===!0&&n(a,e)}else h&&console.warn(`[getMaterialMinMaxLODsCount] Unsupported material type: ${t.type}`);return t[s]=e,e;function n(l,a){const u=r.getAssignedLODInformation(l);if(u){const y=r.lodInfos.get(u.key);if(y&&y.lods){a.min_count=Math.min(a.min_count,y.lods.length),a.max_count=Math.max(a.max_count,y.lods.length);for(let p=0;p<y.lods.length;p++){const w=y.lods[p];w.width&&(a.lods[p]=a.lods[p]||{min_height:1/0,max_height:0},a.lods[p].min_height=Math.min(a.lods[p].min_height,w.height),a.lods[p].max_height=Math.max(a.lods[p].max_height,w.height))}}}}}static hasLODLevelAvailable(t,e){if(Array.isArray(t)){for(const o of t)if(this.hasLODLevelAvailable(o,e))return!0;return!1}if(t.isMaterial===!0){for(const o of Object.keys(t)){const n=t[o];if(n&&n.isTexture&&this.hasLODLevelAvailable(n,e))return!0}return!1}else if(t.isGroup===!0){for(const o of t.children)if(o.isMesh===!0&&this.hasLODLevelAvailable(o,e))return!0}let r,s;if(t.isMesh?r=t.geometry:(t.isBufferGeometry||t.isTexture)&&(r=t),r&&r?.userData?.LODS){const o=r.userData.LODS;if(s=this.lodInfos.get(o.key),e===void 0)return s!=null;if(s)return Array.isArray(s.lods)?e<s.lods.length:e===0}return!1}static assignMeshLOD(t,e){if(!t)return Promise.resolve(null);if(t instanceof U||t.isMesh===!0){const r=t.geometry,s=this.getAssignedLODInformation(r);if(!s)return Promise.resolve(null);for(const o of N)o.onBeforeGetLODMesh?.(t,e);return t["LOD:requested level"]=e,f.getOrLoadLOD(r,e).then(o=>{if(Array.isArray(o)){const n=s.index||0;o=o[n]}return t["LOD:requested level"]===e&&(delete t["LOD:requested level"],o&&r!=o&&(o?.isBufferGeometry?t.geometry=o:h&&console.error("Invalid LOD geometry",o))),o}).catch(o=>(console.error("Error loading mesh LOD",t,o),null))}else h&&console.error("Invalid call to assignMeshLOD: Request mesh LOD but the object is not a mesh",t);return Promise.resolve(null)}static assignTextureLOD(t,e=0){if(!t)return Promise.resolve(null);if(t.isMesh===!0){const r=t;if(Array.isArray(r.material)){const s=new Array;for(const o of r.material){const n=this.assignTextureLOD(o,e);s.push(n)}return Promise.all(s).then(o=>{const n=new Array;for(const l of o)Array.isArray(l)&&n.push(...l);return n})}else return this.assignTextureLOD(r.material,e)}if(t.isMaterial===!0){const r=t,s=[],o=new Array;if(r.uniforms&&(r.isRawShaderMaterial||r.isShaderMaterial===!0)){const n=r;for(const l of Object.keys(n.uniforms)){const a=n.uniforms[l].value;if(a?.isTexture===!0){const u=this.assignTextureLODForSlot(a,e,r,l).then(y=>(y&&n.uniforms[l].value!=y&&(n.uniforms[l].value=y,n.uniformsNeedUpdate=!0),y));s.push(u),o.push(l)}}}else for(const n of Object.keys(r)){const l=r[n];if(l?.isTexture===!0){const a=this.assignTextureLODForSlot(l,e,r,n);s.push(a),o.push(n)}}return Promise.all(s).then(n=>{const l=new Array;for(let a=0;a<n.length;a++){const u=n[a],y=o[a];u&&u.isTexture===!0?l.push({material:r,slot:y,texture:u,level:e}):l.push({material:r,slot:y,texture:null,level:e})}return l})}if(t instanceof $||t.isTexture===!0){const r=t;return this.assignTextureLODForSlot(r,e,null,null)}return Promise.resolve(null)}static set maxConcurrentLoadingTasks(t){f.queue.maxConcurrent=t}static get maxConcurrentLoadingTasks(){return f.queue.maxConcurrent}static assignTextureLODForSlot(t,e,r,s){return t?.isTexture!==!0?Promise.resolve(null):s==="glyphMap"?Promise.resolve(t):f.getOrLoadLOD(t,e).then(o=>{if(Array.isArray(o))return console.warn("Progressive: Got an array of textures for a texture slot, this should not happen..."),null;if(o?.isTexture===!0){if(o!=t&&r&&s){const n=r[s];if(n&&!h){const l=this.getAssignedLODInformation(n);if(l&&l?.level<e)return h==="verbose"&&console.warn("Assigned texture level is already higher: ",l.level,e,r,n,o),o&&o!==n&&((h||X)&&console.log(`[gltf-progressive] Disposing rejected lower-quality texture LOD ${e} (assigned is ${l.level})`,o.uuid),o.dispose()),null}if(this.trackTextureUsage(o),n&&n!==o&&this.untrackTextureUsage(n)&&(h||X)){const l=this.getAssignedLODInformation(n);console.log(`[gltf-progressive] Disposed old texture LOD ${l?.level??"?"} \u2192 ${e} for ${r.name||r.type}.${s}`,n.uuid)}r[s]=o}return o}else h=="verbose"&&console.warn("No LOD found for",t,e);return null}).catch(o=>(console.error("Error loading LOD",t,o),null))}parser;url;constructor(t){const e=t.options.path;h&&console.log("Progressive extension registered for",e),this.parser=t,this.url=e}_isLoadingMesh;loadMesh=t=>{if(this._isLoadingMesh)return null;const e=this.parser.json.meshes[t]?.extensions?.[B];return e?(this._isLoadingMesh=!0,this.parser.getDependency("mesh",t).then(r=>(this._isLoadingMesh=!1,r&&f.registerMesh(this.url,e.guid,r,e.lods?.length,0,e),r))):null};afterRoot(t){return h&&console.log("AFTER",this.url,t),this.parser.json.textures?.forEach((e,r)=>{if(e?.extensions){const s=e?.extensions[B];if(s){if(!s.lods){h&&console.warn("Texture has no LODs",s);return}let o=!1;for(const n of this.parser.associations.keys())n.isTexture===!0&&this.parser.associations.get(n)?.textures===r&&(o=!0,f.registerTexture(this.url,n,s.lods?.length,r,s));o||this.parser.getDependency("texture",r).then(n=>{n&&f.registerTexture(this.url,n,s.lods?.length,r,s)})}}}),this.parser.json.meshes?.forEach((e,r)=>{if(e?.extensions){const s=e?.extensions[B];if(s&&s.lods){for(const o of this.parser.associations.keys())if(o.isMesh){const n=this.parser.associations.get(o);n?.meshes===r&&f.registerMesh(this.url,s.guid,o,s.lods.length,n.primitives,s)}}}}),null}static registerTexture=(t,e,r,s,o)=>{if(!e){h&&console.error("!! gltf-progressive: Called register texture without texture");return}if(h){const{width:l,height:a}=ge(e);console.log(`> gltf-progressive: register texture[${s}] "${e.name||e.uuid}", Current: ${l}x${a}, Max: ${o.lods[0]?.width}x${o.lods[0]?.height}, uuid: ${e.uuid}`,o,e)}e.source&&(e.source[xe]=o);const n=o.guid;f.assignLODInformation(t,e,n,r,s),f.lodInfos.set(n,o),f.lowresCache.set(n,new WeakRef(e))};static registerMesh=(t,e,r,s,o,n)=>{const l=r.geometry;if(!l){h&&console.warn("gltf-progressive: Register mesh without geometry");return}l.userData||(l.userData={}),h&&console.log("> Progressive: register mesh "+r.name,{index:o,uuid:r.uuid},n,r),f.assignLODInformation(t,l,e,s,o),f.lodInfos.set(e,n);let a=f.lowresCache.get(e)?.deref();a?a.push(r.geometry):a=[r.geometry],f.lowresCache.set(e,new WeakRef(a)),s>0&&!V(r)&&Re(r,l);for(const u of N)u.onRegisteredNewMesh?.(r,n)};static dispose(t){if(t){this.lodInfos.delete(t);const e=this.lowresCache.get(t);if(e){const r=e.deref();if(r){if(r.isTexture){const s=r;this.textureRefCounts.delete(s.uuid),s.dispose()}else if(Array.isArray(r))for(const s of r)s.dispose()}this.lowresCache.delete(t)}for(const[r,s]of this.cache)r.includes(t)&&(this._disposeCacheEntry(s),this.cache.delete(r))}else{this.lodInfos.clear();for(const[,e]of this.lowresCache){const r=e.deref();if(r){if(r.isTexture){const s=r;this.textureRefCounts.delete(s.uuid),s.dispose()}else if(Array.isArray(r))for(const s of r)s.dispose()}}this.lowresCache.clear();for(const[,e]of this.cache)this._disposeCacheEntry(e);this.cache.clear(),this.textureRefCounts.clear()}}static _disposeCacheEntry(t){if(t instanceof WeakRef){const e=t.deref();e&&(e.isTexture&&this.textureRefCounts.delete(e.uuid),e.dispose())}else t.then(e=>{if(e)if(Array.isArray(e))for(const r of e)r.dispose();else e.isTexture&&this.textureRefCounts.delete(e.uuid),e.dispose()}).catch(()=>{})}static lodInfos=new Map;static cache=new Map;static lowresCache=new Map;static textureRefCounts=new Map;static _resourceRegistry=new FinalizationRegistry(t=>{const e=f.cache.get(t);(h||X)&&console.debug(`[gltf-progressive] Memory: Resource GC'd
|
|
2
|
+
${t}`),e instanceof WeakRef&&(e.deref()||(f.cache.delete(t),(h||X)&&console.log("[gltf-progressive] \u21AA Cache entry deleted (GC)")))});static trackTextureUsage(t){const e=t.uuid,r=this.textureRefCounts.get(e)||0;this.textureRefCounts.set(e,r+1),h==="verbose"&&console.log(`[gltf-progressive] Track texture ${e}, refCount: ${r} \u2192 ${r+1}`)}static untrackTextureUsage(t){const e=t.uuid,r=this.textureRefCounts.get(e);if(!r)return(h==="verbose"||X)&&o("[gltf-progressive] Memory: Untrack untracked texture (dispose immediately)",0),t.dispose(),!0;const s=r-1;if(s<=0)return this.textureRefCounts.delete(e),(h||X)&&o("[gltf-progressive] Memory: Dispose texture",s),t.dispose(),!0;return this.textureRefCounts.set(e,s),h==="verbose"&&o("[gltf-progressive] Memory: Untrack texture",s),!1;function o(n,l){let{width:a,height:u}=ge(t);const y=a&&u?`${a}x${u}`:"N/A";let p="N/A";a&&u&&(p=`~${(mt(t)/(1024*1024)).toFixed(2)} MB`),console.log(`${n} \u2014 ${t.name} ${y} (${p}), refCount: ${r} \u2192 ${l}
|
|
3
|
+
${e}`)}}static workers=[];static _workersIndex=0;static async getOrLoadLOD(t,e){const r=h=="verbose",s=this.getAssignedLODInformation(t);if(!s)return h&&console.warn(`[gltf-progressive] No LOD information found: ${t.name}, uuid: ${t.uuid}, type: ${t.type}`,t),null;const o=s?.key;let n;if(t.isTexture===!0){const l=t;l.source&&l.source[xe]&&(n=l.source[xe])}if(n||(n=f.lodInfos.get(o)),!n)h&&console.warn(`Can not load LOD ${e}: no LOD info found for "${o}" ${t.name}`,t.type,f.lodInfos);else{if(e>0){let u=!1;const y=Array.isArray(n.lods);if(y&&e>=n.lods.length?u=!0:y||(u=!0),u){const p=this.lowresCache.get(o);if(p){const w=p.deref();if(w)return w;this.lowresCache.delete(o),h&&console.log(`[gltf-progressive] Lowres cache entry was GC'd: ${o}`)}return null}}const l=Array.isArray(n.lods)?n.lods[e]?.path:n.lods;if(!l)return h&&!n["missing:uri"]&&(n["missing:uri"]=!0,console.warn("Missing uri for progressive asset for LOD "+e,n)),null;const a=gt(s.url,l);if(a.endsWith(".glb")||a.endsWith(".gltf")){if(!n.guid)return console.warn("missing pointer for glb/gltf texture",n),null;const u=a+"_"+n.guid,y=await this.queue.slot(a),p=this.cache.get(u);if(p!==void 0)if(r&&console.log(`LOD ${e} was already loading/loaded: ${u}`),p instanceof WeakRef){const c=p.deref();if(c){let x=c,L=!1;if(x instanceof $&&t instanceof $?Ae(x.image)||he(x)?x=this.copySettings(t,x):L=!0:x instanceof W&&t instanceof W&&(x.attributes.position?.array||(L=!0)),!L)return x}this.cache.delete(u),h&&console.log(`[gltf-progressive] Re-loading GC'd/disposed resource: ${u}`)}else{let c=await p.catch(L=>(console.error(`Error loading LOD ${e} from ${a}
|
|
4
|
+
`,L),null)),x=!1;if(c==null||(c instanceof $&&t instanceof $?Ae(c.image)||he(c)?c=this.copySettings(t,c):(x=!0,this.cache.delete(u)):c instanceof W&&t instanceof W&&(c.attributes.position?.array||(x=!0,this.cache.delete(u)))),!x)return c}if(!y.use)return h&&console.log(`LOD ${e} was aborted: ${a}`),null;const w=n,P=new Promise(async(c,x)=>{if(bt){const g=await(await Lt({})).load(a);if(g.textures.length>0)for(const d of g.textures){let m=d.texture;return f.assignLODInformation(s.url,m,o,e,void 0),t instanceof $&&(m=this.copySettings(t,m)),m&&(m.guid=w.guid),c(m)}if(g.geometries.length>0){const d=new Array;for(const m of g.geometries){const _=m.geometry;f.assignLODInformation(s.url,_,o,e,m.primitiveIndex),d.push(_)}return c(d)}return c(null)}const L=new ae;le(L),h&&(await new Promise(g=>setTimeout(g,1e3)),r&&console.warn("Start loading (delayed) "+a,w.guid));let C=a;if(w&&Array.isArray(w.lods)){const g=w.lods[e];g.hash&&(C+="?v="+g.hash)}const M=await L.loadAsync(C).catch(g=>(console.error(`Error loading LOD ${e} from ${a}
|
|
5
|
+
`,g),c(null)));if(!M)return c(null);const E=M.parser;r&&console.log("Loading finished "+a,w.guid);let D=0;if(M.parser.json.textures){let g=!1;for(const d of M.parser.json.textures){if(d?.extensions){const m=d?.extensions[B];if(m?.guid&&m.guid===w.guid){g=!0;break}}D++}if(g){let d=await E.getDependency("texture",D);return d&&f.assignLODInformation(s.url,d,o,e,void 0),r&&console.log('change "'+t.name+'" \u2192 "'+d.name+'"',a,D,d,u),t instanceof $&&(d=this.copySettings(t,d)),d&&(d.guid=w.guid),c(d)}else h&&console.warn("Could not find texture with guid",w.guid,M.parser.json)}if(D=0,M.parser.json.meshes){let g=!1;for(const d of M.parser.json.meshes){if(d?.extensions){const m=d?.extensions[B];if(m?.guid&&m.guid===w.guid){g=!0;break}}D++}if(g){const d=await E.getDependency("mesh",D);if(r&&console.log(`Loaded Mesh "${d.name}"`,a,D,d,u),d.isMesh===!0){const m=d.geometry;return f.assignLODInformation(s.url,m,o,e,0),c(m)}else{const m=new Array;for(let _=0;_<d.children.length;_++){const S=d.children[_];if(S.isMesh===!0){const G=S.geometry;f.assignLODInformation(s.url,G,o,e,_),m.push(G)}}return c(m)}}else h&&console.warn("Could not find mesh with guid",w.guid,M.parser.json)}return c(null)});this.cache.set(u,P),y.use(P);const b=await P;return b!=null?b instanceof $?(this.cache.set(u,new WeakRef(b)),f._resourceRegistry.register(b,u)):Array.isArray(b)?this.cache.set(u,Promise.resolve(b)):this.cache.set(u,Promise.resolve(b)):this.cache.set(u,Promise.resolve(null)),b}else if(t instanceof $){r&&console.log("Load texture from uri: "+a);const u=await new tt().loadAsync(a);return u?(u.guid=n.guid,u.flipY=!1,u.needsUpdate=!0,u.colorSpace=t.colorSpace,r&&console.log(n,u)):h&&console.warn("failed loading",a),u}}return null}static queue=new ft(fe()?20:50,{debug:h!=!1});static assignLODInformation(t,e,r,s,o){if(!e)return;e.userData||(e.userData={});const n=new Mt(t,r,s,o);e.userData.LODS=n,"source"in e&&typeof e.source=="object"&&(e.source.LODS=n)}static getAssignedLODInformation(t){return t?t.userData?.LODS?t.userData.LODS:"source"in t&&t.source?.LODS?t.source.LODS:null:null}static copySettings(t,e){return e?(h==="verbose"&&console.debug(`Copy texture settings
|
|
6
|
+
`,t.uuid,`
|
|
7
|
+
`,e.uuid),e=e.clone(),e.offset=t.offset,e.repeat=t.repeat,e.colorSpace=t.colorSpace,e.magFilter=t.magFilter,e.minFilter=t.minFilter,e.wrapS=t.wrapS,e.wrapT=t.wrapT,e.flipY=t.flipY,e.anisotropy=t.anisotropy,e.mipmaps||(e.generateMipmaps=t.generateMipmaps),e):t}}class Mt{url;key;level;index;constructor(t,e,r,s){this.url=t,this.key=e,this.level=r,s!=null&&(this.index=s)}}class ye{static addPromise=(t,e,r,s)=>{s.forEach(o=>{o.add(t,e,r)})};ready;get awaitedCount(){return this._addedCount}get resolvedCount(){return this._resolvedCount}get currentlyAwaiting(){return this._awaiting.length}_resolve;_signal;_frame_start;_frames_to_capture;_resolved=!1;_addedCount=0;_resolvedCount=0;_awaiting=[];_maxPromisesPerObject=1;constructor(t,e){const r=Math.max(e.frames??2,2);this._frame_start=e.waitForFirstCapture?void 0:t,this._frames_to_capture=r,this.ready=new Promise(s=>{this._resolve=s}),this.ready.finally(()=>{this._resolved=!0,this._awaiting.length=0}),this._signal=e.signal,this._signal?.addEventListener("abort",()=>{this.resolveNow()}),this._maxPromisesPerObject=Math.max(1,e.maxPromisesPerObject??1)}_currentFrame=0;update(t){this._currentFrame=t,this._frame_start===void 0&&this._addedCount>0&&(this._frame_start=t),(this._signal?.aborted||this._awaiting.length===0&&this._frame_start!==void 0&&t>this._frame_start+this._frames_to_capture)&&this.resolveNow()}_seen=new WeakMap;add(t,e,r){if(this._resolved){h&&console.warn("PromiseGroup: Trying to add a promise to a resolved group, ignoring.");return}if(!(this._frame_start!==void 0&&this._currentFrame>this._frame_start+this._frames_to_capture)){if(this._maxPromisesPerObject>=1)if(this._seen.has(e)){let s=this._seen.get(e);if(s>=this._maxPromisesPerObject){h&&console.warn("PromiseGroup: Already awaiting object ignoring new promise for it.");return}this._seen.set(e,s+1)}else this._seen.set(e,1);this._awaiting.push(r),this._addedCount++,r.finally(()=>{this._resolvedCount++,this._awaiting.splice(this._awaiting.indexOf(r),1)})}}resolveNow(){this._resolved||this._resolve?.({awaited_count:this._addedCount,resolved_count:this._resolvedCount,cancelled:this._signal?.aborted??!1})}}const I=j("debugprogressive"),Dt=j("noprogressive"),we=Symbol("Needle:LODSManager"),ve=Symbol("Needle:LODState"),F=Symbol("Needle:CurrentLOD"),O={mesh_lod:-1,texture_lod:-1};class v{static debugDrawLine;static getObjectLODState(t){return t[ve]}static addPlugin(t){N.push(t)}static removePlugin(t){const e=N.indexOf(t);e>=0&&N.splice(e,1)}static get(t,e){if(t[we])return console.debug("[gltf-progressive] LODsManager already exists for this renderer"),t[we];const r=new v(t,{engine:"unknown",...e});return t[we]=r,r}renderer;context;projectionScreenMatrix=new De;get plugins(){return N}overrideLodLevel=void 0;targetTriangleDensity=2e5;skinnedMeshAutoUpdateBoundsInterval=30;updateInterval="auto";#e=1;pause=!1;manual=!1;_newPromiseGroups=[];_promiseGroupIds=0;awaitLoading(t){const e=this._promiseGroupIds++,r=new ye(this.#s,{...t});this._newPromiseGroups.push(r);const s=performance.now();return r.ready.finally(()=>{const o=this._newPromiseGroups.indexOf(r);o>=0&&(this._newPromiseGroups.splice(o,1),ke()&&performance.measure("LODsManager:awaitLoading",{start:s,detail:{id:e,name:t?.name,awaited:r.awaitedCount,resolved:r.resolvedCount}}))}),r.ready}_postprocessPromiseGroups(){if(this._newPromiseGroups.length!==0)for(let t=this._newPromiseGroups.length-1;t>=0;t--)this._newPromiseGroups[t].update(this.#s)}_lodchangedlisteners=[];addEventListener(t,e){t==="changed"&&this._lodchangedlisteners.push(e)}removeEventListener(t,e){if(t==="changed"){const r=this._lodchangedlisteners.indexOf(e);r>=0&&this._lodchangedlisteners.splice(r,1)}}constructor(t,e){this.renderer=t,this.context={...e}}#t;#n=new rt;#s=0;#o=0;#i=0;#r=0;_fpsBuffer=[60,60,60,60,60];enable(){if(this.#t)return;console.debug("[gltf-progressive] Enabling LODsManager for renderer");let t=0;this.#t=this.renderer.render;const e=this;ee(this.renderer),this.renderer.render=function(r,s){const o=e.renderer.getRenderTarget();(o==null||"isXRRenderTarget"in o&&o.isXRRenderTarget)&&(t=0,e.#s+=1,e.#n.update(),e.#o=e.#n.getDelta(),e.#i+=e.#o,e._fpsBuffer.shift(),e._fpsBuffer.push(1/e.#o),e.#r=e._fpsBuffer.reduce((l,a)=>l+a)/e._fpsBuffer.length,I&&e.#s%200===0&&console.log("FPS",Math.round(e.#r),"Interval:",e.#e));const n=t++;e.#t.call(this,r,s),e.onAfterRender(r,s,n)}}disable(){this.#t&&(console.debug("[gltf-progressive] Disabling LODsManager for renderer"),this.renderer.render=this.#t,this.#t=void 0)}update(t,e){this.internalUpdate(t,e)}onAfterRender(t,e,r){if(this.pause)return;const s=this.renderer.renderLists.get(t,0).opaque;let o=!0;if(s.length===1){const n=s[0].material;(n.name==="EffectMaterial"||n.name==="CopyShader")&&(o=!1)}if((e.parent&&e.parent.type==="CubeCamera"||r>=1&&e.type==="OrthographicCamera")&&(o=!1),o){if(Dt||(this.updateInterval==="auto"?this.#r<40&&this.#e<10?(this.#e+=1,I&&console.warn("\u2193 Reducing LOD updates",this.#e,this.#r.toFixed(0))):this.#r>=60&&this.#e>1&&(this.#e-=1,I&&console.warn("\u2191 Increasing LOD updates",this.#e,this.#r.toFixed(0))):this.#e=this.updateInterval,this.#e>0&&this.#s%this.#e!=0))return;this.internalUpdate(t,e),this._postprocessPromiseGroups()}}internalUpdate(t,e){const r=this.renderer.renderLists.get(t,0),s=r.opaque;this.projectionScreenMatrix.multiplyMatrices(e.projectionMatrix,e.matrixWorldInverse);const o=this.targetTriangleDensity;for(const a of s){if(a.material&&(a.geometry?.type==="BoxGeometry"||a.geometry?.type==="BufferGeometry")&&(a.material.name==="SphericalGaussianBlur"||a.material.name=="BackgroundCubeMaterial"||a.material.name==="CubemapFromEquirect"||a.material.name==="EquirectangularToCubeUV")){I&&(a.material["NEEDLE_PROGRESSIVE:IGNORE-WARNING"]||(a.material["NEEDLE_PROGRESSIVE:IGNORE-WARNING"]=!0,console.warn("Ignoring skybox or BLIT object",a,a.material.name,a.material.type)));continue}switch(a.material.type){case"LineBasicMaterial":case"LineDashedMaterial":case"PointsMaterial":case"ShadowMaterial":case"MeshDistanceMaterial":case"MeshDepthMaterial":continue}if(I==="color"&&a.material&&!a.object.progressive_debug_color){a.object.progressive_debug_color=!0;const y=Math.random()*16777215,p=new st({color:y});a.object.material=p}const u=a.object;(u instanceof U||u.isMesh)&&this.updateLODs(t,e,u,o)}const n=r.transparent;for(const a of n){const u=a.object;(u instanceof U||u.isMesh)&&this.updateLODs(t,e,u,o)}const l=r.transmissive;for(const a of l){const u=a.object;(u instanceof U||u.isMesh)&&this.updateLODs(t,e,u,o)}}updateLODs(t,e,r,s){r.userData||(r.userData={});let o=r[ve];if(o||(o=new Ot,r[ve]=o),o.frames++<2)return;for(const l of N)l.onBeforeUpdateLOD?.(this.renderer,t,e,r);const n=this.overrideLodLevel!==void 0?this.overrideLodLevel:q;n>=0?(O.mesh_lod=n,O.texture_lod=n):(this.calculateLodLevel(e,r,o,s,O),O.mesh_lod=Math.round(O.mesh_lod),O.texture_lod=Math.round(O.texture_lod)),O.mesh_lod>=0&&this.loadProgressiveMeshes(r,O.mesh_lod),r.material&&O.texture_lod>=0&&this.loadProgressiveTextures(r.material,O.texture_lod,n),h&&r.material&&!r.isGizmo&&Ee(r.material);for(const l of N)l.onAfterUpdatedLOD?.(this.renderer,t,e,r,O);o.lastLodLevel_Mesh=O.mesh_lod,o.lastLodLevel_Texture=O.texture_lod}loadProgressiveTextures(t,e,r){if(!t)return;if(Array.isArray(t)){for(const o of t)this.loadProgressiveTextures(o,e);return}let s=!1;if((t[F]===void 0||e<t[F])&&(s=!0),r!==void 0&&r>=0&&(s=t[F]!=r,e=r),s){t[F]=e;const o=f.assignTextureLOD(t,e).then(n=>{this._lodchangedlisteners.forEach(l=>l({type:"texture",level:e,object:t}))});ye.addPromise("texture",t,o,this._newPromiseGroups)}}loadProgressiveMeshes(t,e){if(!t)return Promise.resolve(null);let r=t[F]!==e;const s=t["DEBUG:LOD"];if(s!=null&&(r=t[F]!=s,e=s),r){t[F]=e;const o=t.geometry,n=f.assignMeshLOD(t,e).then(l=>(l&&t[F]==e&&o!=t.geometry&&this._lodchangedlisteners.forEach(a=>a({type:"mesh",level:e,object:t})),l));return ye.addPromise("mesh",t,n,this._newPromiseGroups),n}return Promise.resolve(null)}_sphere=new Me;_tempBox=new ie;_tempBox2=new ie;tempMatrix=new De;_tempWorldPosition=new A;_tempBoxSize=new A;_tempBox2Size=new A;static corner0=new A;static corner1=new A;static corner2=new A;static corner3=new A;static _tempPtInside=new A;static isInside(t,e){const r=t.min,s=t.max,o=(r.x+s.x)*.5,n=(r.y+s.y)*.5;return this._tempPtInside.set(o,n,r.z).applyMatrix4(e).z<0}static skinnedMeshBoundsFrameOffsetCounter=0;static $skinnedMeshBoundsOffset=Symbol("gltf-progressive-skinnedMeshBoundsOffset");calculateLodLevel(t,e,r,s,o){if(!e){o.mesh_lod=-1,o.texture_lod=-1;return}if(!t){o.mesh_lod=-1,o.texture_lod=-1;return}let n=11,l=!1;if(I&&e["DEBUG:LOD"]!=null)return e["DEBUG:LOD"];const a=f.getMeshLODExtension(e.geometry)?.lods,u=f.getPrimitiveIndex(e.geometry),y=a&&a.length>0,p=f.getMaterialMinMaxLODsCount(e.material),w=p.min_count!==1/0&&p.min_count>=0&&p.max_count>=0;if(!y&&!w){o.mesh_lod=0,o.texture_lod=0;return}y||(l=!0,n=0);const P=this.renderer.domElement.clientHeight||this.renderer.domElement.height;let b=e.geometry.boundingBox;if(e.type==="SkinnedMesh"){const c=e;if(!c.boundingBox)c.computeBoundingBox();else if(this.skinnedMeshAutoUpdateBoundsInterval>0){if(!c[v.$skinnedMeshBoundsOffset]){const L=v.skinnedMeshBoundsFrameOffsetCounter++;c[v.$skinnedMeshBoundsOffset]=L}const x=c[v.$skinnedMeshBoundsOffset];if((r.frames+x)%this.skinnedMeshAutoUpdateBoundsInterval===0){const L=V(c),C=c.geometry;L&&(c.geometry=L),c.computeBoundingBox(),c.geometry=C}}b=c.boundingBox}if(b){const c=t;if(e.geometry.attributes.color&&e.geometry.attributes.color.count<100&&e.geometry.boundingSphere){this._sphere.copy(e.geometry.boundingSphere),this._sphere.applyMatrix4(e.matrixWorld);const g=t.getWorldPosition(this._tempWorldPosition);if(this._sphere.containsPoint(g)){o.mesh_lod=0,o.texture_lod=0;return}}if(this._tempBox.copy(b),this._tempBox.applyMatrix4(e.matrixWorld),c.isPerspectiveCamera&&v.isInside(this._tempBox,this.projectionScreenMatrix)){o.mesh_lod=0,o.texture_lod=0;return}if(this._tempBox.applyMatrix4(this.projectionScreenMatrix),this.renderer.xr.enabled&&c.isPerspectiveCamera&&c.fov>70){const g=this._tempBox.min,d=this._tempBox.max;let m=g.x,_=g.y,S=d.x,G=d.y;const Q=2,oe=1.5,J=(g.x+d.x)*.5,Z=(g.y+d.y)*.5;m=(m-J)*Q+J,_=(_-Z)*Q+Z,S=(S-J)*Q+J,G=(G-Z)*Q+Z;const Fe=m<0&&S>0?0:Math.min(Math.abs(g.x),Math.abs(d.x)),We=_<0&&G>0?0:Math.min(Math.abs(g.y),Math.abs(d.y)),ne=Math.max(Fe,We);r.lastCentrality=(oe-ne)*(oe-ne)*(oe-ne)}else r.lastCentrality=1;const x=this._tempBox.getSize(this._tempBoxSize);x.multiplyScalar(.5),screen.availHeight>0&&P>0&&x.multiplyScalar(P/screen.availHeight),t.isPerspectiveCamera?x.x*=t.aspect:t.isOrthographicCamera;const L=t.matrixWorldInverse,C=this._tempBox2;C.copy(b),C.applyMatrix4(e.matrixWorld),C.applyMatrix4(L);const M=C.getSize(this._tempBox2Size),E=Math.max(M.x,M.y);if(Math.max(x.x,x.y)!=0&&E!=0&&(x.z=M.z/Math.max(M.x,M.y)*Math.max(x.x,x.y)),r.lastScreenCoverage=Math.max(x.x,x.y,x.z),r.lastScreenspaceVolume.copy(x),r.lastScreenCoverage*=r.lastCentrality,I&&v.debugDrawLine){const g=this.tempMatrix.copy(this.projectionScreenMatrix);g.invert();const d=v.corner0,m=v.corner1,_=v.corner2,S=v.corner3;d.copy(this._tempBox.min),m.copy(this._tempBox.max),m.x=d.x,_.copy(this._tempBox.max),_.y=d.y,S.copy(this._tempBox.max);const G=(d.z+S.z)*.5;d.z=m.z=_.z=S.z=G,d.applyMatrix4(g),m.applyMatrix4(g),_.applyMatrix4(g),S.applyMatrix4(g),v.debugDrawLine(d,m,255),v.debugDrawLine(d,_,255),v.debugDrawLine(m,S,255),v.debugDrawLine(_,S,255)}let D=999;if(a&&r.lastScreenCoverage>0)for(let g=0;g<a.length;g++){const d=a[g],m=(d.densities?.[u]||d.density||1e-5)/r.lastScreenCoverage;if(u>0&&ke()&&!d.densities&&!globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"]&&(window["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"]=!0,console.warn("[Needle Progressive] Detected usage of mesh without primitive densities. This might cause incorrect LOD level selection: Consider re-optimizing your model by updating your Needle Integration, Needle glTF Pipeline or running optimization again on Needle Cloud.")),m<s){D=g;break}}D<n&&(n=D,l=!0)}if(l?o.mesh_lod=n:o.mesh_lod=r.lastLodLevel_Mesh,I&&o.mesh_lod!=r.lastLodLevel_Mesh){const c=a?.[o.mesh_lod];c&&console.log(`Mesh LOD changed: ${r.lastLodLevel_Mesh} \u2192 ${o.mesh_lod} (density: ${c.densities?.[u].toFixed(0)}) | ${e.name}`)}if(w){const c="saveData"in globalThis.navigator&&globalThis.navigator.saveData===!0;if(r.lastLodLevel_Texture<0){if(o.texture_lod=p.max_count-1,I){const x=p.lods[p.max_count-1];I&&console.log(`First Texture LOD ${o.texture_lod} (${x.max_height}px) - ${e.name}`)}}else{const x=r.lastScreenspaceVolume.x+r.lastScreenspaceVolume.y+r.lastScreenspaceVolume.z;let L=r.lastScreenCoverage*4;this.context?.engine==="model-viewer"&&(L*=1.5);const C=P/window.devicePixelRatio*L;let M=!1;for(let E=p.lods.length-1;E>=0;E--){const D=p.lods[E];if(!(c&&D.max_height>=2048)&&!(fe()&&D.max_height>4096)&&(D.max_height>C||!M&&E===0)){if(M=!0,o.texture_lod=E,I&&o.texture_lod<r.lastLodLevel_Texture){const g=D.max_height;console.log(`Texture LOD changed: ${r.lastLodLevel_Texture} \u2192 ${o.texture_lod} = ${g}px
|
|
8
|
+
Screensize: ${C.toFixed(0)}px, Coverage: ${(100*r.lastScreenCoverage).toFixed(2)}%, Volume ${x.toFixed(1)}
|
|
9
|
+
${e.name}`)}break}}}}else o.texture_lod=0}}class Ot{frames=0;lastLodLevel_Mesh=-1;lastLodLevel_Texture=-1;lastScreenCoverage=0;lastScreenspaceVolume=new A;lastCentrality=0}const $e=Symbol("NEEDLE_mesh_lod"),se=Symbol("NEEDLE_texture_lod");let Le=null;function _e(){const i=St();i&&(i.mapURLs(function(t){return Be(),t}),Be(),Le?.disconnect(),Le=new MutationObserver(t=>{t.forEach(e=>{e.addedNodes.forEach(r=>{r instanceof HTMLElement&&r.tagName.toLowerCase()==="model-viewer"&&je(r)})})}),Le.observe(document,{childList:!0,subtree:!0}))}function St(){return typeof customElements>"u"?null:customElements.get("model-viewer")||(customElements.whenDefined("model-viewer").then(()=>{console.debug("[gltf-progressive] model-viewer defined"),_e()}),null)}function Be(){typeof document>"u"||document.querySelectorAll("model-viewer").forEach(i=>{je(i)})}const Ge=new WeakSet;let Tt=0;function je(i){if(!i||Ge.has(i))return null;Ge.add(i),console.debug("[gltf-progressive] found new model-viewer..."+ ++Tt+`
|
|
10
|
+
`,i.getAttribute("src"));let t=null,e=null,r=null;for(let s=i;s!=null;s=Object.getPrototypeOf(s)){const o=Object.getOwnPropertySymbols(s),n=o.find(u=>u.toString()=="Symbol(renderer)"),l=o.find(u=>u.toString()=="Symbol(scene)"),a=o.find(u=>u.toString()=="Symbol(needsRender)");!t&&n!=null&&(t=i[n].threeRenderer),!e&&l!=null&&(e=i[l]),!r&&a!=null&&(r=i[a])}if(t&&e){let s=function(){if(r){let n=0,l=setInterval(()=>{if(n++>5){clearInterval(l);return}r?.call(i)},300)}};console.debug("[gltf-progressive] setup model-viewer");const o=v.get(t,{engine:"model-viewer"});return v.addPlugin(new Pt),o.enable(),o.addEventListener("changed",()=>{r?.call(i)}),i.addEventListener("model-visibility",n=>{n.detail.visible&&r?.call(i)}),i.addEventListener("load",()=>{s()}),()=>{o.disable()}}return null}class Pt{_didWarnAboutMissingUrl=!1;onBeforeUpdateLOD(t,e,r,s){this.tryParseMeshLOD(e,s),this.tryParseTextureLOD(e,s)}getUrl(t){if(!t)return null;let e=t.getAttribute("src");return e||(e=t.src),e||(this._didWarnAboutMissingUrl||console.warn("No url found in modelviewer",t),this._didWarnAboutMissingUrl=!0),e}tryGetCurrentGLTF(t){return t._currentGLTF}tryGetCurrentModelViewer(t){return t.element}tryParseTextureLOD(t,e){if(e[se]==!0)return;e[se]=!0;const r=this.tryGetCurrentGLTF(t),s=this.tryGetCurrentModelViewer(t),o=this.getUrl(s);if(o&&r&&e.material){let n=function(a){if(a[se]==!0)return;a[se]=!0,a.userData&&(a.userData.LOD=-1);const u=Object.keys(a);for(let y=0;y<u.length;y++){const p=u[y],w=a[p];if(w?.isTexture===!0){const P=w.userData?.associations?.textures;if(P==null)continue;const b=r.parser.json.textures[P];if(!b){console.warn("Texture data not found for texture index "+P);continue}if(b?.extensions?.[B]){const c=b.extensions[B];c&&o&&f.registerTexture(o,w,c.lods.length,P,c)}}}};const l=e.material;if(Array.isArray(l))for(const a of l)n(a);else n(l)}}tryParseMeshLOD(t,e){if(e[$e]==!0)return;e[$e]=!0;const r=this.tryGetCurrentModelViewer(t),s=this.getUrl(r);if(!s)return;const o=e.userData?.gltfExtensions?.[B];if(o&&s){const n=e.uuid;f.registerMesh(s,n,e,0,o.lods.length,o)}}}function Ne(...i){let t,e,r,s;switch(i.length){case 2:[r,e]=i,s={};break;case 3:[r,e,s]=i;break;case 4:[t,e,r,s]=i;break;default:throw new Error("Invalid arguments")}ee(e),le(r),de(r,{progressive:!0,...s?.hints}),r.register(n=>new f(n));const o=v.get(e);return s?.enableLODsManager!==!1&&o.enable(),o}if(_e(),!xt){const i={gltfProgressive:{useNeedleProgressive:Ne,LODsManager:v,configureLoader:de,getRaycastMesh:V,useRaycastMeshes:Ie}};if(!globalThis.Needle)globalThis.Needle=i;else for(const t in i)globalThis.Needle[t]=i[t]}export{B as EXTENSION_NAME,v as LODsManager,f as NEEDLE_progressive,Oe as VERSION,le as addDracoAndKTX2Loaders,de as configureLoader,ee as createLoaders,V as getRaycastMesh,_e as patchModelViewer,Re as registerRaycastMesh,Te as setDracoDecoderLocation,Pe as setKTX2TranscoderLocation,Ne as useNeedleProgressive,Ie as useRaycastMeshes};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";var Ve=Object.create;var be=Object.defineProperty;var Ne=Object.getOwnPropertyDescriptor;var qe=Object.getOwnPropertyNames;var Ee=Object.getPrototypeOf,je=Object.prototype.hasOwnProperty;var Xe=(n,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of qe(t))!je.call(n,r)&&r!==e&&be(n,r,{get:()=>t[r],enumerable:!(s=Ne(t,r))||s.enumerable});return n};var Ke=(n,t,e)=>(e=n!=null?Ve(Ee(n)):{},Xe(t||!n||!n.__esModule?be(e,"default",{value:n,enumerable:!0}):e,n));Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("three"),xe=require("three/examples/jsm/loaders/GLTFLoader.js"),Ye=require("three/examples/jsm/libs/meshopt_decoder.module.js"),He=require("three/examples/jsm/loaders/DRACOLoader.js"),Qe=require("three/examples/jsm/loaders/KTX2Loader.js");var ie=typeof document<"u"?document.currentScript:null;const Ce="";globalThis.GLTF_PROGRESSIVE_VERSION=Ce;console.debug("[gltf-progressive] version -");let k="https://www.gstatic.com/draco/versioned/decoders/1.5.7/",q="https://cdn.needle.tools/static/three/0.179.1/basis2/";const Je=k,Ze=q,Ae=new URL(k+"draco_decoder.js");Ae.searchParams.append("range","true");fetch(Ae,{method:"GET",headers:{Range:"bytes=0-1"}}).catch(n=>{console.debug(`Failed to fetch remote Draco decoder from ${k} (offline: ${typeof navigator<"u"?navigator.onLine:"unknown"})`),k===Je&&Re("./include/draco/"),q===Ze&&Ie("./include/ktx2/")}).finally(()=>{ke()});const et=()=>({dracoDecoderPath:k,ktx2TranscoderPath:q});function Re(n){k=n,A&&A[he]!=k?(console.debug("Updating Draco decoder path to "+n),A[he]=k,A.setDecoderPath(k),A.preload()):console.debug("Setting Draco decoder path to "+n)}function Ie(n){q=n,B&&B.transcoderPath!=q?(console.debug("Updating KTX2 transcoder path to "+n),B.setTranscoderPath(q),B.init()):console.debug("Setting KTX2 transcoder path to "+n)}function re(n){return ke(),n?B.detectSupport(n):n!==null&&console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures might fail"),{dracoLoader:A,ktx2Loader:B,meshoptDecoder:se}}function we(n){n.dracoLoader||n.setDRACOLoader(A),n.ktx2Loader||n.setKTX2Loader(B),n.meshoptDecoder||n.setMeshoptDecoder(se)}const he=Symbol("dracoDecoderPath");let A,se,B;function ke(){A||(A=new He.DRACOLoader,A[he]=k,A.setDecoderPath(k),A.setDecoderConfig({type:"js"}),A.preload()),B||(B=new Qe.KTX2Loader,B.setTranscoderPath(q),B.init()),se||(se=Ye.MeshoptDecoder)}const ge=new WeakMap;function Le(n,t){let e=ge.get(n);e?e=Object.assign(e,t):e=t,ge.set(n,e)}const tt=xe.GLTFLoader.prototype.load;function st(...n){const t=ge.get(this);let e=n[0];const s=new URL(e,window.location.href);if(s.hostname.endsWith("needle.tools")){const o=t?.progressive!==void 0?t.progressive:!0,i=t?.usecase?t.usecase:"default";o?this.requestHeader.Accept=`*/*;progressive=allowed;usecase=${i}`:this.requestHeader.Accept=`*/*;usecase=${i}`,e=s.toString()}return n[0]=e,tt?.call(this,...n)}xe.GLTFLoader.prototype.load=st;function De(n){return n!=null&&n.data!=null}function pe(n){const t=n.source?.data;return t!=null&&typeof t=="object"?t:null}function rt(n){const t=n.image;return t!=null&&typeof t=="object"?t:null}function me(n){const t=rt(n),e=pe(n);return{width:t?.width||e?.width||0,height:t?.height||e?.height||0}}z("debugprogressive");function z(n){if(typeof window>"u")return!1;const e=new URL(window.location.href).searchParams.get(n);return e==null||e==="0"||e==="false"?!1:e===""?!0:e}function ot(n,t){if(t===void 0||n===void 0||t.startsWith("./")||t.startsWith("http")||t.startsWith("data:")||t.startsWith("blob:"))return t;const e=n.lastIndexOf("/");if(e>=0){const s=n.substring(0,e+1);for(;s.endsWith("/")&&t.startsWith("/");)t=t.substring(1);return s+t}return t}function _e(){return K!==void 0||(K=/iPhone|iPad|iPod|Android|IEMobile/i.test(navigator.userAgent),z("debugprogressive")&&console.log("[glTF Progressive]: isMobileDevice",K)),K}let K;function Oe(){if(typeof window>"u")return!1;const n=new URL(window.location.href),t=n.hostname==="localhost"||/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(n.hostname);return n.hostname==="127.0.0.1"||t}class nt{constructor(t,e={}){this.maxConcurrent=t,this.debug=e.debug??!1,window.requestAnimationFrame(this.tick)}_running=new Map;_queue=[];debug=!1;tick=()=>{this.internalUpdate(),setTimeout(this.tick,10)};slot(t){return this.debug&&console.debug(`[PromiseQueue]: Requesting slot for key ${t}, running: ${this._running.size}, waiting: ${this._queue.length}`),new Promise(e=>{this._queue.push({key:t,resolve:e})})}add(t,e){this._running.has(t)||(this._running.set(t,e),e.finally(()=>{this._running.delete(t),this.debug&&console.debug(`[PromiseQueue]: Promise finished now running: ${this._running.size}, waiting: ${this._queue.length}. (finished ${t})`)}),this.debug&&console.debug(`[PromiseQueue]: Added new promise, now running: ${this._running.size}, waiting: ${this._queue.length}. (added ${t})`))}internalUpdate(){const t=this.maxConcurrent-this._running.size;for(let e=0;e<t&&this._queue.length>0;e++){this.debug&&console.debug(`[PromiseQueue]: Running ${this._running.size} promises, waiting for ${this._queue.length} more.`);const{key:s,resolve:r}=this._queue.shift();r({use:o=>this.add(s,o)})}}}function it(n){const t=n.image,e=t?.width??0,s=t?.height??0,r=t?.depth??1,o=Math.floor(Math.log2(Math.max(e,s,r)))+1,i=at(n);return e*s*r*i*(1-Math.pow(.25,o))/(1-.25)}function at(n){let t=4;const e=n.format;e===c.RedFormat||e===c.RedIntegerFormat?t=1:e===c.RGFormat||e===c.RGIntegerFormat?t=2:e===c.RGBFormat||e===1029?t=3:(e===c.RGBAFormat||e===c.RGBAIntegerFormat)&&(t=4);let s=1;const r=n.type;return r===1009||r===1010?s=1:r===1011||r===1012?s=2:r===1013||r===1014||r===1015?s=4:r===1016&&(s=2),t*s}const lt=typeof window>"u"&&typeof document>"u",ye=Symbol("needle:raycast-mesh");function j(n){return n?.[ye]instanceof c.BufferGeometry?n[ye]:null}function Be(n,t){if((n.type==="Mesh"||n.type==="SkinnedMesh")&&!j(n)){const s=ut(t);s.userData={isRaycastMesh:!0},n[ye]=s}}function $e(n=!0){if(n){if(Y)return;const t=Y=c.Mesh.prototype.raycast;c.Mesh.prototype.raycast=function(e,s){const r=this,o=j(r);let i;o&&r.isMesh&&(i=r.geometry,r.geometry=o),t.call(this,e,s),i&&(r.geometry=i)}}else{if(!Y)return;c.Mesh.prototype.raycast=Y,Y=null}}let Y=null;function ut(n){const t=new c.BufferGeometry;for(const e in n.attributes)t.setAttribute(e,n.getAttribute(e));return t.setIndex(n.getIndex()),t}const W=new Array,h=z("debugprogressive");let Q,N=-1;if(h){let n=function(){N+=1,N>=t&&(N=-1),console.log(`Toggle LOD level [${N}]`)},t=6;window.addEventListener("keyup",e=>{e.key==="p"&&n(),e.key==="w"&&(Q=!Q,console.log(`Toggle wireframe [${Q}]`));const s=parseInt(e.key);!isNaN(s)&&s>=0&&(N=s,console.log(`Set LOD level to [${N}]`))})}function Ge(n){if(h&&Q!==void 0)if(Array.isArray(n))for(const t of n)Ge(t);else n&&"wireframe"in n&&(n.wireframe=Q===!0)}const H=new Array;let ct=0;const dt=_e()?2:10;function ft(n){if(H.length<dt){const s=H.length;h&&console.warn(`[Worker] Creating new worker #${s}`);const r=ve.createWorker(n||{});return H.push(r),r}const t=ct++%H.length;return H[t]}class ve{constructor(t,e){this.worker=t,this._debug=e.debug??!1,t.onmessage=s=>{const r=s.data;switch(this._debug&&console.log("[Worker] EVENT",r),r.type){case"loaded-gltf":for(const o of this._running)if(o.url===r.result.url){ht(r.result),o.resolve(r.result);const i=o.url;i.startsWith("blob:")&&URL.revokeObjectURL(i)}}},t.onerror=s=>{console.error("[Worker] Error in gltf-progressive worker:",s)},t.postMessage({type:"init"})}static async createWorker(t){const e=new Worker(new URL("/assets/gltf-progressive.worker-CDSrhw-p.js",typeof document>"u"?require("url").pathToFileURL(__filename).href:ie&&ie.tagName.toUpperCase()==="SCRIPT"&&ie.src||new URL("gltf-progressive.umd.cjs",document.baseURI).href),{type:"module"});return new ve(e,t)}_running=[];_webglRenderer=null;async load(t,e){const s=et();let r=e?.renderer;r||(this._webglRenderer??=(async()=>{const{WebGLRenderer:u}=await import("three");return new u})(),r=await this._webglRenderer);const l=re(r).ktx2Loader.workerConfig;t instanceof URL?t=t.toString():t.startsWith("file:")?t=URL.createObjectURL(new Blob([t])):!t.startsWith("blob:")&&!t.startsWith("http:")&&!t.startsWith("https:")&&(t=new URL(t,window.location.href).toString());const a={type:"load",url:t,dracoDecoderPath:s.dracoDecoderPath,ktx2TranscoderPath:s.ktx2TranscoderPath,ktx2LoaderConfig:l};return this._debug&&console.debug("[Worker] Sending load request",a),this.worker.postMessage(a),new Promise(u=>{this._running.push({url:t.toString(),resolve:u})})}_debug=!1}function ht(n){for(const t of n.geometries){const e=t.geometry,s=new c.BufferGeometry;if(s.name=e.name||"",e.index){const r=e.index;s.setIndex(ae(r))}for(const r in e.attributes){const o=e.attributes[r],i=ae(o);s.setAttribute(r,i)}if(e.morphAttributes)for(const r in e.morphAttributes){const i=e.morphAttributes[r].map(l=>ae(l));s.morphAttributes[r]=i}if(s.morphTargetsRelative=e.morphTargetsRelative??!1,s.boundingBox=new c.Box3,s.boundingBox.min=new c.Vector3(e.boundingBox?.min.x,e.boundingBox?.min.y,e.boundingBox?.min.z),s.boundingBox.max=new c.Vector3(e.boundingBox?.max.x,e.boundingBox?.max.y,e.boundingBox?.max.z),s.boundingSphere=new c.Sphere(new c.Vector3(e.boundingSphere?.center.x,e.boundingSphere?.center.y,e.boundingSphere?.center.z),e.boundingSphere?.radius),e.groups)for(const r of e.groups)s.addGroup(r.start,r.count,r.materialIndex);e.userData&&(s.userData=e.userData),t.geometry=s}for(const t of n.textures){const e=t.texture;let s=null;if(e.isCompressedTexture){const r=e.mipmaps,{width:o,height:i}=me(e);s=new c.CompressedTexture(r,o,i,e.format,e.type,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.anisotropy,e.colorSpace)}else s=new c.Texture(e.image,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.format,e.type,e.anisotropy,e.colorSpace),s.mipmaps=e.mipmaps,s.channel=e.channel,s.source.data=e.source.data,s.flipY=e.flipY,s.premultiplyAlpha=e.premultiplyAlpha,s.unpackAlignment=e.unpackAlignment,s.matrix=new c.Matrix3(...e.matrix.elements);if(!s){console.error("[Worker] Failed to create new texture from received data. Texture is not a CompressedTexture or Texture.");continue}t.texture=s}return n}function ae(n){let t=n;if("isInterleavedBufferAttribute"in n&&n.isInterleavedBufferAttribute){const e=n.data,s=e.array,r=new c.InterleavedBuffer(s,e.stride);t=new c.InterleavedBufferAttribute(r,n.itemSize,s.byteOffset,n.normalized),t.offset=n.offset}else"isBufferAttribute"in n&&n.isBufferAttribute&&(t=new c.BufferAttribute(n.array,n.itemSize,n.normalized),t.usage=n.usage,t.gpuType=n.gpuType,t.updateRanges=n.updateRanges);return t}const gt=z("gltf-progressive-worker");z("gltf-progressive-reduce-mipmaps");const E=z("gltf-progressive-gc"),le=Symbol("needle-progressive-texture"),F="NEEDLE_progressive";class m{get name(){return F}static getMeshLODExtension(t){const e=this.getAssignedLODInformation(t);return e?.key?this.lodInfos.get(e.key):null}static getPrimitiveIndex(t){const e=this.getAssignedLODInformation(t)?.index;return e??-1}static getMaterialMinMaxLODsCount(t,e){const s=this,r="LODS:minmax",o=t[r];if(o!=null)return o;if(e||(e={min_count:1/0,max_count:0,lods:[]}),Array.isArray(t)){for(const l of t)this.getMaterialMinMaxLODsCount(l,e);return t[r]=e,e}if(h==="verbose"&&console.log("getMaterialMinMaxLODsCount",t),t.type==="ShaderMaterial"||t.type==="RawShaderMaterial"){const l=t;for(const a of Object.keys(l.uniforms)){const u=l.uniforms[a].value;u?.isTexture===!0&&i(u,e)}}else if(t.isMaterial)for(const l of Object.keys(t)){const a=t[l];a?.isTexture===!0&&i(a,e)}else h&&console.warn(`[getMaterialMinMaxLODsCount] Unsupported material type: ${t.type}`);return t[r]=e,e;function i(l,a){const u=s.getAssignedLODInformation(l);if(u){const d=s.lodInfos.get(u.key);if(d&&d.lods){a.min_count=Math.min(a.min_count,d.lods.length),a.max_count=Math.max(a.max_count,d.lods.length);for(let x=0;x<d.lods.length;x++){const L=d.lods[x];L.width&&(a.lods[x]=a.lods[x]||{min_height:1/0,max_height:0},a.lods[x].min_height=Math.min(a.lods[x].min_height,L.height),a.lods[x].max_height=Math.max(a.lods[x].max_height,L.height))}}}}}static hasLODLevelAvailable(t,e){if(Array.isArray(t)){for(const o of t)if(this.hasLODLevelAvailable(o,e))return!0;return!1}if(t.isMaterial===!0){for(const o of Object.keys(t)){const i=t[o];if(i&&i.isTexture&&this.hasLODLevelAvailable(i,e))return!0}return!1}else if(t.isGroup===!0){for(const o of t.children)if(o.isMesh===!0&&this.hasLODLevelAvailable(o,e))return!0}let s,r;if(t.isMesh?s=t.geometry:(t.isBufferGeometry||t.isTexture)&&(s=t),s&&s?.userData?.LODS){const o=s.userData.LODS;if(r=this.lodInfos.get(o.key),e===void 0)return r!=null;if(r)return Array.isArray(r.lods)?e<r.lods.length:e===0}return!1}static assignMeshLOD(t,e){if(!t)return Promise.resolve(null);if(t instanceof c.Mesh||t.isMesh===!0){const s=t.geometry,r=this.getAssignedLODInformation(s);if(!r)return Promise.resolve(null);for(const o of W)o.onBeforeGetLODMesh?.(t,e);return t["LOD:requested level"]=e,m.getOrLoadLOD(s,e).then(o=>{if(Array.isArray(o)){const i=r.index||0;o=o[i]}return t["LOD:requested level"]===e&&(delete t["LOD:requested level"],o&&s!=o&&(o?.isBufferGeometry?t.geometry=o:h&&console.error("Invalid LOD geometry",o))),o}).catch(o=>(console.error("Error loading mesh LOD",t,o),null))}else h&&console.error("Invalid call to assignMeshLOD: Request mesh LOD but the object is not a mesh",t);return Promise.resolve(null)}static assignTextureLOD(t,e=0){if(!t)return Promise.resolve(null);if(t.isMesh===!0){const s=t;if(Array.isArray(s.material)){const r=new Array;for(const o of s.material){const i=this.assignTextureLOD(o,e);r.push(i)}return Promise.all(r).then(o=>{const i=new Array;for(const l of o)Array.isArray(l)&&i.push(...l);return i})}else return this.assignTextureLOD(s.material,e)}if(t.isMaterial===!0){const s=t,r=[],o=new Array;if(s.uniforms&&(s.isRawShaderMaterial||s.isShaderMaterial===!0)){const i=s;for(const l of Object.keys(i.uniforms)){const a=i.uniforms[l].value;if(a?.isTexture===!0){const u=this.assignTextureLODForSlot(a,e,s,l).then(d=>(d&&i.uniforms[l].value!=d&&(i.uniforms[l].value=d,i.uniformsNeedUpdate=!0),d));r.push(u),o.push(l)}}}else for(const i of Object.keys(s)){const l=s[i];if(l?.isTexture===!0){const a=this.assignTextureLODForSlot(l,e,s,i);r.push(a),o.push(i)}}return Promise.all(r).then(i=>{const l=new Array;for(let a=0;a<i.length;a++){const u=i[a],d=o[a];u&&u.isTexture===!0?l.push({material:s,slot:d,texture:u,level:e}):l.push({material:s,slot:d,texture:null,level:e})}return l})}if(t instanceof c.Texture||t.isTexture===!0){const s=t;return this.assignTextureLODForSlot(s,e,null,null)}return Promise.resolve(null)}static set maxConcurrentLoadingTasks(t){m.queue.maxConcurrent=t}static get maxConcurrentLoadingTasks(){return m.queue.maxConcurrent}static assignTextureLODForSlot(t,e,s,r){return t?.isTexture!==!0?Promise.resolve(null):r==="glyphMap"?Promise.resolve(t):m.getOrLoadLOD(t,e).then(o=>{if(Array.isArray(o))return console.warn("Progressive: Got an array of textures for a texture slot, this should not happen..."),null;if(o?.isTexture===!0){if(o!=t&&s&&r){const i=s[r];if(i&&!h){const l=this.getAssignedLODInformation(i);if(l&&l?.level<e)return h==="verbose"&&console.warn("Assigned texture level is already higher: ",l.level,e,s,i,o),o&&o!==i&&((h||E)&&console.log(`[gltf-progressive] Disposing rejected lower-quality texture LOD ${e} (assigned is ${l.level})`,o.uuid),o.dispose()),null}if(this.trackTextureUsage(o),i&&i!==o&&this.untrackTextureUsage(i)&&(h||E)){const a=this.getAssignedLODInformation(i);console.log(`[gltf-progressive] Disposed old texture LOD ${a?.level??"?"} → ${e} for ${s.name||s.type}.${r}`,i.uuid)}s[r]=o}return o}else h=="verbose"&&console.warn("No LOD found for",t,e);return null}).catch(o=>(console.error("Error loading LOD",t,o),null))}parser;url;constructor(t){const e=t.options.path;h&&console.log("Progressive extension registered for",e),this.parser=t,this.url=e}_isLoadingMesh;loadMesh=t=>{if(this._isLoadingMesh)return null;const e=this.parser.json.meshes[t]?.extensions?.[F];return e?(this._isLoadingMesh=!0,this.parser.getDependency("mesh",t).then(s=>(this._isLoadingMesh=!1,s&&m.registerMesh(this.url,e.guid,s,e.lods?.length,0,e),s))):null};afterRoot(t){return h&&console.log("AFTER",this.url,t),this.parser.json.textures?.forEach((e,s)=>{if(e?.extensions){const r=e?.extensions[F];if(r){if(!r.lods){h&&console.warn("Texture has no LODs",r);return}let o=!1;for(const i of this.parser.associations.keys())i.isTexture===!0&&this.parser.associations.get(i)?.textures===s&&(o=!0,m.registerTexture(this.url,i,r.lods?.length,s,r));o||this.parser.getDependency("texture",s).then(i=>{i&&m.registerTexture(this.url,i,r.lods?.length,s,r)})}}}),this.parser.json.meshes?.forEach((e,s)=>{if(e?.extensions){const r=e?.extensions[F];if(r&&r.lods){for(const o of this.parser.associations.keys())if(o.isMesh){const i=this.parser.associations.get(o);i?.meshes===s&&m.registerMesh(this.url,r.guid,o,r.lods.length,i.primitives,r)}}}}),null}static registerTexture=(t,e,s,r,o)=>{if(!e){h&&console.error("!! gltf-progressive: Called register texture without texture");return}if(h){const{width:l,height:a}=me(e);console.log(`> gltf-progressive: register texture[${r}] "${e.name||e.uuid}", Current: ${l}x${a}, Max: ${o.lods[0]?.width}x${o.lods[0]?.height}, uuid: ${e.uuid}`,o,e)}e.source&&(e.source[le]=o);const i=o.guid;m.assignLODInformation(t,e,i,s,r),m.lodInfos.set(i,o),m.lowresCache.set(i,new WeakRef(e))};static registerMesh=(t,e,s,r,o,i)=>{const l=s.geometry;if(!l){h&&console.warn("gltf-progressive: Register mesh without geometry");return}l.userData||(l.userData={}),h&&console.log("> Progressive: register mesh "+s.name,{index:o,uuid:s.uuid},i,s),m.assignLODInformation(t,l,e,r,o),m.lodInfos.set(e,i);let u=m.lowresCache.get(e)?.deref();u?u.push(s.geometry):u=[s.geometry],m.lowresCache.set(e,new WeakRef(u)),r>0&&!j(s)&&Be(s,l);for(const d of W)d.onRegisteredNewMesh?.(s,i)};static dispose(t){if(t){this.lodInfos.delete(t);const e=this.lowresCache.get(t);if(e){const s=e.deref();if(s){if(s.isTexture){const r=s;this.textureRefCounts.delete(r.uuid),r.dispose()}else if(Array.isArray(s))for(const r of s)r.dispose()}this.lowresCache.delete(t)}for(const[s,r]of this.cache)s.includes(t)&&(this._disposeCacheEntry(r),this.cache.delete(s))}else{this.lodInfos.clear();for(const[,e]of this.lowresCache){const s=e.deref();if(s){if(s.isTexture){const r=s;this.textureRefCounts.delete(r.uuid),r.dispose()}else if(Array.isArray(s))for(const r of s)r.dispose()}}this.lowresCache.clear();for(const[,e]of this.cache)this._disposeCacheEntry(e);this.cache.clear(),this.textureRefCounts.clear()}}static _disposeCacheEntry(t){if(t instanceof WeakRef){const e=t.deref();e&&(e.isTexture&&this.textureRefCounts.delete(e.uuid),e.dispose())}else t.then(e=>{if(e)if(Array.isArray(e))for(const s of e)s.dispose();else e.isTexture&&this.textureRefCounts.delete(e.uuid),e.dispose()}).catch(()=>{})}static lodInfos=new Map;static cache=new Map;static lowresCache=new Map;static textureRefCounts=new Map;static _resourceRegistry=new FinalizationRegistry(t=>{const e=m.cache.get(t);(h||E)&&console.debug(`[gltf-progressive] Memory: Resource GC'd
|
|
2
|
+
${t}`),e instanceof WeakRef&&(e.deref()||(m.cache.delete(t),(h||E)&&console.log("[gltf-progressive] ↪ Cache entry deleted (GC)")))});static trackTextureUsage(t){const e=t.uuid,s=this.textureRefCounts.get(e)||0;this.textureRefCounts.set(e,s+1),h==="verbose"&&console.log(`[gltf-progressive] Track texture ${e}, refCount: ${s} → ${s+1}`)}static untrackTextureUsage(t){const e=t.uuid,s=this.textureRefCounts.get(e);if(!s)return(h==="verbose"||E)&&o("[gltf-progressive] Memory: Untrack untracked texture (dispose immediately)",0),t.dispose(),!0;const r=s-1;if(r<=0)return this.textureRefCounts.delete(e),(h||E)&&o("[gltf-progressive] Memory: Dispose texture",r),t.dispose(),!0;return this.textureRefCounts.set(e,r),h==="verbose"&&o("[gltf-progressive] Memory: Untrack texture",r),!1;function o(i,l){let{width:a,height:u}=me(t);const d=a&&u?`${a}x${u}`:"N/A";let x="N/A";a&&u&&(x=`~${(it(t)/(1024*1024)).toFixed(2)} MB`),console.log(`${i} — ${t.name} ${d} (${x}), refCount: ${s} → ${l}
|
|
3
|
+
${e}`)}}static workers=[];static _workersIndex=0;static async getOrLoadLOD(t,e){const s=h=="verbose",r=this.getAssignedLODInformation(t);if(!r)return h&&console.warn(`[gltf-progressive] No LOD information found: ${t.name}, uuid: ${t.uuid}, type: ${t.type}`,t),null;const o=r?.key;let i;if(t.isTexture===!0){const a=t;a.source&&a.source[le]&&(i=a.source[le])}if(i||(i=m.lodInfos.get(o)),!i)h&&console.warn(`Can not load LOD ${e}: no LOD info found for "${o}" ${t.name}`,t.type,m.lodInfos);else{if(e>0){let d=!1;const x=Array.isArray(i.lods);if(x&&e>=i.lods.length?d=!0:x||(d=!0),d){const L=this.lowresCache.get(o);if(L){const _=L.deref();if(_)return _;this.lowresCache.delete(o),h&&console.log(`[gltf-progressive] Lowres cache entry was GC'd: ${o}`)}return null}}const a=Array.isArray(i.lods)?i.lods[e]?.path:i.lods;if(!a)return h&&!i["missing:uri"]&&(i["missing:uri"]=!0,console.warn("Missing uri for progressive asset for LOD "+e,i)),null;const u=ot(r.url,a);if(u.endsWith(".glb")||u.endsWith(".gltf")){if(!i.guid)return console.warn("missing pointer for glb/gltf texture",i),null;const d=u+"_"+i.guid,x=await this.queue.slot(u),L=this.cache.get(d);if(L!==void 0)if(s&&console.log(`LOD ${e} was already loading/loaded: ${d}`),L instanceof WeakRef){const f=L.deref();if(f){let p=f,b=!1;if(p instanceof c.Texture&&t instanceof c.Texture?De(p.image)||pe(p)?p=this.copySettings(t,p):b=!0:p instanceof c.BufferGeometry&&t instanceof c.BufferGeometry&&(p.attributes.position?.array||(b=!0)),!b)return p}this.cache.delete(d),h&&console.log(`[gltf-progressive] Re-loading GC'd/disposed resource: ${d}`)}else{let f=await L.catch(b=>(console.error(`Error loading LOD ${e} from ${u}
|
|
4
|
+
`,b),null)),p=!1;if(f==null||(f instanceof c.Texture&&t instanceof c.Texture?De(f.image)||pe(f)?f=this.copySettings(t,f):(p=!0,this.cache.delete(d)):f instanceof c.BufferGeometry&&t instanceof c.BufferGeometry&&(f.attributes.position?.array||(p=!0,this.cache.delete(d)))),!p)return f}if(!x.use)return h&&console.log(`LOD ${e} was aborted: ${u}`),null;const _=i,R=new Promise(async(f,p)=>{if(gt){const w=await(await ft({})).load(u);if(w.textures.length>0)for(const g of w.textures){let y=g.texture;return m.assignLODInformation(r.url,y,o,e,void 0),t instanceof c.Texture&&(y=this.copySettings(t,y)),y&&(y.guid=_.guid),f(y)}if(w.geometries.length>0){const g=new Array;for(const y of w.geometries){const T=y.geometry;m.assignLODInformation(r.url,T,o,e,y.primitiveIndex),g.push(T)}return f(g)}return f(null)}const b=new xe.GLTFLoader;we(b),h&&(await new Promise(v=>setTimeout(v,1e3)),s&&console.warn("Start loading (delayed) "+u,_.guid));let $=u;if(_&&Array.isArray(_.lods)){const v=_.lods[e];v.hash&&($+="?v="+v.hash)}const O=await b.loadAsync($).catch(v=>(console.error(`Error loading LOD ${e} from ${u}
|
|
5
|
+
`,v),f(null)));if(!O)return f(null);const V=O.parser;s&&console.log("Loading finished "+u,_.guid);let P=0;if(O.parser.json.textures){let v=!1;for(const w of O.parser.json.textures){if(w?.extensions){const g=w?.extensions[F];if(g?.guid&&g.guid===_.guid){v=!0;break}}P++}if(v){let w=await V.getDependency("texture",P);return w&&m.assignLODInformation(r.url,w,o,e,void 0),s&&console.log('change "'+t.name+'" → "'+w.name+'"',u,P,w,d),t instanceof c.Texture&&(w=this.copySettings(t,w)),w&&(w.guid=_.guid),f(w)}else h&&console.warn("Could not find texture with guid",_.guid,O.parser.json)}if(P=0,O.parser.json.meshes){let v=!1;for(const w of O.parser.json.meshes){if(w?.extensions){const g=w?.extensions[F];if(g?.guid&&g.guid===_.guid){v=!0;break}}P++}if(v){const w=await V.getDependency("mesh",P);if(s&&console.log(`Loaded Mesh "${w.name}"`,u,P,w,d),w.isMesh===!0){const g=w.geometry;return m.assignLODInformation(r.url,g,o,e,0),f(g)}else{const g=new Array;for(let y=0;y<w.children.length;y++){const T=w.children[y];if(T.isMesh===!0){const S=T.geometry;m.assignLODInformation(r.url,S,o,e,y),g.push(S)}}return f(g)}}else h&&console.warn("Could not find mesh with guid",_.guid,O.parser.json)}return f(null)});this.cache.set(d,R),x.use(R);const D=await R;return D!=null?D instanceof c.Texture?(this.cache.set(d,new WeakRef(D)),m._resourceRegistry.register(D,d)):Array.isArray(D)?this.cache.set(d,Promise.resolve(D)):this.cache.set(d,Promise.resolve(D)):this.cache.set(d,Promise.resolve(null)),D}else if(t instanceof c.Texture){s&&console.log("Load texture from uri: "+u);const x=await new c.TextureLoader().loadAsync(u);return x?(x.guid=i.guid,x.flipY=!1,x.needsUpdate=!0,x.colorSpace=t.colorSpace,s&&console.log(i,x)):h&&console.warn("failed loading",u),x}}return null}static queue=new nt(_e()?20:50,{debug:h!=!1});static assignLODInformation(t,e,s,r,o){if(!e)return;e.userData||(e.userData={});const i=new pt(t,s,r,o);e.userData.LODS=i,"source"in e&&typeof e.source=="object"&&(e.source.LODS=i)}static getAssignedLODInformation(t){return t?t.userData?.LODS?t.userData.LODS:"source"in t&&t.source?.LODS?t.source.LODS:null:null}static copySettings(t,e){return e?(h==="verbose"&&console.debug(`Copy texture settings
|
|
6
|
+
`,t.uuid,`
|
|
7
|
+
`,e.uuid),e=e.clone(),e.offset=t.offset,e.repeat=t.repeat,e.colorSpace=t.colorSpace,e.magFilter=t.magFilter,e.minFilter=t.minFilter,e.wrapS=t.wrapS,e.wrapT=t.wrapT,e.flipY=t.flipY,e.anisotropy=t.anisotropy,e.mipmaps||(e.generateMipmaps=t.generateMipmaps),e):t}}class pt{url;key;level;index;constructor(t,e,s,r){this.url=t,this.key=e,this.level=s,r!=null&&(this.index=r)}}class ue{static addPromise=(t,e,s,r)=>{r.forEach(o=>{o.add(t,e,s)})};ready;get awaitedCount(){return this._addedCount}get resolvedCount(){return this._resolvedCount}get currentlyAwaiting(){return this._awaiting.length}_resolve;_signal;_frame_start;_frames_to_capture;_resolved=!1;_addedCount=0;_resolvedCount=0;_awaiting=[];_maxPromisesPerObject=1;constructor(t,e){const r=Math.max(e.frames??2,2);this._frame_start=e.waitForFirstCapture?void 0:t,this._frames_to_capture=r,this.ready=new Promise(o=>{this._resolve=o}),this.ready.finally(()=>{this._resolved=!0,this._awaiting.length=0}),this._signal=e.signal,this._signal?.addEventListener("abort",()=>{this.resolveNow()}),this._maxPromisesPerObject=Math.max(1,e.maxPromisesPerObject??1)}_currentFrame=0;update(t){this._currentFrame=t,this._frame_start===void 0&&this._addedCount>0&&(this._frame_start=t),(this._signal?.aborted||this._awaiting.length===0&&this._frame_start!==void 0&&t>this._frame_start+this._frames_to_capture)&&this.resolveNow()}_seen=new WeakMap;add(t,e,s){if(this._resolved){h&&console.warn("PromiseGroup: Trying to add a promise to a resolved group, ignoring.");return}if(!(this._frame_start!==void 0&&this._currentFrame>this._frame_start+this._frames_to_capture)){if(this._maxPromisesPerObject>=1)if(this._seen.has(e)){let r=this._seen.get(e);if(r>=this._maxPromisesPerObject){h&&console.warn("PromiseGroup: Already awaiting object ignoring new promise for it.");return}this._seen.set(e,r+1)}else this._seen.set(e,1);this._awaiting.push(s),this._addedCount++,s.finally(()=>{this._resolvedCount++,this._awaiting.splice(this._awaiting.indexOf(s),1)})}}resolveNow(){this._resolved||this._resolve?.({awaited_count:this._addedCount,resolved_count:this._resolvedCount,cancelled:this._signal?.aborted??!1})}}const I=z("debugprogressive"),mt=z("noprogressive"),ce=Symbol("Needle:LODSManager"),de=Symbol("Needle:LODState"),U=Symbol("Needle:CurrentLOD"),C={mesh_lod:-1,texture_lod:-1};class M{static debugDrawLine;static getObjectLODState(t){return t[de]}static addPlugin(t){W.push(t)}static removePlugin(t){const e=W.indexOf(t);e>=0&&W.splice(e,1)}static get(t,e){if(t[ce])return console.debug("[gltf-progressive] LODsManager already exists for this renderer"),t[ce];const s=new M(t,{engine:"unknown",...e});return t[ce]=s,s}renderer;context;projectionScreenMatrix=new c.Matrix4;get plugins(){return W}overrideLodLevel=void 0;targetTriangleDensity=2e5;skinnedMeshAutoUpdateBoundsInterval=30;updateInterval="auto";#e=1;pause=!1;manual=!1;_newPromiseGroups=[];_promiseGroupIds=0;awaitLoading(t){const e=this._promiseGroupIds++,s=new ue(this.#r,{...t});this._newPromiseGroups.push(s);const r=performance.now();return s.ready.finally(()=>{const o=this._newPromiseGroups.indexOf(s);o>=0&&(this._newPromiseGroups.splice(o,1),Oe()&&performance.measure("LODsManager:awaitLoading",{start:r,detail:{id:e,name:t?.name,awaited:s.awaitedCount,resolved:s.resolvedCount}}))}),s.ready}_postprocessPromiseGroups(){if(this._newPromiseGroups.length!==0)for(let t=this._newPromiseGroups.length-1;t>=0;t--)this._newPromiseGroups[t].update(this.#r)}_lodchangedlisteners=[];addEventListener(t,e){t==="changed"&&this._lodchangedlisteners.push(e)}removeEventListener(t,e){if(t==="changed"){const s=this._lodchangedlisteners.indexOf(e);s>=0&&this._lodchangedlisteners.splice(s,1)}}constructor(t,e){this.renderer=t,this.context={...e}}#t;#n=new c.Timer;#r=0;#o=0;#i=0;#s=0;_fpsBuffer=[60,60,60,60,60];enable(){if(this.#t)return;console.debug("[gltf-progressive] Enabling LODsManager for renderer");let t=0;this.#t=this.renderer.render;const e=this;re(this.renderer),this.renderer.render=function(s,r){const o=e.renderer.getRenderTarget();(o==null||"isXRRenderTarget"in o&&o.isXRRenderTarget)&&(t=0,e.#r+=1,e.#n.update(),e.#o=e.#n.getDelta(),e.#i+=e.#o,e._fpsBuffer.shift(),e._fpsBuffer.push(1/e.#o),e.#s=e._fpsBuffer.reduce((l,a)=>l+a)/e._fpsBuffer.length,I&&e.#r%200===0&&console.log("FPS",Math.round(e.#s),"Interval:",e.#e));const i=t++;e.#t.call(this,s,r),e.onAfterRender(s,r,i)}}disable(){this.#t&&(console.debug("[gltf-progressive] Disabling LODsManager for renderer"),this.renderer.render=this.#t,this.#t=void 0)}update(t,e){this.internalUpdate(t,e)}onAfterRender(t,e,s){if(this.pause)return;const o=this.renderer.renderLists.get(t,0).opaque;let i=!0;if(o.length===1){const l=o[0].material;(l.name==="EffectMaterial"||l.name==="CopyShader")&&(i=!1)}if((e.parent&&e.parent.type==="CubeCamera"||s>=1&&e.type==="OrthographicCamera")&&(i=!1),i){if(mt||(this.updateInterval==="auto"?this.#s<40&&this.#e<10?(this.#e+=1,I&&console.warn("↓ Reducing LOD updates",this.#e,this.#s.toFixed(0))):this.#s>=60&&this.#e>1&&(this.#e-=1,I&&console.warn("↑ Increasing LOD updates",this.#e,this.#s.toFixed(0))):this.#e=this.updateInterval,this.#e>0&&this.#r%this.#e!=0))return;this.internalUpdate(t,e),this._postprocessPromiseGroups()}}internalUpdate(t,e){const s=this.renderer.renderLists.get(t,0),r=s.opaque;this.projectionScreenMatrix.multiplyMatrices(e.projectionMatrix,e.matrixWorldInverse);const o=this.targetTriangleDensity;for(const a of r){if(a.material&&(a.geometry?.type==="BoxGeometry"||a.geometry?.type==="BufferGeometry")&&(a.material.name==="SphericalGaussianBlur"||a.material.name=="BackgroundCubeMaterial"||a.material.name==="CubemapFromEquirect"||a.material.name==="EquirectangularToCubeUV")){I&&(a.material["NEEDLE_PROGRESSIVE:IGNORE-WARNING"]||(a.material["NEEDLE_PROGRESSIVE:IGNORE-WARNING"]=!0,console.warn("Ignoring skybox or BLIT object",a,a.material.name,a.material.type)));continue}switch(a.material.type){case"LineBasicMaterial":case"LineDashedMaterial":case"PointsMaterial":case"ShadowMaterial":case"MeshDistanceMaterial":case"MeshDepthMaterial":continue}if(I==="color"&&a.material&&!a.object.progressive_debug_color){a.object.progressive_debug_color=!0;const d=Math.random()*16777215,x=new c.MeshStandardMaterial({color:d});a.object.material=x}const u=a.object;(u instanceof c.Mesh||u.isMesh)&&this.updateLODs(t,e,u,o)}const i=s.transparent;for(const a of i){const u=a.object;(u instanceof c.Mesh||u.isMesh)&&this.updateLODs(t,e,u,o)}const l=s.transmissive;for(const a of l){const u=a.object;(u instanceof c.Mesh||u.isMesh)&&this.updateLODs(t,e,u,o)}}updateLODs(t,e,s,r){s.userData||(s.userData={});let o=s[de];if(o||(o=new yt,s[de]=o),o.frames++<2)return;for(const l of W)l.onBeforeUpdateLOD?.(this.renderer,t,e,s);const i=this.overrideLodLevel!==void 0?this.overrideLodLevel:N;i>=0?(C.mesh_lod=i,C.texture_lod=i):(this.calculateLodLevel(e,s,o,r,C),C.mesh_lod=Math.round(C.mesh_lod),C.texture_lod=Math.round(C.texture_lod)),C.mesh_lod>=0&&this.loadProgressiveMeshes(s,C.mesh_lod),s.material&&C.texture_lod>=0&&this.loadProgressiveTextures(s.material,C.texture_lod,i),h&&s.material&&!s.isGizmo&&Ge(s.material);for(const l of W)l.onAfterUpdatedLOD?.(this.renderer,t,e,s,C);o.lastLodLevel_Mesh=C.mesh_lod,o.lastLodLevel_Texture=C.texture_lod}loadProgressiveTextures(t,e,s){if(!t)return;if(Array.isArray(t)){for(const o of t)this.loadProgressiveTextures(o,e);return}let r=!1;if((t[U]===void 0||e<t[U])&&(r=!0),s!==void 0&&s>=0&&(r=t[U]!=s,e=s),r){t[U]=e;const o=m.assignTextureLOD(t,e).then(i=>{this._lodchangedlisteners.forEach(l=>l({type:"texture",level:e,object:t}))});ue.addPromise("texture",t,o,this._newPromiseGroups)}}loadProgressiveMeshes(t,e){if(!t)return Promise.resolve(null);let s=t[U]!==e;const r=t["DEBUG:LOD"];if(r!=null&&(s=t[U]!=r,e=r),s){t[U]=e;const o=t.geometry,i=m.assignMeshLOD(t,e).then(l=>(l&&t[U]==e&&o!=t.geometry&&this._lodchangedlisteners.forEach(a=>a({type:"mesh",level:e,object:t})),l));return ue.addPromise("mesh",t,i,this._newPromiseGroups),i}return Promise.resolve(null)}_sphere=new c.Sphere;_tempBox=new c.Box3;_tempBox2=new c.Box3;tempMatrix=new c.Matrix4;_tempWorldPosition=new c.Vector3;_tempBoxSize=new c.Vector3;_tempBox2Size=new c.Vector3;static corner0=new c.Vector3;static corner1=new c.Vector3;static corner2=new c.Vector3;static corner3=new c.Vector3;static _tempPtInside=new c.Vector3;static isInside(t,e){const s=t.min,r=t.max,o=(s.x+r.x)*.5,i=(s.y+r.y)*.5;return this._tempPtInside.set(o,i,s.z).applyMatrix4(e).z<0}static skinnedMeshBoundsFrameOffsetCounter=0;static $skinnedMeshBoundsOffset=Symbol("gltf-progressive-skinnedMeshBoundsOffset");calculateLodLevel(t,e,s,r,o){if(!e){o.mesh_lod=-1,o.texture_lod=-1;return}if(!t){o.mesh_lod=-1,o.texture_lod=-1;return}let l=10+1,a=!1;if(I&&e["DEBUG:LOD"]!=null)return e["DEBUG:LOD"];const u=m.getMeshLODExtension(e.geometry)?.lods,d=m.getPrimitiveIndex(e.geometry),x=u&&u.length>0,L=m.getMaterialMinMaxLODsCount(e.material),_=L.min_count!==1/0&&L.min_count>=0&&L.max_count>=0;if(!x&&!_){o.mesh_lod=0,o.texture_lod=0;return}x||(a=!0,l=0);const R=this.renderer.domElement.clientHeight||this.renderer.domElement.height;let D=e.geometry.boundingBox;if(e.type==="SkinnedMesh"){const f=e;if(!f.boundingBox)f.computeBoundingBox();else if(this.skinnedMeshAutoUpdateBoundsInterval>0){if(!f[M.$skinnedMeshBoundsOffset]){const b=M.skinnedMeshBoundsFrameOffsetCounter++;f[M.$skinnedMeshBoundsOffset]=b}const p=f[M.$skinnedMeshBoundsOffset];if((s.frames+p)%this.skinnedMeshAutoUpdateBoundsInterval===0){const b=j(f),$=f.geometry;b&&(f.geometry=b),f.computeBoundingBox(),f.geometry=$}}D=f.boundingBox}if(D){const f=t;if(e.geometry.attributes.color&&e.geometry.attributes.color.count<100&&e.geometry.boundingSphere){this._sphere.copy(e.geometry.boundingSphere),this._sphere.applyMatrix4(e.matrixWorld);const g=t.getWorldPosition(this._tempWorldPosition);if(this._sphere.containsPoint(g)){o.mesh_lod=0,o.texture_lod=0;return}}if(this._tempBox.copy(D),this._tempBox.applyMatrix4(e.matrixWorld),f.isPerspectiveCamera&&M.isInside(this._tempBox,this.projectionScreenMatrix)){o.mesh_lod=0,o.texture_lod=0;return}if(this._tempBox.applyMatrix4(this.projectionScreenMatrix),this.renderer.xr.enabled&&f.isPerspectiveCamera&&f.fov>70){const g=this._tempBox.min,y=this._tempBox.max;let T=g.x,S=g.y,G=y.x,X=y.y;const J=2,oe=1.5,Z=(g.x+y.x)*.5,ee=(g.y+y.y)*.5;T=(T-Z)*J+Z,S=(S-ee)*J+ee,G=(G-Z)*J+Z,X=(X-ee)*J+ee;const We=T<0&&G>0?0:Math.min(Math.abs(g.x),Math.abs(y.x)),ze=S<0&&X>0?0:Math.min(Math.abs(g.y),Math.abs(y.y)),ne=Math.max(We,ze);s.lastCentrality=(oe-ne)*(oe-ne)*(oe-ne)}else s.lastCentrality=1;const p=this._tempBox.getSize(this._tempBoxSize);p.multiplyScalar(.5),screen.availHeight>0&&R>0&&p.multiplyScalar(R/screen.availHeight),t.isPerspectiveCamera?p.x*=t.aspect:t.isOrthographicCamera;const b=t.matrixWorldInverse,$=this._tempBox2;$.copy(D),$.applyMatrix4(e.matrixWorld),$.applyMatrix4(b);const O=$.getSize(this._tempBox2Size),V=Math.max(O.x,O.y);if(Math.max(p.x,p.y)!=0&&V!=0&&(p.z=O.z/Math.max(O.x,O.y)*Math.max(p.x,p.y)),s.lastScreenCoverage=Math.max(p.x,p.y,p.z),s.lastScreenspaceVolume.copy(p),s.lastScreenCoverage*=s.lastCentrality,I&&M.debugDrawLine){const g=this.tempMatrix.copy(this.projectionScreenMatrix);g.invert();const y=M.corner0,T=M.corner1,S=M.corner2,G=M.corner3;y.copy(this._tempBox.min),T.copy(this._tempBox.max),T.x=y.x,S.copy(this._tempBox.max),S.y=y.y,G.copy(this._tempBox.max);const X=(y.z+G.z)*.5;y.z=T.z=S.z=G.z=X,y.applyMatrix4(g),T.applyMatrix4(g),S.applyMatrix4(g),G.applyMatrix4(g),M.debugDrawLine(y,T,255),M.debugDrawLine(y,S,255),M.debugDrawLine(T,G,255),M.debugDrawLine(S,G,255)}let v=999;if(u&&s.lastScreenCoverage>0)for(let g=0;g<u.length;g++){const y=u[g],S=(y.densities?.[d]||y.density||1e-5)/s.lastScreenCoverage;if(d>0&&Oe()&&!y.densities&&!globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"]&&(window["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"]=!0,console.warn("[Needle Progressive] Detected usage of mesh without primitive densities. This might cause incorrect LOD level selection: Consider re-optimizing your model by updating your Needle Integration, Needle glTF Pipeline or running optimization again on Needle Cloud.")),S<r){v=g;break}}v<l&&(l=v,a=!0)}if(a?o.mesh_lod=l:o.mesh_lod=s.lastLodLevel_Mesh,I&&o.mesh_lod!=s.lastLodLevel_Mesh){const p=u?.[o.mesh_lod];p&&console.log(`Mesh LOD changed: ${s.lastLodLevel_Mesh} → ${o.mesh_lod} (density: ${p.densities?.[d].toFixed(0)}) | ${e.name}`)}if(_){const f="saveData"in globalThis.navigator&&globalThis.navigator.saveData===!0;if(s.lastLodLevel_Texture<0){if(o.texture_lod=L.max_count-1,I){const p=L.lods[L.max_count-1];I&&console.log(`First Texture LOD ${o.texture_lod} (${p.max_height}px) - ${e.name}`)}}else{const p=s.lastScreenspaceVolume.x+s.lastScreenspaceVolume.y+s.lastScreenspaceVolume.z;let b=s.lastScreenCoverage*4;this.context?.engine==="model-viewer"&&(b*=1.5);const O=R/window.devicePixelRatio*b;let V=!1;for(let P=L.lods.length-1;P>=0;P--){const v=L.lods[P];if(!(f&&v.max_height>=2048)&&!(_e()&&v.max_height>4096)&&(v.max_height>O||!V&&P===0)){if(V=!0,o.texture_lod=P,I&&o.texture_lod<s.lastLodLevel_Texture){const w=v.max_height;console.log(`Texture LOD changed: ${s.lastLodLevel_Texture} → ${o.texture_lod} = ${w}px
|
|
8
|
+
Screensize: ${O.toFixed(0)}px, Coverage: ${(100*s.lastScreenCoverage).toFixed(2)}%, Volume ${p.toFixed(1)}
|
|
9
|
+
${e.name}`)}break}}}}else o.texture_lod=0}}class yt{frames=0;lastLodLevel_Mesh=-1;lastLodLevel_Texture=-1;lastScreenCoverage=0;lastScreenspaceVolume=new c.Vector3;lastCentrality=0}const Te=Symbol("NEEDLE_mesh_lod"),te=Symbol("NEEDLE_texture_lod");let fe=null;function Me(){const n=xt();n&&(n.mapURLs(function(t){return Se(),t}),Se(),fe?.disconnect(),fe=new MutationObserver(t=>{t.forEach(e=>{e.addedNodes.forEach(s=>{s instanceof HTMLElement&&s.tagName.toLowerCase()==="model-viewer"&&Fe(s)})})}),fe.observe(document,{childList:!0,subtree:!0}))}function xt(){if(typeof customElements>"u")return null;const n=customElements.get("model-viewer");return n||(customElements.whenDefined("model-viewer").then(()=>{console.debug("[gltf-progressive] model-viewer defined"),Me()}),null)}function Se(){if(typeof document>"u")return;document.querySelectorAll("model-viewer").forEach(t=>{Fe(t)})}const Pe=new WeakSet;let wt=0;function Fe(n){if(!n||Pe.has(n))return null;Pe.add(n),console.debug("[gltf-progressive] found new model-viewer..."+ ++wt+`
|
|
10
|
+
`,n.getAttribute("src"));let t=null,e=null,s=null;for(let r=n;r!=null;r=Object.getPrototypeOf(r)){const o=Object.getOwnPropertySymbols(r),i=o.find(u=>u.toString()=="Symbol(renderer)"),l=o.find(u=>u.toString()=="Symbol(scene)"),a=o.find(u=>u.toString()=="Symbol(needsRender)");!t&&i!=null&&(t=n[i].threeRenderer),!e&&l!=null&&(e=n[l]),!s&&a!=null&&(s=n[a])}if(t&&e){let r=function(){if(s){let i=0,l=setInterval(()=>{if(i++>5){clearInterval(l);return}s?.call(n)},300)}};console.debug("[gltf-progressive] setup model-viewer");const o=M.get(t,{engine:"model-viewer"});return M.addPlugin(new Lt),o.enable(),o.addEventListener("changed",()=>{s?.call(n)}),n.addEventListener("model-visibility",i=>{i.detail.visible&&s?.call(n)}),n.addEventListener("load",()=>{r()}),()=>{o.disable()}}return null}class Lt{_didWarnAboutMissingUrl=!1;onBeforeUpdateLOD(t,e,s,r){this.tryParseMeshLOD(e,r),this.tryParseTextureLOD(e,r)}getUrl(t){if(!t)return null;let e=t.getAttribute("src");return e||(e=t.src),e||(this._didWarnAboutMissingUrl||console.warn("No url found in modelviewer",t),this._didWarnAboutMissingUrl=!0),e}tryGetCurrentGLTF(t){return t._currentGLTF}tryGetCurrentModelViewer(t){return t.element}tryParseTextureLOD(t,e){if(e[te]==!0)return;e[te]=!0;const s=this.tryGetCurrentGLTF(t),r=this.tryGetCurrentModelViewer(t),o=this.getUrl(r);if(o&&s&&e.material){let i=function(a){if(a[te]==!0)return;a[te]=!0,a.userData&&(a.userData.LOD=-1);const u=Object.keys(a);for(let d=0;d<u.length;d++){const x=u[d],L=a[x];if(L?.isTexture===!0){const _=L.userData?.associations?.textures;if(_==null)continue;const R=s.parser.json.textures[_];if(!R){console.warn("Texture data not found for texture index "+_);continue}if(R?.extensions?.[F]){const D=R.extensions[F];D&&o&&m.registerTexture(o,L,D.lods.length,_,D)}}}};const l=e.material;if(Array.isArray(l))for(const a of l)i(a);else i(l)}}tryParseMeshLOD(t,e){if(e[Te]==!0)return;e[Te]=!0;const s=this.tryGetCurrentModelViewer(t),r=this.getUrl(s);if(!r)return;const o=e.userData?.gltfExtensions?.[F];if(o&&r){const i=e.uuid;m.registerMesh(r,i,e,0,o.lods.length,o)}}}function Ue(...n){let t,e,s,r;switch(n.length){case 2:[s,e]=n,r={};break;case 3:[s,e,r]=n;break;case 4:[t,e,s,r]=n;break;default:throw new Error("Invalid arguments")}re(e),we(s),Le(s,{progressive:!0,...r?.hints}),s.register(i=>new m(i));const o=M.get(e);return r?.enableLODsManager!==!1&&o.enable(),o}Me();if(!lt){const n={gltfProgressive:{useNeedleProgressive:Ue,LODsManager:M,configureLoader:Le,getRaycastMesh:j,useRaycastMeshes:$e}};if(!globalThis.Needle)globalThis.Needle=n;else for(const t in n)globalThis.Needle[t]=n[t]}exports.EXTENSION_NAME=F;exports.LODsManager=M;exports.NEEDLE_progressive=m;exports.VERSION=Ce;exports.addDracoAndKTX2Loaders=we;exports.configureLoader=Le;exports.createLoaders=re;exports.getRaycastMesh=j;exports.patchModelViewer=Me;exports.registerRaycastMesh=Be;exports.setDracoDecoderLocation=Re;exports.setKTX2TranscoderLocation=Ie;exports.useNeedleProgressive=Ue;exports.useRaycastMeshes=$e;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { BufferGeometry, Material, Mesh, Texture } from "three";
|
|
2
|
+
import { type GLTF, type GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
3
|
+
import { NEEDLE_ext_progressive_mesh, NEEDLE_ext_progressive_texture } from "./extension.model.js";
|
|
4
|
+
export declare const EXTENSION_NAME = "NEEDLE_progressive";
|
|
5
|
+
/**
|
|
6
|
+
* This is the result of a progressive texture loading event for a material's texture slot in {@link NEEDLE_progressive.assignTextureLOD}
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
type ProgressiveMaterialTextureLoadingResult = {
|
|
10
|
+
/** the material the progressive texture was loaded for */
|
|
11
|
+
material: Material;
|
|
12
|
+
/** the slot in the material where the texture was loaded */
|
|
13
|
+
slot: string;
|
|
14
|
+
/** the texture that was loaded (if any) */
|
|
15
|
+
texture: Texture | null;
|
|
16
|
+
/** the level of detail that was loaded */
|
|
17
|
+
level: number;
|
|
18
|
+
};
|
|
19
|
+
type TextureLODsMinMaxInfo = {
|
|
20
|
+
min_count: number;
|
|
21
|
+
max_count: number;
|
|
22
|
+
lods: Array<{
|
|
23
|
+
min_height: number;
|
|
24
|
+
max_height: number;
|
|
25
|
+
}>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The NEEDLE_progressive extension for the GLTFLoader is responsible for loading progressive LODs for meshes and textures.
|
|
29
|
+
* This extension can be used to load different resolutions of a mesh or texture at runtime (e.g. for LODs or progressive textures).
|
|
30
|
+
* @example
|
|
31
|
+
* ```javascript
|
|
32
|
+
* const loader = new GLTFLoader();
|
|
33
|
+
* loader.register(new NEEDLE_progressive());
|
|
34
|
+
* loader.load("model.glb", (gltf) => {
|
|
35
|
+
* const mesh = gltf.scene.children[0] as Mesh;
|
|
36
|
+
* NEEDLE_progressive.assignMeshLOD(context, sourceId, mesh, 1).then(mesh => {
|
|
37
|
+
* console.log("Mesh with LOD level 1 loaded", mesh);
|
|
38
|
+
* });
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare class NEEDLE_progressive implements GLTFLoaderPlugin {
|
|
43
|
+
/** The name of the extension */
|
|
44
|
+
get name(): string;
|
|
45
|
+
static getMeshLODExtension(geo: BufferGeometry): NEEDLE_ext_progressive_mesh | null;
|
|
46
|
+
static getPrimitiveIndex(geo: BufferGeometry): number;
|
|
47
|
+
static getMaterialMinMaxLODsCount(material: Material | Material[], minmax?: TextureLODsMinMaxInfo): TextureLODsMinMaxInfo;
|
|
48
|
+
/** Check if a LOD level is available for a mesh or a texture
|
|
49
|
+
* @param obj the mesh or texture to check
|
|
50
|
+
* @param level the level of detail to check for (0 is the highest resolution). If undefined, the function checks if any LOD level is available
|
|
51
|
+
* @returns true if the LOD level is available (or if any LOD level is available if level is undefined)
|
|
52
|
+
*/
|
|
53
|
+
static hasLODLevelAvailable(obj: Mesh | BufferGeometry | Texture | Material | Material[], level?: number): boolean;
|
|
54
|
+
/** Load a different resolution of a mesh (if available)
|
|
55
|
+
* @param context the context
|
|
56
|
+
* @param source the sourceid of the file from which the mesh is loaded (this is usually the component's sourceId)
|
|
57
|
+
* @param mesh the mesh to load the LOD for
|
|
58
|
+
* @param level the level of detail to load (0 is the highest resolution)
|
|
59
|
+
* @returns a promise that resolves to the mesh with the requested LOD level
|
|
60
|
+
* @example
|
|
61
|
+
* ```javascript
|
|
62
|
+
* const mesh = this.gameObject as Mesh;
|
|
63
|
+
* NEEDLE_progressive.assignMeshLOD(context, sourceId, mesh, 1).then(mesh => {
|
|
64
|
+
* console.log("Mesh with LOD level 1 loaded", mesh);
|
|
65
|
+
* });
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
static assignMeshLOD(mesh: Mesh, level: number): Promise<BufferGeometry | null>;
|
|
69
|
+
/** Load a different resolution of a texture (if available)
|
|
70
|
+
* @param context the context
|
|
71
|
+
* @param source the sourceid of the file from which the texture is loaded (this is usually the component's sourceId)
|
|
72
|
+
* @param materialOrTexture the material or texture to load the LOD for (if passing in a material all textures in the material will be loaded)
|
|
73
|
+
* @param level the level of detail to load (0 is the highest resolution) - currently only 0 is supported
|
|
74
|
+
* @returns a promise that resolves to the material or texture with the requested LOD level
|
|
75
|
+
*/
|
|
76
|
+
static assignTextureLOD(materialOrTexture: Material, level: number): Promise<Array<ProgressiveMaterialTextureLoadingResult> | null>;
|
|
77
|
+
static assignTextureLOD(materialOrTexture: Mesh, level: number): Promise<Array<ProgressiveMaterialTextureLoadingResult> | null>;
|
|
78
|
+
static assignTextureLOD(materialOrTexture: Texture, level: number): Promise<Texture | null>;
|
|
79
|
+
/**
|
|
80
|
+
* Set the maximum number of concurrent loading tasks for LOD resources. This limits how many LOD resources (meshes or textures) can be loaded at the same time to prevent overloading the network or GPU. If the limit is reached, additional loading requests will be queued and processed as previous ones finish.
|
|
81
|
+
* @default 50 on desktop, 20 on mobile devices
|
|
82
|
+
*/
|
|
83
|
+
static set maxConcurrentLoadingTasks(value: number);
|
|
84
|
+
static get maxConcurrentLoadingTasks(): number;
|
|
85
|
+
private static assignTextureLODForSlot;
|
|
86
|
+
private readonly parser;
|
|
87
|
+
private readonly url;
|
|
88
|
+
constructor(parser: GLTFParser);
|
|
89
|
+
private _isLoadingMesh;
|
|
90
|
+
loadMesh: (meshIndex: number) => Promise<any> | null;
|
|
91
|
+
afterRoot(gltf: GLTF): null;
|
|
92
|
+
/**
|
|
93
|
+
* Register a texture with LOD information
|
|
94
|
+
*/
|
|
95
|
+
static registerTexture: (url: string, tex: Texture, level: number, index: number, ext: NEEDLE_ext_progressive_texture) => void;
|
|
96
|
+
/**
|
|
97
|
+
* Register a mesh with LOD information
|
|
98
|
+
*/
|
|
99
|
+
static registerMesh: (url: string, key: string, mesh: Mesh, level: number, index: number, ext: NEEDLE_ext_progressive_mesh) => void;
|
|
100
|
+
/**
|
|
101
|
+
* Dispose cached resources to free memory.
|
|
102
|
+
* Call this when a model is removed from the scene to allow garbage collection of its LOD resources.
|
|
103
|
+
* Calls three.js `.dispose()` on cached Textures and BufferGeometries to free GPU memory.
|
|
104
|
+
* Also clears reference counts for disposed textures.
|
|
105
|
+
* @param guid Optional GUID to dispose resources for a specific model. If omitted, all cached resources are cleared.
|
|
106
|
+
*/
|
|
107
|
+
static dispose(guid?: string): void;
|
|
108
|
+
/** Dispose a single cache entry's three.js resource(s) to free GPU memory. */
|
|
109
|
+
private static _disposeCacheEntry;
|
|
110
|
+
/** A map of key = asset uuid and value = LOD information */
|
|
111
|
+
private static readonly lodInfos;
|
|
112
|
+
/** cache of already loaded mesh lods. Uses WeakRef for single resources to allow garbage collection when unused. */
|
|
113
|
+
private static readonly cache;
|
|
114
|
+
/** this contains the geometry/textures that were originally loaded. Uses WeakRef to allow garbage collection when unused. */
|
|
115
|
+
private static readonly lowresCache;
|
|
116
|
+
/** Reference counting for textures to track usage across multiple materials/objects */
|
|
117
|
+
private static readonly textureRefCounts;
|
|
118
|
+
/**
|
|
119
|
+
* FinalizationRegistry to automatically clean up `previouslyLoaded` cache entries
|
|
120
|
+
* when their associated three.js resources are garbage collected by the browser.
|
|
121
|
+
* The held value is the cache key string used in `previouslyLoaded`.
|
|
122
|
+
*/
|
|
123
|
+
private static readonly _resourceRegistry;
|
|
124
|
+
/**
|
|
125
|
+
* Track texture usage by incrementing reference count
|
|
126
|
+
*/
|
|
127
|
+
private static trackTextureUsage;
|
|
128
|
+
/**
|
|
129
|
+
* Untrack texture usage by decrementing reference count.
|
|
130
|
+
* Automatically disposes the texture when reference count reaches zero.
|
|
131
|
+
* @returns true if the texture was disposed, false otherwise
|
|
132
|
+
*/
|
|
133
|
+
private static untrackTextureUsage;
|
|
134
|
+
private static readonly workers;
|
|
135
|
+
private static _workersIndex;
|
|
136
|
+
private static getOrLoadLOD;
|
|
137
|
+
private static queue;
|
|
138
|
+
private static assignLODInformation;
|
|
139
|
+
private static getAssignedLODInformation;
|
|
140
|
+
private static copySettings;
|
|
141
|
+
}
|
|
142
|
+
export {};
|