@needle-tools/gltf-progressive 1.0.0-alpha → 1.0.0-alpha.2

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.
@@ -0,0 +1,3 @@
1
+ var se=Object.defineProperty,ne=(r,e,t)=>e in r?se(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t,u=(r,e,t)=>(ne(r,typeof e!="symbol"?e+"":e,t),t);import{Mesh as W,BufferGeometry as U,Material as oe,RawShaderMaterial as ie,Texture as C,TextureLoader as ae,Matrix4 as K,Frustum as le,Sphere as ue,Box3 as Y,Vector3 as E}from"three";import{GLTFLoader as ce}from"three/examples/jsm/loaders/GLTFLoader.js";import{MeshoptDecoder as de}from"three/examples/jsm/libs/meshopt_decoder.module.js";import{DRACOLoader as he}from"three/examples/jsm/loaders/DRACOLoader.js";import{KTX2Loader as fe}from"three/examples/jsm/loaders/KTX2Loader.js";let ge="https://www.gstatic.com/draco/versioned/decoders/1.4.1/",pe="https://www.gstatic.com/basis-universal/versioned/2021-04-15-ba1c3e4/",R,$,G;function J(r){R||(R=new he,R.setDecoderPath(ge),R.setDecoderConfig({type:"js"})),G||(G=new fe,G.setTranscoderPath(pe)),$||($=de),r?G.detectSupport(r):console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures will probably fail")}function Q(r){r.dracoLoader||r.setDRACOLoader(R),r.ktx2Loader||r.setKTX2Loader(G),r.meshoptDecoder||r.setMeshoptDecoder($)}function q(r){let e=new URL(window.location.href).searchParams.get(r);return e==null||e==="0"||e==="false"?!1:e===""?!0:e}function me(r,e){if(e===void 0||e.startsWith("./")||e.startsWith("http")||r===void 0)return e;const t=r.lastIndexOf("/");if(t>=0){const n=r.substring(0,t+1);for(;n.endsWith("/")&&e.startsWith("/");)e=e.substring(1);return n+e}return e}const V=new Array;function ye(r){V.push(r)}const B="NEEDLE_progressive",L=q("debugprogressive"),X=Symbol("needle-progressive-texture"),F=new Map,H=new Set;if(L){let r=function(){e+=1,console.log("Toggle LOD level",e,F),F.forEach((o,s)=>{for(const i of o.keys){const a=s[i];if(a.isBufferGeometry===!0){const l=O.getMeshLODInformation(a),d=l?Math.min(e,l.lods.length):0;s["DEBUG:LOD"]=d,O.assignMeshLOD(s,d),l&&(t=Math.max(t,l.lods.length-1))}else if(s.isMaterial===!0){s["DEBUG:LOD"]=e,O.assignTextureLOD(s,e);break}}}),e>=t&&(e=-1)},e=-1,t=2,n=!1;window.addEventListener("keyup",o=>{o.key==="p"&&r(),o.key==="w"&&(n=!n,H&&H.forEach(s=>{s.name!="BackgroundCubeMaterial"&&"wireframe"in s&&(s.wireframe=n)}))})}function Z(r,e,t){var n;if(!L)return;F.has(r)||F.set(r,{keys:[],sourceId:t});const o=F.get(r);((n=o?.keys)==null?void 0:n.includes(e))==!1&&o.keys.push(e)}const v=class{constructor(r,e){u(this,"parser"),u(this,"url"),L&&console.log("Progressive extension registered for",e),this.parser=r,this.url=e}get name(){return B}static getMeshLODInformation(r){const e=this.getAssignedLODInformation(r);return e!=null&&e.key?this.lodInfos.get(e.key):null}static hasLODLevelAvailable(r,e){var t;if(r.isMaterial===!0){for(const s of Object.keys(r)){const i=r[s];if(i.isTexture&&this.hasLODLevelAvailable(i,e))return!0}return!1}else if(r.isGroup===!0){for(const s of r.children)if(s.isMesh===!0&&this.hasLODLevelAvailable(s,e))return!0}let n,o;if(r.isMesh?n=r.geometry:(r.isBufferGeometry||r.isTexture)&&(n=r),n&&(t=n?.userData)!=null&&t.LODS){const s=n.userData.LODS;if(o=this.lodInfos.get(s.key),e===void 0)return o!=null;if(o)return Array.isArray(o.lods)?e<o.lods.length:e===0}return!1}static assignMeshLOD(r,e){var t;if(!r)return Promise.resolve(null);if(r instanceof W||r.isMesh===!0){const n=r.geometry,o=this.getAssignedLODInformation(n);if(!o)return Promise.resolve(null);for(const s of V)(t=s.onBeforeGetLODMesh)==null||t.call(s,r,e);return r["LOD:requested level"]=e,v.getOrLoadLOD(n,e).then(s=>{if(r["LOD:requested level"]===e){if(delete r["LOD:requested level"],Array.isArray(s)){const i=o.index||0;s=s[i]}s&&n!=s&&s instanceof U&&(r.geometry=s,L&&Z(r,"geometry",o.url))}return s}).catch(s=>(console.error("Error loading mesh LOD",r,s),null))}else L&&console.error("Invalid call to assignMeshLOD: Request mesh LOD but the object is not a mesh",r);return Promise.resolve(null)}static assignTextureLOD(r,e=0){if(!r)return Promise.resolve(null);if(r instanceof oe||r.isMaterial===!0){const t=r,n=[],o=new Array;if(L&&H.add(t),t instanceof ie)for(const s of Object.keys(t.uniforms)){const i=t.uniforms[s].value;if(i?.isTexture===!0){const a=this.assignTextureLODForSlot(i,e,t,s);n.push(a),o.push(s)}}else for(const s of Object.keys(t)){const i=t[s];if(i?.isTexture===!0){const a=this.assignTextureLODForSlot(i,e,t,s);n.push(a),o.push(s)}}return Promise.all(n).then(s=>{const i=new Array;for(let a=0;a<s.length;a++){const l=s[a],d=o[a];l&&l.isTexture===!0?i.push({material:t,slot:d,texture:l,level:e}):i.push({material:t,slot:d,texture:null,level:e})}return i})}if(r instanceof C||r.isTexture===!0){const t=r;return this.assignTextureLODForSlot(t,e,null,null)}return Promise.resolve(null)}static assignTextureLODForSlot(r,e,t,n){return r?.isTexture!==!0?Promise.resolve(null):v.getOrLoadLOD(r,e).then(o=>{if(Array.isArray(o))return null;if(o?.isTexture===!0){if(o!=r&&(t&&n&&(t[n]=o),L&&n&&t)){const s=this.getAssignedLODInformation(r);s&&Z(t,n,s.url)}return o}else L=="verbose"&&console.warn("No LOD found for",r,e);return null}).catch(o=>(console.error("Error loading LOD",r,o),null))}afterRoot(r){var e,t;return L&&console.log("AFTER",this.url,r),(e=this.parser.json.textures)==null||e.forEach((n,o)=>{if(n!=null&&n.extensions){const s=n?.extensions[B];if(s){let i=!1;for(const a of this.parser.associations.keys())a.isTexture===!0&&this.parser.associations.get(a).textures===o&&(i=!0,v.registerTexture(this.url,a,o,s));i||this.parser.getDependency("texture",o).then(a=>{a&&v.registerTexture(this.url,a,o,s)})}}}),(t=this.parser.json.meshes)==null||t.forEach((n,o)=>{if(n!=null&&n.extensions){const s=n?.extensions[B];if(s&&s.lods){for(const i of this.parser.associations.keys())if(i.isMesh){const a=this.parser.associations.get(i);a.meshes===o&&v.registerMesh(this.url,i.uuid,i,s.lods.length,a.primitives,s)}}}}),null}static async getOrLoadLOD(r,e){var t,n,o;const s=L=="verbose",i=r.userData.LODS;if(!i)return null;const a=i?.key;let l;if(r.isTexture===!0){const d=r;d.source&&d.source[X]&&(l=d.source[X])}if(l||(l=v.lodInfos.get(a)),l){if(e>0){let g=!1;const M=Array.isArray(l.lods);if(M&&e>=l.lods.length?g=!0:M||(g=!0),g)return this.lowresCache.get(a)}const d=Array.isArray(l.lods)?l.lods[e].path:l.lods;if(!d)return L&&!l["missing:uri"]&&(l["missing:uri"]=!0,console.warn("Missing uri for progressive asset for LOD "+e,l)),null;const c=me(i.url,d);if(c.endsWith(".glb")||c.endsWith(".gltf")){if(!l.guid)return console.warn("missing pointer for glb/gltf texture",l),null;const g=c+"_"+l.guid,M=this.previouslyLoaded.get(g);if(M!==void 0){s&&console.log(`LOD ${e} was already loading/loaded: ${g}`);let m=await M.catch(p=>(console.error(`Error loading LOD ${e} from ${c}
2
+ `,p),null)),f=!1;if(m==null||(m instanceof C&&r instanceof C?(t=m.image)!=null&&t.data||(n=m.source)!=null&&n.data?m=this.copySettings(r,m):(f=!0,this.previouslyLoaded.delete(g)):m instanceof U&&r instanceof U&&((o=m.attributes.position)!=null&&o.array||(f=!0,this.previouslyLoaded.delete(g)))),!f)return m}const y=l,A=new Promise(async(m,f)=>{const p=new ce;Q(p),L&&(await new Promise(D=>setTimeout(D,1e3)),s&&console.warn("Start loading (delayed) "+c,y.guid));let b=c;if(y&&Array.isArray(y.lods)){const D=y.lods[e];D.hash&&(b+="?v="+D.hash)}const x=await p.loadAsync(b).catch(D=>(console.error(`Error loading LOD ${e} from ${c}
3
+ `,D),null));if(!x)return null;const S=x.parser;s&&console.log("Loading finished "+c,y.guid);let T=0;if(x.parser.json.textures){let D=!1;for(const h of x.parser.json.textures){if(h!=null&&h.extensions){const w=h?.extensions[B];if(w!=null&&w.guid&&w.guid===y.guid){D=!0;break}}T++}if(D){let h=await S.getDependency("texture",T);return s&&console.log('change "'+r.name+'" \u2192 "'+h.name+'"',c,T,h,g),r instanceof C&&(h=this.copySettings(r,h)),h&&(h.guid=y.guid),m(h)}}if(T=0,x.parser.json.meshes){let D=!1;for(const h of x.parser.json.meshes){if(h!=null&&h.extensions){const w=h?.extensions[B];if(w!=null&&w.guid&&w.guid===y.guid){D=!0;break}}T++}if(D){const h=await S.getDependency("mesh",T),w=y;if(s&&console.log(`Loaded Mesh "${h.name}"`,c,T,h,g),h.isMesh===!0){const _=h.geometry;return v.assignLODInformation(i.url,_,a,e,void 0,w.density),m(_)}else{const _=new Array;for(let I=0;I<h.children.length;I++){const z=h.children[I];if(z instanceof W){const j=z.geometry;v.assignLODInformation(i.url,j,a,e,I,w.density),_.push(j)}}return m(_)}}}return m(null)});return this.previouslyLoaded.set(g,A),await A}else if(r instanceof C){s&&console.log("Load texture from uri: "+c);const g=await new ae().loadAsync(c);return g?(g.guid=l.guid,g.flipY=!1,g.needsUpdate=!0,g.colorSpace=r.colorSpace,s&&console.log(l,g)):L&&console.warn("failed loading",c),g}}else L&&console.warn(`Can not load LOD ${e}: no LOD info found for "${a}" ${r.name}`,r.type);return null}static assignLODInformation(r,e,t,n,o,s){if(!e)return;e.userData||(e.userData={});const i=new xe(r,t,n,o,s);e.userData.LODS=i,e.userData.LOD=n}static getAssignedLODInformation(r){var e;return((e=r?.userData)==null?void 0:e.LODS)||null}static copySettings(r,e){return this._copiedTextures.get(r)||(e=e.clone(),this._copiedTextures.set(r,e),e.offset=r.offset,e.repeat=r.repeat,e.colorSpace=r.colorSpace,e)}};let O=v;u(O,"registerTexture",(r,e,t,n)=>{L&&console.log("> Progressive: register texture",t,e.name,e.uuid,e,n),e.source&&(e.source[X]=n);const o=e.uuid;v.assignLODInformation(r,e,o,0,0,void 0),v.lodInfos.set(o,n),v.lowresCache.set(o,e)}),u(O,"registerMesh",(r,e,t,n,o,s)=>{var i;L&&console.log("> Progressive: register mesh",o,t.name,s,t.uuid,t);const a=t.geometry;a.userData||(a.userData={}),v.assignLODInformation(r,a,e,n,o,s.density),v.lodInfos.set(e,s);let l=v.lowresCache.get(e);l?l.push(t.geometry):l=[t.geometry],v.lowresCache.set(e,l);for(const d of V)(i=d.onRegisteredNewMesh)==null||i.call(d,t,s)}),u(O,"lodInfos",new Map),u(O,"previouslyLoaded",new Map),u(O,"lowresCache",new Map),u(O,"_copiedTextures",new Map);class xe{constructor(e,t,n,o,s){u(this,"url"),u(this,"key"),u(this,"level"),u(this,"index"),u(this,"density"),this.url=e,this.key=t,this.level=n,o!=null&&(this.index=o),s!=null&&(this.density=s)}}let ee=q("debugprogressive");const De=q("noprogressive"),P=class{constructor(r){u(this,"renderer"),u(this,"projectionScreenMatrix",new K),u(this,"cameraFrustrum",new le),u(this,"updateInterval",0),u(this,"pause",!1),u(this,"plugins",[]),u(this,"_originalRender"),u(this,"_sphere",new ue),u(this,"_tempBox",new Y),u(this,"tempMatrix",new K),u(this,"_tempWorldPosition",new E),u(this,"_tempBoxSize",new E),u(this,"_tempBox2Size",new E),this.renderer=r}enable(){if(this._originalRender)return;let r=0;this._originalRender=this.renderer.render;const e=this;let t=0;J(this.renderer),this.renderer.render=function(n,o){const s=t++,i=r++;e.onBeforeRender(n,o,i,s),e._originalRender.call(this,n,o),e.onAfterRender(n,o,i,s),r--}}disable(){this._originalRender&&(this.renderer.render=this._originalRender,this._originalRender=void 0)}onBeforeRender(r,e,t,n){}onAfterRender(r,e,t,n){if(De||this.pause||this.updateInterval>0&&n%this.updateInterval!=0)return;this.projectionScreenMatrix.multiplyMatrices(e.projectionMatrix,e.matrixWorldInverse),this.cameraFrustrum.setFromProjectionMatrix(this.projectionScreenMatrix,this.renderer.coordinateSystem);const o=1e5,s=this.renderer.renderLists.get(r,t).opaque;for(const i of s){const a=i.object;(a instanceof W||a.isMesh)&&this.updateLODs(r,e,a,o)}}static getObjectLODState(r){var e;return(e=r.userData)==null?void 0:e.LOD_state}updateLODs(r,e,t,n){var o,s;for(const d of this.plugins)(o=d.onBeforeUpdateLOD)==null||o.call(d,this.renderer,r,e,t);let i=t.userData.LOD_state;i||(i=new Le,t.userData.LOD_state=i);let a=this.calculateLodLevel(e,t,i,n);a=Math.round(a),a>=0&&this.loadProgressiveMeshes(t,a);let l=0;if(t.material){const d=t["DEBUG:LOD"];if(d!=null&&(l=d),Array.isArray(t.material))for(const c of t.material)this.loadProgressiveTextures(c,l);else this.loadProgressiveTextures(t.material,l)}for(const d of this.plugins)(s=d.onAfterUpdatedLOD)==null||s.call(d,this.renderer,r,e,t,a);i.lastLodLevel=a}loadProgressiveTextures(r,e){return r&&r.userData&&r.userData.LOD!==e?(r.userData.LOD=e,O.assignTextureLOD(r,e)):Promise.resolve(null)}loadProgressiveMeshes(r,e){if(!r)return Promise.resolve(null);if(r.userData||(r.userData={}),r.userData.LOD!==e){r.userData.LOD=e;const t=r.geometry;return O.assignMeshLOD(r,e).then(n=>(n&&r.userData.LOD==e&&t!=r.geometry,n))}return Promise.resolve(null)}calculateLodLevel(r,e,t,n){var o;if(!e)return-1;let s=10+1;if(r){if(ee&&e["DEBUG:LOD"]!=null)return e["DEBUG:LOD"];const i=O.getMeshLODInformation(e.geometry),a=i?.lods;if(!a||a.length<=0||!((o=this.cameraFrustrum)!=null&&o.intersectsObject(e)))return 99;const l=e.geometry.boundingBox;if(l&&r.isPerspectiveCamera){const d=r;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 f=r.getWorldPosition(this._tempWorldPosition);if(this._sphere.containsPoint(f))return 0}if(this._tempBox.copy(l),this._tempBox.applyMatrix4(e.matrixWorld),this._tempBox.applyMatrix4(this.projectionScreenMatrix),this.renderer.xr.enabled&&d.fov>70){const f=this._tempBox.min,p=this._tempBox.max;let b=f.x,x=f.y,S=p.x,T=p.y;const D=2,h=1.5,w=(f.x+p.x)*.5,_=(f.y+p.y)*.5;b=(b-w)*D+w,x=(x-_)*D+_,S=(S-w)*D+w,T=(T-_)*D+_;const I=b<0&&S>0?0:Math.min(Math.abs(f.x),Math.abs(p.x)),z=x<0&&T>0?0:Math.min(Math.abs(f.y),Math.abs(p.y)),j=Math.max(I,z);t.lastCentrality=(h-j)*(h-j)*(h-j)}else t.lastCentrality=1;const c=this._tempBox.getSize(this._tempBoxSize);c.multiplyScalar(.5),screen.availHeight>0&&c.multiplyScalar(this.renderer.domElement.clientHeight/screen.availHeight),c.x*=d.aspect;const g=r.matrixWorldInverse,M=new Y;M.copy(l),M.applyMatrix4(e.matrixWorld),M.applyMatrix4(g);const y=M.getSize(this._tempBox2Size),A=Math.max(y.x,y.y);if(Math.max(c.x,c.y)!=0&&A!=0&&(c.z=y.z/Math.max(y.x,y.y)*Math.max(c.x,c.y)),t.lastScreenCoverage=Math.max(c.x,c.y,c.z),t.lastScreenspaceVolume.copy(c),t.lastScreenCoverage*=t.lastCentrality,ee&&P.debugDrawLine){const f=this.tempMatrix.copy(this.projectionScreenMatrix);f.invert();const p=P.corner0,b=P.corner1,x=P.corner2,S=P.corner3;p.copy(this._tempBox.min),b.copy(this._tempBox.max),b.x=p.x,x.copy(this._tempBox.max),x.y=p.y,S.copy(this._tempBox.max);const T=(p.z+S.z)*.5;p.z=b.z=x.z=S.z=T,p.applyMatrix4(f),b.applyMatrix4(f),x.applyMatrix4(f),S.applyMatrix4(f),P.debugDrawLine(p,b,255),P.debugDrawLine(p,x,255),P.debugDrawLine(b,S,255),P.debugDrawLine(x,S,255)}let m=999;if(a&&t.lastScreenCoverage>0){for(let f=0;f<a.length;f++)if(a[f].density/t.lastScreenCoverage<n){m=f;break}}m<s&&(s=m)}}return s}};let k=P;u(k,"corner0",new E),u(k,"corner1",new E),u(k,"corner2",new E),u(k,"corner3",new E),u(k,"debugDrawLine");class Le{constructor(){u(this,"lastLodLevel",0),u(this,"lastScreenCoverage",0),u(this,"lastScreenspaceVolume",new E),u(this,"lastCentrality",0)}}const re=Symbol("NEEDLE_mesh_lod"),N=Symbol("NEEDLE_texture_lod");function te(){const r=document.querySelector("model-viewer");if(!r)return;let e=null;for(let t=r;t!=null;t=Object.getPrototypeOf(t)){const n=Object.getOwnPropertySymbols(t).find(o=>o.toString()=="Symbol(renderer)");!e&&n!=null&&(e=r[n].threeRenderer)}if(e){console.log("Adding Needle LODs to modelviewer");const t=new k(e);t.plugins.push(new ve(r)),t.enable()}}class ve{constructor(e){u(this,"modelviewer"),this.modelviewer=e}onBeforeUpdateLOD(e,t,n,o){this.tryParseMeshLOD(t,o),this.tryParseTextureLOD(t,o)}getUrl(){return this.modelviewer.getAttribute("src")}tryGetCurrentGLTF(e){return e._currentGLTF}tryParseTextureLOD(e,t){if(t[N]==!0)return;t[N]=!0;const n=this.tryGetCurrentGLTF(e),o=this.getUrl();if(!o){console.error("No url found in modelviewer");return}if(n&&t.material){let s=function(a){var l,d,c;if(a[N]==!0)return;a[N]=!0,a.userData&&(a.userData.LOD=-1);const g=Object.keys(a);for(let M=0;M<g.length;M++){const y=g[M],A=a[y];if(A?.isTexture===!0){const m=(d=(l=A.userData)==null?void 0:l.associations)==null?void 0:d.textures,f=n.parser.json.textures[m];if((c=f.extensions)!=null&&c[B]){const p=f.extensions[B];p&&o&&O.registerTexture(o,A,p.lods.length,p)}}}};const i=t.material;if(Array.isArray(i))for(const a of i)s(a);else s(i)}}tryParseMeshLOD(e,t){var n,o;if(t[re]==!0)return;t[re]=!0;const s=this.getUrl();if(!s){console.error("No url found in modelviewer");return}const i=(o=(n=t.userData)==null?void 0:n.gltfExtensions)==null?void 0:o[B];if(i&&s){const a=t.uuid;O.registerMesh(s,a,t,0,i.lods.length,i)}}}function Oe(r,e,t){J(e),Q(t),t.register(o=>new O(o,r));const n=new k(e);return n.enable(),n}document.addEventListener("DOMContentLoaded",()=>{te()});export{B as EXTENSION_NAME,k as LODsManager,O as NEEDLE_progressive,te as patchModelViewer,ye as registerPlugin,Oe as useNeedleGLTFProgressive};
@@ -0,0 +1,3 @@
1
+ "use strict";var re=Object.defineProperty;var se=(l,e,t)=>e in l?re(l,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):l[e]=t;var u=(l,e,t)=>(se(l,typeof e!="symbol"?e+"":e,t),t);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=require("three"),ie=require("three/examples/jsm/loaders/GLTFLoader.js"),oe=require("three/examples/jsm/libs/meshopt_decoder.module.js"),ne=require("three/examples/jsm/loaders/DRACOLoader.js"),ae=require("three/examples/jsm/loaders/KTX2Loader.js");let le="https://www.gstatic.com/draco/versioned/decoders/1.4.1/",ue="https://www.gstatic.com/basis-universal/versioned/2021-04-15-ba1c3e4/",z,$,I;function Z(l){z||(z=new ne.DRACOLoader,z.setDecoderPath(le),z.setDecoderConfig({type:"js"})),I||(I=new ae.KTX2Loader,I.setTranscoderPath(ue)),$||($=oe.MeshoptDecoder),l?I.detectSupport(l):console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures will probably fail")}function j(l){l.dracoLoader||l.setDRACOLoader(z),l.ktx2Loader||l.setKTX2Loader(I),l.meshoptDecoder||l.setMeshoptDecoder($)}function Y(l){let t=new URL(window.location.href).searchParams.get(l);return t==null||t==="0"||t==="false"?!1:t===""?!0:t}function ce(l,e){if(e===void 0||e.startsWith("./")||e.startsWith("http")||l===void 0)return e;const t=l.lastIndexOf("/");if(t>=0){const r=l.substring(0,t+1);for(;r.endsWith("/")&&e.startsWith("/");)e=e.substring(1);return r+e}return e}const X=new Array;function fe(l){X.push(l)}const C="NEEDLE_progressive",T=Y("debugprogressive"),V=Symbol("needle-progressive-texture"),U=new Map,K=new Set;if(T){let l=function(){e+=1,console.log("Toggle LOD level",e,U),U.forEach((i,o)=>{for(const s of i.keys){const n=o[s];if(n.isBufferGeometry===!0){const a=O.getMeshLODInformation(n),d=a?Math.min(e,a.lods.length):0;o["DEBUG:LOD"]=d,O.assignMeshLOD(o,d),a&&(t=Math.max(t,a.lods.length-1))}else if(o.isMaterial===!0){o["DEBUG:LOD"]=e,O.assignTextureLOD(o,e);break}}}),e>=t&&(e=-1)},e=-1,t=2,r=!1;window.addEventListener("keyup",i=>{i.key==="p"&&l(),i.key==="w"&&(r=!r,K&&K.forEach(o=>{o.name!="BackgroundCubeMaterial"&&"wireframe"in o&&(o.wireframe=r)}))})}function H(l,e,t){var i;if(!T)return;U.has(l)||U.set(l,{keys:[],sourceId:t});const r=U.get(l);((i=r==null?void 0:r.keys)==null?void 0:i.includes(e))==!1&&r.keys.push(e)}const S=class{constructor(e,t){u(this,"parser");u(this,"url");T&&console.log("Progressive extension registered for",t),this.parser=e,this.url=t}get name(){return C}static getMeshLODInformation(e){const t=this.getAssignedLODInformation(e);return t!=null&&t.key?this.lodInfos.get(t.key):null}static hasLODLevelAvailable(e,t){var o;if(e.isMaterial===!0){for(const s of Object.keys(e)){const n=e[s];if(n.isTexture&&this.hasLODLevelAvailable(n,t))return!0}return!1}else if(e.isGroup===!0){for(const s of e.children)if(s.isMesh===!0&&this.hasLODLevelAvailable(s,t))return!0}let r,i;if(e.isMesh?r=e.geometry:(e.isBufferGeometry||e.isTexture)&&(r=e),r&&(o=r==null?void 0:r.userData)!=null&&o.LODS){const s=r.userData.LODS;if(i=this.lodInfos.get(s.key),t===void 0)return i!=null;if(i)return Array.isArray(i.lods)?t<i.lods.length:t===0}return!1}static assignMeshLOD(e,t){var r;if(!e)return Promise.resolve(null);if(e instanceof h.Mesh||e.isMesh===!0){const i=e.geometry,o=this.getAssignedLODInformation(i);if(!o)return Promise.resolve(null);for(const s of X)(r=s.onBeforeGetLODMesh)==null||r.call(s,e,t);return e["LOD:requested level"]=t,S.getOrLoadLOD(i,t).then(s=>{if(e["LOD:requested level"]===t){if(delete e["LOD:requested level"],Array.isArray(s)){const n=o.index||0;s=s[n]}s&&i!=s&&s instanceof h.BufferGeometry&&(e.geometry=s,T&&H(e,"geometry",o.url))}return s}).catch(s=>(console.error("Error loading mesh LOD",e,s),null))}else T&&console.error("Invalid call to assignMeshLOD: Request mesh LOD but the object is not a mesh",e);return Promise.resolve(null)}static assignTextureLOD(e,t=0){if(!e)return Promise.resolve(null);if(e instanceof h.Material||e.isMaterial===!0){const r=e,i=[],o=new Array;if(T&&K.add(r),r instanceof h.RawShaderMaterial)for(const s of Object.keys(r.uniforms)){const n=r.uniforms[s].value;if((n==null?void 0:n.isTexture)===!0){const a=this.assignTextureLODForSlot(n,t,r,s);i.push(a),o.push(s)}}else for(const s of Object.keys(r)){const n=r[s];if((n==null?void 0:n.isTexture)===!0){const a=this.assignTextureLODForSlot(n,t,r,s);i.push(a),o.push(s)}}return Promise.all(i).then(s=>{const n=new Array;for(let a=0;a<s.length;a++){const d=s[a],f=o[a];d&&d.isTexture===!0?n.push({material:r,slot:f,texture:d,level:t}):n.push({material:r,slot:f,texture:null,level:t})}return n})}if(e instanceof h.Texture||e.isTexture===!0){const r=e;return this.assignTextureLODForSlot(r,t,null,null)}return Promise.resolve(null)}static assignTextureLODForSlot(e,t,r,i){return(e==null?void 0:e.isTexture)!==!0?Promise.resolve(null):S.getOrLoadLOD(e,t).then(o=>{if(Array.isArray(o))return null;if((o==null?void 0:o.isTexture)===!0){if(o!=e&&(r&&i&&(r[i]=o),T&&i&&r)){const s=this.getAssignedLODInformation(e);s&&H(r,i,s.url)}return o}else T=="verbose"&&console.warn("No LOD found for",e,t);return null}).catch(o=>(console.error("Error loading LOD",e,o),null))}afterRoot(e){var t,r;return T&&console.log("AFTER",this.url,e),(t=this.parser.json.textures)==null||t.forEach((i,o)=>{if(i!=null&&i.extensions){const s=i==null?void 0:i.extensions[C];if(s){let n=!1;for(const a of this.parser.associations.keys())a.isTexture===!0&&this.parser.associations.get(a).textures===o&&(n=!0,S.registerTexture(this.url,a,o,s));n||this.parser.getDependency("texture",o).then(a=>{a&&S.registerTexture(this.url,a,o,s)})}}}),(r=this.parser.json.meshes)==null||r.forEach((i,o)=>{if(i!=null&&i.extensions){const s=i==null?void 0:i.extensions[C];if(s&&s.lods){for(const n of this.parser.associations.keys())if(n.isMesh){const a=this.parser.associations.get(n);a.meshes===o&&S.registerMesh(this.url,n.uuid,n,s.lods.length,a.primitives,s)}}}}),null}static async getOrLoadLOD(e,t){var n,a,d;const r=T=="verbose",i=e.userData.LODS;if(!i)return null;const o=i==null?void 0:i.key;let s;if(e.isTexture===!0){const f=e;f.source&&f.source[V]&&(s=f.source[V])}if(s||(s=S.lodInfos.get(o)),s){if(t>0){let g=!1;const v=Array.isArray(s.lods);if(v&&t>=s.lods.length?g=!0:v||(g=!0),g)return this.lowresCache.get(o)}const f=Array.isArray(s.lods)?s.lods[t].path:s.lods;if(!f)return T&&!s["missing:uri"]&&(s["missing:uri"]=!0,console.warn("Missing uri for progressive asset for LOD "+t,s)),null;const D=ce(i.url,f);if(D.endsWith(".glb")||D.endsWith(".gltf")){if(!s.guid)return console.warn("missing pointer for glb/gltf texture",s),null;const g=D+"_"+s.guid,v=this.previouslyLoaded.get(g);if(v!==void 0){r&&console.log(`LOD ${t} was already loading/loaded: ${g}`);let m=await v.catch(F=>(console.error(`Error loading LOD ${t} from ${D}
2
+ `,F),null)),R=!1;if(m==null||(m instanceof h.Texture&&e instanceof h.Texture?(n=m.image)!=null&&n.data||(a=m.source)!=null&&a.data?m=this.copySettings(e,m):(R=!0,this.previouslyLoaded.delete(g)):m instanceof h.BufferGeometry&&e instanceof h.BufferGeometry&&((d=m.attributes.position)!=null&&d.array||(R=!0,this.previouslyLoaded.delete(g)))),!R)return m}const x=s,b=new Promise(async(m,R)=>{const F=new ie.GLTFLoader;j(F),T&&(await new Promise(y=>setTimeout(y,1e3)),r&&console.warn("Start loading (delayed) "+D,x.guid));let L=D;if(x&&Array.isArray(x.lods)){const y=x.lods[t];y.hash&&(L+="?v="+y.hash)}const p=await F.loadAsync(L).catch(y=>(console.error(`Error loading LOD ${t} from ${D}
3
+ `,y),null));if(!p)return null;const P=p.parser;r&&console.log("Loading finished "+D,x.guid);let w=0;if(p.parser.json.textures){let y=!1;for(const c of p.parser.json.textures){if(c!=null&&c.extensions){const M=c==null?void 0:c.extensions[C];if(M!=null&&M.guid&&M.guid===x.guid){y=!0;break}}w++}if(y){let c=await P.getDependency("texture",w);return r&&console.log('change "'+e.name+'" → "'+c.name+'"',D,w,c,g),e instanceof h.Texture&&(c=this.copySettings(e,c)),c&&(c.guid=x.guid),m(c)}}if(w=0,p.parser.json.meshes){let y=!1;for(const c of p.parser.json.meshes){if(c!=null&&c.extensions){const M=c==null?void 0:c.extensions[C];if(M!=null&&M.guid&&M.guid===x.guid){y=!0;break}}w++}if(y){const c=await P.getDependency("mesh",w),M=x;if(r&&console.log(`Loaded Mesh "${c.name}"`,D,w,c,g),c.isMesh===!0){const B=c.geometry;return S.assignLODInformation(i.url,B,o,t,void 0,M.density),m(B)}else{const B=new Array;for(let k=0;k<c.children.length;k++){const E=c.children[k];if(E instanceof h.Mesh){const N=E.geometry;S.assignLODInformation(i.url,N,o,t,k,M.density),B.push(N)}}return m(B)}}}return m(null)});return this.previouslyLoaded.set(g,b),await b}else if(e instanceof h.Texture){r&&console.log("Load texture from uri: "+D);const v=await new h.TextureLoader().loadAsync(D);return v?(v.guid=s.guid,v.flipY=!1,v.needsUpdate=!0,v.colorSpace=e.colorSpace,r&&console.log(s,v)):T&&console.warn("failed loading",D),v}}else T&&console.warn(`Can not load LOD ${t}: no LOD info found for "${o}" ${e.name}`,e.type);return null}static assignLODInformation(e,t,r,i,o,s){if(!t)return;t.userData||(t.userData={});const n=new de(e,r,i,o,s);t.userData.LODS=n,t.userData.LOD=i}static getAssignedLODInformation(e){var t;return((t=e==null?void 0:e.userData)==null?void 0:t.LODS)||null}static copySettings(e,t){const r=this._copiedTextures.get(e);return r||(t=t.clone(),this._copiedTextures.set(e,t),t.offset=e.offset,t.repeat=e.repeat,t.colorSpace=e.colorSpace,t)}};let O=S;u(O,"registerTexture",(e,t,r,i)=>{T&&console.log("> Progressive: register texture",r,t.name,t.uuid,t,i),t.source&&(t.source[V]=i);const o=t.uuid;S.assignLODInformation(e,t,o,0,0,void 0),S.lodInfos.set(o,i),S.lowresCache.set(o,t)}),u(O,"registerMesh",(e,t,r,i,o,s)=>{var d;T&&console.log("> Progressive: register mesh",o,r.name,s,r.uuid,r);const n=r.geometry;n.userData||(n.userData={}),S.assignLODInformation(e,n,t,i,o,s.density),S.lodInfos.set(t,s);let a=S.lowresCache.get(t);a?a.push(r.geometry):a=[r.geometry],S.lowresCache.set(t,a);for(const f of X)(d=f.onRegisteredNewMesh)==null||d.call(f,r,s)}),u(O,"lodInfos",new Map),u(O,"previouslyLoaded",new Map),u(O,"lowresCache",new Map),u(O,"_copiedTextures",new Map);class de{constructor(e,t,r,i,o){u(this,"url");u(this,"key");u(this,"level");u(this,"index");u(this,"density");this.url=e,this.key=t,this.level=r,i!=null&&(this.index=i),o!=null&&(this.density=o)}}let J=Y("debugprogressive");const ge=Y("noprogressive"),A=class{constructor(e){u(this,"renderer");u(this,"projectionScreenMatrix",new h.Matrix4);u(this,"cameraFrustrum",new h.Frustum);u(this,"updateInterval",0);u(this,"pause",!1);u(this,"plugins",[]);u(this,"_originalRender");u(this,"_sphere",new h.Sphere);u(this,"_tempBox",new h.Box3);u(this,"tempMatrix",new h.Matrix4);u(this,"_tempWorldPosition",new h.Vector3);u(this,"_tempBoxSize",new h.Vector3);u(this,"_tempBox2Size",new h.Vector3);this.renderer=e}enable(){if(this._originalRender)return;let e=0;this._originalRender=this.renderer.render;const t=this;let r=0;Z(this.renderer),this.renderer.render=function(i,o){const s=r++,n=e++;t.onBeforeRender(i,o,n,s),t._originalRender.call(this,i,o),t.onAfterRender(i,o,n,s),e--}}disable(){this._originalRender&&(this.renderer.render=this._originalRender,this._originalRender=void 0)}onBeforeRender(e,t,r,i){}onAfterRender(e,t,r,i){if(ge||this.pause||this.updateInterval>0&&i%this.updateInterval!=0)return;this.projectionScreenMatrix.multiplyMatrices(t.projectionMatrix,t.matrixWorldInverse),this.cameraFrustrum.setFromProjectionMatrix(this.projectionScreenMatrix,this.renderer.coordinateSystem);const o=1e5,n=this.renderer.renderLists.get(e,r).opaque;for(const a of n){const d=a.object;(d instanceof h.Mesh||d.isMesh)&&this.updateLODs(e,t,d,o)}}static getObjectLODState(e){var t;return(t=e.userData)==null?void 0:t.LOD_state}updateLODs(e,t,r,i){var a,d;for(const f of this.plugins)(a=f.onBeforeUpdateLOD)==null||a.call(f,this.renderer,e,t,r);let o=r.userData.LOD_state;o||(o=new he,r.userData.LOD_state=o);let s=this.calculateLodLevel(t,r,o,i);s=Math.round(s),s>=0&&this.loadProgressiveMeshes(r,s);let n=0;if(r.material){const f=r["DEBUG:LOD"];if(f!=null&&(n=f),Array.isArray(r.material))for(const D of r.material)this.loadProgressiveTextures(D,n);else this.loadProgressiveTextures(r.material,n)}for(const f of this.plugins)(d=f.onAfterUpdatedLOD)==null||d.call(f,this.renderer,e,t,r,s);o.lastLodLevel=s}loadProgressiveTextures(e,t){return e&&e.userData&&e.userData.LOD!==t?(e.userData.LOD=t,O.assignTextureLOD(e,t)):Promise.resolve(null)}loadProgressiveMeshes(e,t){if(!e)return Promise.resolve(null);if(e.userData||(e.userData={}),e.userData.LOD!==t){e.userData.LOD=t;const r=e.geometry;return O.assignMeshLOD(e,t).then(i=>(i&&e.userData.LOD==t&&r!=e.geometry,i))}return Promise.resolve(null)}calculateLodLevel(e,t,r,i){var n;if(!t)return-1;let s=10+1;if(e){if(J&&t["DEBUG:LOD"]!=null)return t["DEBUG:LOD"];const a=O.getMeshLODInformation(t.geometry),d=a==null?void 0:a.lods;if(!d||d.length<=0||!((n=this.cameraFrustrum)!=null&&n.intersectsObject(t)))return 99;const f=t.geometry.boundingBox;if(f&&e.isPerspectiveCamera){const D=e;if(t.geometry.attributes.color&&t.geometry.attributes.color.count<100&&t.geometry.boundingSphere){this._sphere.copy(t.geometry.boundingSphere),this._sphere.applyMatrix4(t.matrixWorld);const L=e.getWorldPosition(this._tempWorldPosition);if(this._sphere.containsPoint(L))return 0}if(this._tempBox.copy(f),this._tempBox.applyMatrix4(t.matrixWorld),this._tempBox.applyMatrix4(this.projectionScreenMatrix),this.renderer.xr.enabled&&D.fov>70){const L=this._tempBox.min,p=this._tempBox.max;let P=L.x,w=L.y,y=p.x,c=p.y;const M=2,B=1.5,k=(L.x+p.x)*.5,E=(L.y+p.y)*.5;P=(P-k)*M+k,w=(w-E)*M+E,y=(y-k)*M+k,c=(c-E)*M+E;const N=P<0&&y>0?0:Math.min(Math.abs(L.x),Math.abs(p.x)),te=w<0&&c>0?0:Math.min(Math.abs(L.y),Math.abs(p.y)),q=Math.max(N,te);r.lastCentrality=(B-q)*(B-q)*(B-q)}else r.lastCentrality=1;const g=this._tempBox.getSize(this._tempBoxSize);g.multiplyScalar(.5),screen.availHeight>0&&g.multiplyScalar(this.renderer.domElement.clientHeight/screen.availHeight),g.x*=D.aspect;const v=e.matrixWorldInverse,x=new h.Box3;x.copy(f),x.applyMatrix4(t.matrixWorld),x.applyMatrix4(v);const b=x.getSize(this._tempBox2Size),G=Math.max(b.x,b.y);if(Math.max(g.x,g.y)!=0&&G!=0&&(g.z=b.z/Math.max(b.x,b.y)*Math.max(g.x,g.y)),r.lastScreenCoverage=Math.max(g.x,g.y,g.z),r.lastScreenspaceVolume.copy(g),r.lastScreenCoverage*=r.lastCentrality,J&&A.debugDrawLine){const L=this.tempMatrix.copy(this.projectionScreenMatrix);L.invert();const p=A.corner0,P=A.corner1,w=A.corner2,y=A.corner3;p.copy(this._tempBox.min),P.copy(this._tempBox.max),P.x=p.x,w.copy(this._tempBox.max),w.y=p.y,y.copy(this._tempBox.max);const c=(p.z+y.z)*.5;p.z=P.z=w.z=y.z=c,p.applyMatrix4(L),P.applyMatrix4(L),w.applyMatrix4(L),y.applyMatrix4(L),A.debugDrawLine(p,P,255),A.debugDrawLine(p,w,255),A.debugDrawLine(P,y,255),A.debugDrawLine(w,y,255)}let R=999;if(d&&r.lastScreenCoverage>0){for(let L=0;L<d.length;L++)if(d[L].density/r.lastScreenCoverage<i){R=L;break}}R<s&&(s=R)}}return s}};let _=A;u(_,"corner0",new h.Vector3),u(_,"corner1",new h.Vector3),u(_,"corner2",new h.Vector3),u(_,"corner3",new h.Vector3),u(_,"debugDrawLine");class he{constructor(){u(this,"lastLodLevel",0);u(this,"lastScreenCoverage",0);u(this,"lastScreenspaceVolume",new h.Vector3);u(this,"lastCentrality",0)}}const Q=Symbol("NEEDLE_mesh_lod"),W=Symbol("NEEDLE_texture_lod");function ee(){const l=document.querySelector("model-viewer");if(!l)return;let e=null;for(let t=l;t!=null;t=Object.getPrototypeOf(t)){const i=Object.getOwnPropertySymbols(t).find(o=>o.toString()=="Symbol(renderer)");!e&&i!=null&&(e=l[i].threeRenderer)}if(e){console.log("Adding Needle LODs to modelviewer");const t=new _(e);t.plugins.push(new pe(l)),t.enable()}}class pe{constructor(e){u(this,"modelviewer");this.modelviewer=e}onBeforeUpdateLOD(e,t,r,i){this.tryParseMeshLOD(t,i),this.tryParseTextureLOD(t,i)}getUrl(){return this.modelviewer.getAttribute("src")}tryGetCurrentGLTF(e){return e._currentGLTF}tryParseTextureLOD(e,t){if(t[W]==!0)return;t[W]=!0;const r=this.tryGetCurrentGLTF(e),i=this.getUrl();if(!i){console.error("No url found in modelviewer");return}if(r&&t.material){let o=function(n){var d,f,D;if(n[W]==!0)return;n[W]=!0,n.userData&&(n.userData.LOD=-1);const a=Object.keys(n);for(let g=0;g<a.length;g++){const v=a[g],x=n[v];if((x==null?void 0:x.isTexture)===!0){const b=(f=(d=x.userData)==null?void 0:d.associations)==null?void 0:f.textures,G=r.parser.json.textures[b];if((D=G.extensions)!=null&&D[C]){const m=G.extensions[C];m&&i&&O.registerTexture(i,x,m.lods.length,m)}}}};const s=t.material;if(Array.isArray(s))for(const n of s)o(n);else o(s)}}tryParseMeshLOD(e,t){var o,s;if(t[Q]==!0)return;t[Q]=!0;const r=this.getUrl();if(!r){console.error("No url found in modelviewer");return}const i=(s=(o=t.userData)==null?void 0:o.gltfExtensions)==null?void 0:s[C];if(i&&r){const n=t.uuid;O.registerMesh(r,n,t,0,i.lods.length,i)}}}function ye(l,e,t){Z(e),j(t),t.register(i=>new O(i,l));const r=new _(e);return r.enable(),r}document.addEventListener("DOMContentLoaded",()=>{ee()});exports.EXTENSION_NAME=C;exports.LODsManager=_;exports.NEEDLE_progressive=O;exports.patchModelViewer=ee;exports.registerPlugin=fe;exports.useNeedleGLTFProgressive=ye;
package/package.json CHANGED
@@ -1,18 +1,24 @@
1
1
  {
2
2
  "name": "@needle-tools/gltf-progressive",
3
- "version": "1.0.0-alpha",
4
- "description": "",
5
- "type": "module",
6
- "main": "src/index.ts",
3
+ "version": "1.0.0-alpha.2",
4
+ "description": "three.js support for loading glTF or GLB files that contain progressive loading data",
7
5
  "homepage": "https://needle.tools",
8
6
  "author": {
9
7
  "name": "Needle",
10
8
  "email": "hi@needle.tools",
11
9
  "url": "https://needle.tools/"
12
10
  },
13
- "scripts": {
14
- "build:dist": "vite build"
15
- },
11
+ "keywords": [
12
+ "three.js",
13
+ "gltf",
14
+ "glb",
15
+ "progressive",
16
+ "loading",
17
+ "needle",
18
+ "engine",
19
+ "webgl",
20
+ "optimization"
21
+ ],
16
22
  "peerDependencies": {
17
23
  "three": ">= 0.160.0"
18
24
  },
@@ -21,8 +27,5 @@
21
27
  "three": ">= 0.160.0",
22
28
  "vite": "<= 4.3.9"
23
29
  },
24
- "publishConfig": {
25
- "access": "public",
26
- "registry": "https://registry.npmjs.org"
27
- }
30
+ "types": "./types/index.d.ts"
28
31
  }
@@ -0,0 +1,109 @@
1
+ import { BufferGeometry, Material, Mesh, Texture } from "three";
2
+ import { type GLTF, type GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
3
+ export declare const EXTENSION_NAME = "NEEDLE_progressive";
4
+ declare type NEEDLE_progressive_model_LOD = {
5
+ path: string;
6
+ hash?: string;
7
+ };
8
+ /** This is the data structure we have in the NEEDLE_progressive extension */
9
+ declare type NEEDLE_progressive_model = {
10
+ guid: string;
11
+ lods: Array<NEEDLE_progressive_model_LOD>;
12
+ };
13
+ export declare type NEEDLE_progressive_texture_model = NEEDLE_progressive_model & {};
14
+ export declare type NEEDLE_progressive_mesh_model = NEEDLE_progressive_model & {
15
+ density: number;
16
+ lods: Array<NEEDLE_progressive_model_LOD & {
17
+ density: number;
18
+ indexCount: number;
19
+ vertexCount: number;
20
+ }>;
21
+ };
22
+ /**
23
+ * This is the result of a progressive texture loading event for a material's texture slot in {@link NEEDLE_progressive.assignTextureLOD}
24
+ * @internal
25
+ */
26
+ export declare type ProgressiveMaterialTextureLoadingResult = {
27
+ /** the material the progressive texture was loaded for */
28
+ material: Material;
29
+ /** the slot in the material where the texture was loaded */
30
+ slot: string;
31
+ /** the texture that was loaded (if any) */
32
+ texture: Texture | null;
33
+ /** the level of detail that was loaded */
34
+ level: number;
35
+ };
36
+ /**
37
+ * The NEEDLE_progressive extension for the GLTFLoader is responsible for loading progressive LODs for meshes and textures.
38
+ * This extension can be used to load different resolutions of a mesh or texture at runtime (e.g. for LODs or progressive textures).
39
+ * @example
40
+ * ```javascript
41
+ * const loader = new GLTFLoader();
42
+ * loader.register(new NEEDLE_progressive());
43
+ * loader.load("model.glb", (gltf) => {
44
+ * const mesh = gltf.scene.children[0] as Mesh;
45
+ * NEEDLE_progressive.assignMeshLOD(context, sourceId, mesh, 1).then(mesh => {
46
+ * console.log("Mesh with LOD level 1 loaded", mesh);
47
+ * });
48
+ * });
49
+ * ```
50
+ */
51
+ export declare class NEEDLE_progressive implements GLTFLoaderPlugin {
52
+ /** The name of the extension */
53
+ get name(): string;
54
+ static getMeshLODInformation(geo: BufferGeometry): NEEDLE_progressive_mesh_model | null;
55
+ /** Check if a LOD level is available for a mesh or a texture
56
+ * @param obj the mesh or texture to check
57
+ * @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
58
+ * @returns true if the LOD level is available (or if any LOD level is available if level is undefined)
59
+ */
60
+ static hasLODLevelAvailable(obj: Mesh | BufferGeometry | Texture | Material, level?: number): boolean;
61
+ /** Load a different resolution of a mesh (if available)
62
+ * @param context the context
63
+ * @param source the sourceid of the file from which the mesh is loaded (this is usually the component's sourceId)
64
+ * @param mesh the mesh to load the LOD for
65
+ * @param level the level of detail to load (0 is the highest resolution)
66
+ * @returns a promise that resolves to the mesh with the requested LOD level
67
+ * @example
68
+ * ```javascript
69
+ * const mesh = this.gameObject as Mesh;
70
+ * NEEDLE_progressive.assignMeshLOD(context, sourceId, mesh, 1).then(mesh => {
71
+ * console.log("Mesh with LOD level 1 loaded", mesh);
72
+ * });
73
+ * ```
74
+ */
75
+ static assignMeshLOD(mesh: Mesh, level: number): Promise<BufferGeometry | null>;
76
+ /** Load a different resolution of a texture (if available)
77
+ * @param context the context
78
+ * @param source the sourceid of the file from which the texture is loaded (this is usually the component's sourceId)
79
+ * @param materialOrTexture the material or texture to load the LOD for (if passing in a material all textures in the material will be loaded)
80
+ * @param level the level of detail to load (0 is the highest resolution) - currently only 0 is supported
81
+ * @returns a promise that resolves to the material or texture with the requested LOD level
82
+ */
83
+ static assignTextureLOD(materialOrTexture: Material | Texture, level?: number): Promise<Array<ProgressiveMaterialTextureLoadingResult> | Texture | null>;
84
+ private static assignTextureLODForSlot;
85
+ private readonly parser;
86
+ private readonly url;
87
+ constructor(parser: GLTFParser, url: string);
88
+ afterRoot(gltf: GLTF): null;
89
+ /**
90
+ * Register a texture with LOD information
91
+ */
92
+ static registerTexture: (url: string, tex: Texture, index: number, ext: NEEDLE_progressive_texture_model) => void;
93
+ /**
94
+ * Register a mesh with LOD information
95
+ */
96
+ static registerMesh: (url: string, key: string, mesh: Mesh, level: number, index: number | undefined, ext: NEEDLE_progressive_mesh_model) => void;
97
+ /** A map of key = asset uuid and value = LOD information */
98
+ private static readonly lodInfos;
99
+ /** cache of already loaded mesh lods */
100
+ private static readonly previouslyLoaded;
101
+ /** this contains the geometry/textures that were originally loaded */
102
+ private static readonly lowresCache;
103
+ private static getOrLoadLOD;
104
+ private static assignLODInformation;
105
+ private static getAssignedLODInformation;
106
+ private static readonly _copiedTextures;
107
+ private static copySettings;
108
+ }
109
+ export {};
@@ -0,0 +1,10 @@
1
+ export * from "./extension.js";
2
+ export * from "./plugins/index.js";
3
+ export { LODsManager } from "./lods_manager.js";
4
+ import { WebGLRenderer } from "three";
5
+ import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
6
+ /**
7
+ * Use this function to load a gltf model progressively.
8
+ */
9
+ export declare function useNeedleGLTFProgressive(url: string, renderer: WebGLRenderer, loader: GLTFLoader): LODsManager;
10
+ import { LODsManager } from "./lods_manager.js";
@@ -0,0 +1,4 @@
1
+ import { WebGLRenderer } from 'three';
2
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
3
+ export declare function createLoaders(renderer: WebGLRenderer): void;
4
+ export declare function addDracoAndKTX2Loaders(loader: GLTFLoader): void;
@@ -0,0 +1,51 @@
1
+ import { Frustum, Matrix4, Object3D, Vector3, WebGLRenderer } from "three";
2
+ import { NEEDLE_progressive_plugin } from "./plugins/plugin.js";
3
+ export declare class LODsManager {
4
+ readonly renderer: WebGLRenderer;
5
+ readonly projectionScreenMatrix: Matrix4;
6
+ readonly cameraFrustrum: Frustum;
7
+ updateInterval: number;
8
+ pause: boolean;
9
+ readonly plugins: NEEDLE_progressive_plugin[];
10
+ constructor(renderer: WebGLRenderer);
11
+ private _originalRender?;
12
+ enable(): void;
13
+ disable(): void;
14
+ private onBeforeRender;
15
+ private onAfterRender;
16
+ static getObjectLODState(object: Object3D): LOD_state | undefined;
17
+ /** Update the LOD levels for the renderer. */
18
+ private updateLODs;
19
+ /** Load progressive textures for the given material
20
+ * @param material the material to load the textures for
21
+ * @param level the LOD level to load. Level 0 is the best quality, higher levels are lower quality
22
+ * @returns Promise with true if the LOD was loaded, false if not
23
+ */
24
+ private loadProgressiveTextures;
25
+ /** Load progressive meshes for the given mesh
26
+ * @param mesh the mesh to load the LOD for
27
+ * @param index the index of the mesh if it's part of a group
28
+ * @param level the LOD level to load. Level 0 is the best quality, higher levels are lower quality
29
+ * @returns Promise with true if the LOD was loaded, false if not
30
+ */
31
+ private loadProgressiveMeshes;
32
+ private readonly _sphere;
33
+ private readonly _tempBox;
34
+ private readonly tempMatrix;
35
+ private readonly _tempWorldPosition;
36
+ private readonly _tempBoxSize;
37
+ private readonly _tempBox2Size;
38
+ private static corner0;
39
+ private static corner1;
40
+ private static corner2;
41
+ private static corner3;
42
+ static debugDrawLine: (a: Vector3, b: Vector3, color: number) => void | null;
43
+ private calculateLodLevel;
44
+ }
45
+ declare class LOD_state {
46
+ lastLodLevel: number;
47
+ lastScreenCoverage: number;
48
+ readonly lastScreenspaceVolume: Vector3;
49
+ lastCentrality: number;
50
+ }
51
+ export {};
@@ -0,0 +1,2 @@
1
+ export { patchModelViewer } from "./modelviewer.js";
2
+ export { registerPlugin, type NEEDLE_progressive_plugin } from "./plugin.js";
@@ -0,0 +1,15 @@
1
+ import { Scene, Camera, Object3D, Object3DEventMap, WebGLRenderer } from "three";
2
+ import { NEEDLE_progressive_plugin } from "./plugin.js";
3
+ export declare function patchModelViewer(): void;
4
+ /**
5
+ * LODs manager plugin that registers LOD data to the NEEDLE progressive system
6
+ */
7
+ export declare class RegisterModelviewerDataPlugin implements NEEDLE_progressive_plugin {
8
+ readonly modelviewer: HTMLElement;
9
+ constructor(modelviewer: HTMLElement);
10
+ onBeforeUpdateLOD(_renderer: WebGLRenderer, scene: Scene, _camera: Camera, object: Object3D<Object3DEventMap>): void;
11
+ private getUrl;
12
+ private tryGetCurrentGLTF;
13
+ private tryParseTextureLOD;
14
+ private tryParseMeshLOD;
15
+ }
@@ -0,0 +1,14 @@
1
+ import { WebGLRenderer, Scene, Camera, Mesh } from 'three';
2
+ import { NEEDLE_progressive_mesh_model } from '../extension.js';
3
+ export interface NEEDLE_progressive_plugin {
4
+ /** Called before the LOD level will be requested/updated for a object */
5
+ onBeforeUpdateLOD?(renderer: WebGLRenderer, scene: Scene, camera: Camera, object: Mesh): void;
6
+ /** Called after the LOD level has been requested */
7
+ onAfterUpdatedLOD?(renderer: WebGLRenderer, scene: Scene, camera: Camera, object: Mesh, level: number): void;
8
+ /** Called when a new mesh is registered */
9
+ onRegisteredNewMesh?(mesh: Mesh, ext: NEEDLE_progressive_mesh_model): void;
10
+ /** Called before the LOD mesh is fetched */
11
+ onBeforeGetLODMesh?(mesh: Mesh, level: number): void;
12
+ }
13
+ export declare const plugins: NEEDLE_progressive_plugin[];
14
+ export declare function registerPlugin(plugin: NEEDLE_progressive_plugin): void;
@@ -0,0 +1,2 @@
1
+ export declare function getParam(name: string): boolean | string;
2
+ export declare function resolveUrl(source: string | undefined, uri: string): string;
@@ -1,3 +0,0 @@
1
- var oe=Object.defineProperty,ne=(r,e,t)=>e in r?oe(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t,c=(r,e,t)=>(ne(r,typeof e!="symbol"?e+"":e,t),t);import{Material as Y,Texture as T,Group as ie,Mesh as I,BufferGeometry as W,RawShaderMaterial as ae,TextureLoader as le,Matrix4 as J,Frustum as ue,Sphere as ce,Box3 as Q,Vector3 as j,PerspectiveCamera as de}from"three";import{GLTFLoader as he}from"three/examples/jsm/loaders/GLTFLoader.js";import{MeshoptDecoder as fe}from"three/examples/jsm/libs/meshopt_decoder.module.js";import{DRACOLoader as ge}from"three/examples/jsm/loaders/DRACOLoader.js";import{KTX2Loader as pe}from"three/examples/jsm/loaders/KTX2Loader.js";let N="https://www.gstatic.com/draco/versioned/decoders/1.4.1/",Z="https://www.gstatic.com/basis-universal/versioned/2021-04-15-ba1c3e4/";const me=fetch(N+"draco_decoder.js",{method:"head"}).catch(r=>{console.log("Use local loaders"),N="./include/draco/",Z="./include/ktx2/"});let R,$,F;async function ye(r){await me,R||(R=new ge,R.setDecoderPath(N),R.setDecoderConfig({type:"js"})),F||(F=new pe,F.setTranscoderPath(Z)),$||($=fe),r?F.detectSupport(r):console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures will probably fail")}async function xe(r){r.setDRACOLoader(R),r.setKTX2Loader(F),r.setMeshoptDecoder($)}function q(r){let e=new URL(window.location.href).searchParams.get(r);return e==null||e==="0"||e==="false"?!1:e===""?!0:e}function ve(r,e){if(e===void 0||e.startsWith("./")||e.startsWith("http")||r===void 0)return e;const t=r.lastIndexOf("/");if(t>=0){const o=r.substring(0,t+1);for(;o.endsWith("/")&&e.startsWith("/");)e=e.substring(1);return o+e}return e}const K=new Array;function De(r){K.push(r)}const A="NEEDLE_progressive",L=q("debugprogressive"),V=Symbol("needle-progressive-texture"),G=new Map,X=new Set;if(L){let r=function(){e+=1,console.log("Toggle LOD level",e,G),G.forEach((n,s)=>{for(const i of n.keys){const a=s[i];if(a.isBufferGeometry===!0){const l=w.getMeshLODInformation(a),d=l?Math.min(e,l.lods.length):0;s["DEBUG:LOD"]=d,w.assignMeshLOD(s,d),l&&(t=Math.max(t,l.lods.length-1))}else if(s.isMaterial===!0){s["DEBUG:LOD"]=e,w.assignTextureLOD(s,e);break}}}),e>=t&&(e=-1)},e=-1,t=2,o=!1;window.addEventListener("keyup",n=>{n.key==="p"&&r(),n.key==="w"&&(o=!o,X&&X.forEach(s=>{"wireframe"in s&&(s.wireframe=o)}))})}function ee(r,e,t){var o;if(!L)return;G.has(r)||G.set(r,{keys:[],sourceId:t});const n=G.get(r);((o=n?.keys)==null?void 0:o.includes(e))==!1&&n.keys.push(e)}const O=class{constructor(r,e){c(this,"parser"),c(this,"url"),this.parser=r,this.url=e}get name(){return A}static getMeshLODInformation(r){const e=this.getAssignedLODInformation(r);return e!=null&&e.key?this.lodInfos.get(e.key):null}static hasLODLevelAvailable(r,e){var t;if(r instanceof Y){for(const s of Object.keys(r)){const i=r[s];if(i instanceof T&&this.hasLODLevelAvailable(i,e))return!0}return!1}else if(r instanceof ie){for(const s of r.children)if(s instanceof I&&this.hasLODLevelAvailable(s,e))return!0}let o,n;if(r instanceof I?o=r.geometry:r instanceof T&&(o=r),o&&(t=o?.userData)!=null&&t.LODS){const s=o.userData.LODS;if(n=this.lodInfos.get(s.key),e===void 0)return n!=null;if(n)return Array.isArray(n.lods)?e<n.lods.length:e===0}return!1}static assignMeshLOD(r,e){var t;if(!r)return Promise.resolve(null);if(r instanceof I||r.isMesh===!0){const o=r.geometry,n=this.getAssignedLODInformation(o);if(!n)return Promise.resolve(null);for(const s of K)(t=s.onBeforeGetLODMesh)==null||t.call(s,r,e);return r["LOD:requested level"]=e,O.getOrLoadLOD(o,e).then(s=>{if(r["LOD:requested level"]===e){if(delete r["LOD:requested level"],Array.isArray(s)){const i=n.index||0;s=s[i]}s&&o!=s&&s instanceof W&&(r.geometry=s,L&&ee(r,"geometry",n.url))}return s}).catch(s=>(console.error("Error loading mesh LOD",r,s),null))}else L&&console.error("Invalid call to assignMeshLOD: Request mesh LOD but the object is not a mesh",r);return Promise.resolve(null)}static assignTextureLOD(r,e=0){if(!r)return Promise.resolve(null);if(r instanceof Y||r.isMaterial===!0){const t=r,o=[],n=new Array;if(L&&X.add(t),t instanceof ae)for(const s of Object.keys(t.uniforms)){const i=t.uniforms[s].value;if(i?.isTexture===!0){const a=this.assignTextureLODForSlot(i,e,t,s);o.push(a),n.push(s)}}else for(const s of Object.keys(t)){const i=t[s];if(i?.isTexture===!0){const a=this.assignTextureLODForSlot(i,e,t,s);o.push(a),n.push(s)}}return Promise.all(o).then(s=>{const i=new Array;for(let a=0;a<s.length;a++){const l=s[a],d=n[a];l instanceof T?i.push({material:t,slot:d,texture:l,level:e}):i.push({material:t,slot:d,texture:null,level:e})}return i})}if(r instanceof T||r.isTexture===!0){const t=r;return this.assignTextureLODForSlot(t,e,null,null)}return Promise.resolve(null)}static assignTextureLODForSlot(r,e,t,o){return r?.isTexture!==!0?Promise.resolve(null):O.getOrLoadLOD(r,e).then(n=>{if(Array.isArray(n))return null;if(n?.isTexture===!0){if(n!=r&&(t&&o&&(t[o]=n),L&&o&&t)){const s=this.getAssignedLODInformation(r);s&&ee(t,o,s.url)}return n}else L=="verbose"&&console.warn("No LOD found for",r,e);return null}).catch(n=>(console.error("Error loading LOD",r,n),null))}afterRoot(r){var e,t;return L&&console.log("AFTER",this.url,r),(e=this.parser.json.textures)==null||e.forEach((o,n)=>{if(o!=null&&o.extensions){const s=o?.extensions[A];if(s)for(const i of this.parser.associations.keys())i instanceof T&&this.parser.associations.get(i).textures===n&&O.registerTexture(this.url,i,n,s)}}),(t=this.parser.json.meshes)==null||t.forEach((o,n)=>{if(o!=null&&o.extensions){const s=o?.extensions[A];if(s&&s.lods){for(const i of this.parser.associations.keys())if(i instanceof I){const a=this.parser.associations.get(i);a.meshes===n&&i instanceof I&&O.registerMesh(this.url,i.uuid,i,s.lods.length,a.primitives,s)}}}}),null}static async getOrLoadLOD(r,e){var t,o,n;const s=L=="verbose",i=r.userData.LODS;if(!i)return null;const a=i?.key;let l;if(r instanceof T&&r.source&&r.source[V]&&(l=r.source[V]),l||(l=O.lodInfos.get(a)),l){if(e>0){let f=!1;const v=Array.isArray(l.lods);if(v&&e>=l.lods.length?f=!0:v||(f=!0),f)return this.lowresCache.get(a)}const d=Array.isArray(l.lods)?l.lods[e].path:l.lods;if(!d)return L&&!l["missing:uri"]&&(l["missing:uri"]=!0,console.warn("Missing uri for progressive asset for LOD "+e,l)),null;const m=ve(i.url,d);if(m.endsWith(".glb")||m.endsWith(".gltf")){if(!l.guid)return console.warn("missing pointer for glb/gltf texture",l),null;const f=m+"_"+l.guid,v=this.previouslyLoaded.get(f);if(v!==void 0){s&&console.log(`LOD ${e} was already loading/loaded: ${f}`);let u=await v.catch(p=>(console.error(`Error loading LOD ${e} from ${m}
2
- `,p),null)),g=!1;if(u==null||(u instanceof T&&r instanceof T?(t=u.image)!=null&&t.data||(o=u.source)!=null&&o.data?u=this.copySettings(r,u):(g=!0,this.previouslyLoaded.delete(f)):u instanceof W&&r instanceof W&&((n=u.attributes.position)!=null&&n.array||(g=!0,this.previouslyLoaded.delete(f)))),!g)return u}const _=l,S=new Promise(async(u,g)=>{const p=new he;xe(p),L&&(await new Promise(x=>setTimeout(x,1e3)),s&&console.warn("Start loading (delayed) "+m,_.guid));let b=m;if(Array.isArray(l.lods)){const x=l.lods[e];x.hash&&(b+="?v="+x.hash)}const y=await p.loadAsync(b).catch(x=>(console.error(`Error loading LOD ${e} from ${m}
3
- `,x),null));if(!y)return null;const E=y.parser;s&&console.log("Loading finished "+m,_.guid);let M=0;if(y.parser.json.textures){let x=!1;for(const h of y.parser.json.textures){if(h!=null&&h.extensions){const D=h?.extensions[A];if(D!=null&&D.guid&&D.guid===_.guid){x=!0;break}}M++}if(x){let h=await E.getDependency("texture",M);return s&&console.log('change "'+r.name+'" \u2192 "'+h.name+'"',m,M,h,f),r instanceof T&&(h=this.copySettings(r,h)),h&&(h.guid=_.guid),u(h)}}if(M=0,y.parser.json.meshes){let x=!1;for(const h of y.parser.json.meshes){if(h!=null&&h.extensions){const D=h?.extensions[A];if(D!=null&&D.guid&&D.guid===_.guid){x=!0;break}}M++}if(x){const h=await E.getDependency("mesh",M),D=_;if(s&&console.log(`Loaded Mesh "${h.name}"`,m,M,h,f),h instanceof I){const k=h.geometry;return O.assignLODInformation(i.url,k,a,e,void 0,D.density),u(k)}else{const k=new Array;for(let z=0;z<h.children.length;z++){const B=h.children[z];if(B instanceof I){const H=B.geometry;O.assignLODInformation(i.url,H,a,e,z,D.density),k.push(H)}}return u(k)}}}return u(null)});return this.previouslyLoaded.set(f,S),await S}else if(r instanceof T){s&&console.log("Load texture from uri: "+m);const f=await new le().loadAsync(m);return f?(f.guid=l.guid,f.flipY=!1,f.needsUpdate=!0,f.colorSpace=r.colorSpace,s&&console.log(l,f)):L&&console.warn("failed loading",m),f}}else L&&console.warn(`Can not load LOD ${e}: no LOD info found for "${a}" ${r.name}`,r.type);return null}static assignLODInformation(r,e,t,o,n,s){if(!e)return;e.userData||(e.userData={});const i=new Le(r,t,o,n,s);e.userData.LODS=i,e.userData.LOD=o}static getAssignedLODInformation(r){var e;return((e=r?.userData)==null?void 0:e.LODS)||null}static copySettings(r,e){return this._copiedTextures.get(r)||(e=e.clone(),this._copiedTextures.set(r,e),e.offset=r.offset,e.repeat=r.repeat,e.colorSpace=r.colorSpace,e)}};let w=O;c(w,"registerTexture",(r,e,t,o)=>{L&&console.log("> Progressive: register texture",t,e.name,e.uuid,e,o),e.source&&(e.source[V]=o);const n=e.uuid;O.assignLODInformation(r,e,n,0,0,void 0),O.lodInfos.set(n,o),O.lowresCache.set(n,e)}),c(w,"registerMesh",(r,e,t,o,n,s)=>{var i;L&&console.log("> Progressive: register mesh",n,t.name,s,t.uuid,t);const a=t.geometry;a.userData||(a.userData={}),O.assignLODInformation(r,a,e,o,n,s.density),O.lodInfos.set(e,s);let l=O.lowresCache.get(e);l?l.push(t.geometry):l=[t.geometry],O.lowresCache.set(e,l);for(const d of K)(i=d.onRegisteredMesh)==null||i.call(d,t,s)}),c(w,"lodInfos",new Map),c(w,"previouslyLoaded",new Map),c(w,"lowresCache",new Map),c(w,"_copiedTextures",new Map);class Le{constructor(e,t,o,n,s){c(this,"url"),c(this,"key"),c(this,"level"),c(this,"index"),c(this,"density"),this.url=e,this.key=t,this.level=o,n!=null&&(this.index=n),s!=null&&(this.density=s)}}let re=q("debugprogressive");const Oe=q("noprogressive"),P=class{constructor(r){c(this,"renderer"),c(this,"projectionScreenMatrix",new J),c(this,"cameraFrustrum",new ue),c(this,"updateInterval",0),c(this,"pause",!1),c(this,"plugins",[]),c(this,"_originalRender"),c(this,"_sphere",new ce),c(this,"_box",new Q),c(this,"tempMatrix",new J),c(this,"_tempWorldPosition",new j),c(this,"_tempBoxSize",new j),c(this,"_tempBox2Size",new j),this.renderer=r}enable(){if(this._originalRender)return;let r=0;this._originalRender=this.renderer.render;const e=this;let t=0;ye(this.renderer),this.renderer.render=function(o,n){const s=t++,i=r++;e._originalRender.call(this,o,n),e.onUpdateLODs(o,n,i,s),r--}}disable(){this._originalRender&&(this.renderer.render=this._originalRender,this._originalRender=void 0)}onUpdateLODs(r,e,t,o){var n;if(Oe||this.pause||this.updateInterval>0&&o%this.updateInterval!=0)return;this.projectionScreenMatrix.multiplyMatrices(e.projectionMatrix,e.matrixWorldInverse),this.cameraFrustrum.setFromProjectionMatrix(this.projectionScreenMatrix,this.renderer.coordinateSystem);const s=1e5,i=this.renderer.renderLists.get(r,t).opaque;for(const a of i){const l=a.object;for(const d of this.plugins)(n=d.onBeforeUpdateLOD)==null||n.call(d,this.renderer,r,e,l);(l instanceof I||l.isMesh)&&this.updateLODs(e,l,s)}}updateLODs(r,e,t){let o=e.userData.LOD_state;o||(o=new we,e.userData.LOD_state=o);let n=this.calculateLodLevel(r,e,o,t);n=Math.round(n),o.lastLodLevel=n,n>=0&&this.loadProgressiveMeshes(e,n);let s=0;if(e.material){const i=e["DEBUG:LOD"];if(i!=null&&(s=i),Array.isArray(e.material))for(const a of e.material)this.loadProgressiveTextures(a,s);else this.loadProgressiveTextures(e.material,s)}}loadProgressiveTextures(r,e){return r&&r.userData&&r.userData.LOD!==e?(r.userData.LOD=e,w.assignTextureLOD(r,e)):Promise.resolve(null)}loadProgressiveMeshes(r,e){if(e=0,!r)return Promise.resolve(null);if(r.userData||(r.userData={}),r.userData.LOD!==e){r.userData.LOD=e;const t=r.geometry;return w.assignMeshLOD(r,e).then(o=>(o&&r.userData.LOD==e&&t!=r.geometry,o))}return Promise.resolve(null)}calculateLodLevel(r,e,t,o){var n;if(!e)return-1;let s=10+1;if(r){if(re&&e["DEBUG:LOD"]!=null)return e["DEBUG:LOD"];const i=w.getMeshLODInformation(e.geometry),a=i?.lods;if(!a||a.length<=0)return 99;if(!((n=this.cameraFrustrum)!=null&&n.intersectsObject(e)))return console.log("Mesh not visible"),99;const l=e.geometry.boundingBox;if(l&&r instanceof de){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 u=r.getWorldPosition(this._tempWorldPosition);if(this._sphere.containsPoint(u))return 0}if(this._box.copy(l),this._box.applyMatrix4(e.matrixWorld),this._box.applyMatrix4(this.projectionScreenMatrix),this.renderer.xr.enabled&&r.fov>70){const u=this._box.min,g=this._box.max;let p=u.x,b=u.y,y=g.x,E=g.y;const M=2,x=1.5,h=(u.x+g.x)*.5,D=(u.y+g.y)*.5;p=(p-h)*M+h,b=(b-D)*M+D,y=(y-h)*M+h,E=(E-D)*M+D;const k=p<0&&y>0?0:Math.min(Math.abs(u.x),Math.abs(g.x)),z=b<0&&E>0?0:Math.min(Math.abs(u.y),Math.abs(g.y)),B=Math.max(k,z);t.lastCentrality=(x-B)*(x-B)*(x-B)}else t.lastCentrality=1;const d=this._box.getSize(this._tempBoxSize);d.multiplyScalar(.5),screen.availHeight>0&&d.multiplyScalar(this.renderer.domElement.clientHeight/screen.availHeight),d.x*=r.aspect;const m=r.matrixWorldInverse,f=new Q;f.copy(l),f.applyMatrix4(e.matrixWorld),f.applyMatrix4(m);const v=f.getSize(this._tempBox2Size),_=Math.max(v.x,v.y);if(Math.max(d.x,d.y)!=0&&_!=0&&(d.z=v.z/Math.max(v.x,v.y)*Math.max(d.x,d.y)),t.lastScreenCoverage=Math.max(d.x,d.y,d.z),t.lastScreenspaceVolume.copy(d),t.lastScreenCoverage*=t.lastCentrality,re&&P.debugDrawLine){const u=this.tempMatrix.copy(this.projectionScreenMatrix);u.invert();const g=P.corner0,p=P.corner1,b=P.corner2,y=P.corner3;g.copy(this._box.min),p.copy(this._box.max),p.x=g.x,b.copy(this._box.max),b.y=g.y,y.copy(this._box.max);const E=(g.z+y.z)*.5;g.z=p.z=b.z=y.z=E,g.applyMatrix4(u),p.applyMatrix4(u),b.applyMatrix4(u),y.applyMatrix4(u),P.debugDrawLine(g,p,255),P.debugDrawLine(g,b,255),P.debugDrawLine(p,y,255),P.debugDrawLine(b,y,255)}let S=999;if(a&&t.lastScreenCoverage>0){for(let u=0;u<a.length;u++)if(a[u].density/t.lastScreenCoverage<o){S=u;break}}S<s&&(s=S)}}return s}};let C=P;c(C,"corner0",new j),c(C,"corner1",new j),c(C,"corner2",new j),c(C,"corner3",new j),c(C,"debugDrawLine");class we{constructor(){c(this,"lastLodLevel",0),c(this,"lastScreenCoverage",0),c(this,"lastScreenspaceVolume",new j),c(this,"lastCentrality",0)}}const te=Symbol("NEEDLE_mesh_lod"),U=Symbol("NEEDLE_texture_lod");function se(){const r=document.querySelector("model-viewer");if(!r)return;let e=null;for(let t=r;t!=null;t=Object.getPrototypeOf(t)){const o=Object.getOwnPropertySymbols(t).find(n=>n.toString()=="Symbol(renderer)");!e&&o!=null&&(e=r[o].threeRenderer)}if(e){console.log("Adding Needle LODs to modelviewer");const t=new C(e);t.plugins.push(new be(r)),t.enable()}}class be{constructor(e){c(this,"modelviewer"),this.modelviewer=e}onBeforeUpdateLOD(e,t,o,n){this.tryParseMeshLOD(t,n),this.tryParseTextureLOD(t,n)}getUrl(){return this.modelviewer.getAttribute("src")}tryGetCurrentGLTF(e){return e._currentGLTF}tryParseTextureLOD(e,t){if(t[U]==!0)return;t[U]=!0;const o=this.tryGetCurrentGLTF(e),n=this.getUrl();if(!n){console.error("No url found in modelviewer");return}if(o&&t.material){let s=function(a){var l,d,m;if(a[U]==!0)return;a[U]=!0,a.userData&&(a.userData.LOD=-1);const f=Object.keys(a);for(let v=0;v<f.length;v++){const _=f[v],S=a[_];if(S?.isTexture===!0){const u=(d=(l=S.userData)==null?void 0:l.associations)==null?void 0:d.textures,g=o.parser.json.textures[u];if((m=g.extensions)!=null&&m[A]){const p=g.extensions[A];p&&n&&w.registerTexture(n,S,p.lods.length,p)}}}};const i=t.material;if(Array.isArray(i))for(const a of i)s(a);else s(i)}}tryParseMeshLOD(e,t){var o,n;if(t[te]==!0)return;t[te]=!0;const s=this.getUrl();if(!s){console.error("No url found in modelviewer");return}const i=(n=(o=t.userData)==null?void 0:o.gltfExtensions)==null?void 0:n[A];if(i&&s){const a=t.uuid;w.registerMesh(s,a,t,0,i.lods.length,i)}}}document.addEventListener("DOMContentLoaded",()=>{Me()});function Me(){se()}export{A as EXTENSION_NAME,C as LODsManager,w as NEEDLE_progressive,se as patchModelViewer,De as registerPlugin};
@@ -1,27 +0,0 @@
1
- <html>
2
-
3
- <head>
4
- <title>Minimal Example</title>
5
- <script type="importmap">
6
- {
7
- "imports": {
8
- "three": "https://unpkg.com/three/build/three.module.js",
9
- "three/": "https://unpkg.com/three/"
10
- }
11
- }
12
- </script>
13
- <script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.4.0/model-viewer.min.js"></script>
14
- <script type="module" src="./@needle-tools-gltf-progressive.min.js"></script>
15
- </head>
16
-
17
- <model-viewer src="https://engine.needle.tools/demos/lods/assets/ganesha.glb" ar
18
- shadow-intensity="1" camera-controls touch-action="pan-y"></model-viewer>
19
-
20
- <style>
21
- model-viewer {
22
- width: 100%;
23
- height: 100vh;
24
- }
25
- </style>
26
-
27
- </html>