@hology/core 0.0.272 → 0.0.274
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/dist/effects/sequence/sequence-player.js +1 -1
- package/dist/effects/vfx/gpu-sprite-runtime.js +1 -1
- package/dist/effects/vfx/gpu-vfx-world-service.js +1 -1
- package/dist/effects/vfx/rates.d.ts +2 -0
- package/dist/effects/vfx/rates.js +1 -1
- package/dist/effects/vfx/vfx-actor.d.ts +2 -1
- package/dist/effects/vfx/vfx-actor.js +1 -1
- package/dist/effects/vfx/vfx-asset.d.ts +10 -1
- package/dist/effects/vfx/vfx-backend-partition.d.ts +8 -0
- package/dist/effects/vfx/vfx-backend-partition.js +4 -0
- package/dist/effects/vfx/vfx-materializer.js +1 -1
- package/dist/effects/vfx/vfx-render-preparation.js +1 -1
- package/dist/effects/vfx/vfx-renderers.d.ts +27 -2
- package/dist/effects/vfx/vfx-renderers.js +1 -1
- package/dist/gameplay/actors/builtin/components/character/character-animation.d.ts +2 -0
- package/dist/gameplay/actors/builtin/components/character/character-animation.js +1 -1
- package/dist/gameplay/actors/builtin/components/character/character-movement.d.ts +19 -0
- package/dist/gameplay/actors/builtin/components/character/character-movement.js +1 -1
- package/dist/gameplay/actors/builtin/water-spline-volume-actor.d.ts +10 -0
- package/dist/gameplay/actors/builtin/water-spline-volume-actor.js +1 -1
- package/dist/gameplay/actors/builtin/water-volume-actor.d.ts +3 -1
- package/dist/gameplay/actors/builtin/water-volume-actor.js +1 -1
- package/dist/gameplay/actors/camera/first-person-camera-component.js +1 -1
- package/dist/gameplay/actors/camera/third-person-camera-component.d.ts +7 -0
- package/dist/gameplay/actors/camera/third-person-camera-component.js +1 -1
- package/dist/gameplay/ai/behavior-tree/move.d.ts +5 -0
- package/dist/gameplay/ai/behavior-tree/move.js +1 -1
- package/dist/gameplay/ai/behavior-tree/move.test.d.ts +2 -0
- package/dist/gameplay/ai/behavior-tree/move.test.js +4 -0
- package/dist/gameplay/animation/root-motion.js +1 -1
- package/dist/gameplay/input/input-service.d.ts +9 -1
- package/dist/gameplay/input/input-service.js +1 -1
- package/dist/gameplay/services/physics/physics-system.d.ts +2 -0
- package/dist/gameplay/services/physics/physics-system.js +1 -1
- package/dist/gameplay/services/render.d.ts +4 -1
- package/dist/gameplay/services/render.js +1 -1
- package/dist/gameplay/services/world.d.ts +5 -0
- package/dist/rendering/node-material-webgpu-resolver.js +1 -1
- package/dist/rendering/webgpu-experimental-backend.d.ts +4 -2
- package/dist/rendering/webgpu-experimental-backend.js +1 -1
- package/dist/rendering.d.ts +17 -2
- package/dist/rendering.js +1 -1
- package/dist/scene/asset-resource-loader.d.ts +4 -0
- package/dist/scene/asset-resource-loader.js +1 -1
- package/dist/scene/collision/collision-shape-import.js +1 -1
- package/dist/scene/collision/rapier-shape.js +1 -1
- package/dist/scene/landscape/landscape-geometry.d.ts +24 -0
- package/dist/scene/landscape/landscape-geometry.js +4 -0
- package/dist/scene/landscape/landscape-manager.d.ts +1 -0
- package/dist/scene/landscape/landscape-manager.js +1 -1
- package/dist/scene/materializer.d.ts +7 -1
- package/dist/scene/materializer.js +1 -1
- package/dist/scene/scene-data-service.d.ts +2 -1
- package/dist/scene/scene-data-service.js +1 -1
- package/dist/scene/sky.d.ts +2 -0
- package/dist/scene/sky.js +1 -1
- package/dist/shader/graph/compiler.js +1 -1
- package/dist/shader/graph/registry.js +1 -1
- package/dist/shader/parameter.js +1 -1
- package/dist/shader/sprite-shader.js +1 -1
- package/dist/shader-nodes/depth.d.ts +6 -4
- package/dist/shader-nodes/depth.js +1 -1
- package/dist/shader-nodes/index.d.ts +2 -1
- package/dist/shader-nodes/index.js +1 -1
- package/dist/shader-nodes/surface-reflection.d.ts +9 -0
- package/dist/shader-nodes/surface-reflection.js +4 -0
- package/dist/shader-nodes/water-volume.d.ts +6 -2
- package/dist/shader-nodes/water-volume.js +1 -1
- package/dist/test/asset-resource-loader-texture-array.test.js +1 -1
- package/dist/test/character-movement-solve.test.js +1 -1
- package/dist/test/collision-shape-import.test.js +1 -1
- package/dist/test/first-person-camera-component.test.js +1 -1
- package/dist/test/frame-stats.test.js +1 -1
- package/dist/test/gpu-sprite-compiler.test.js +1 -1
- package/dist/test/input.test.js +1 -1
- package/dist/test/landscape-geometry.test.d.ts +2 -0
- package/dist/test/landscape-geometry.test.js +4 -0
- package/dist/test/material-assignment.test.js +1 -1
- package/dist/test/materializer-dynamic-transform.test.js +1 -1
- package/dist/test/materializer-prefetch.test.js +1 -1
- package/dist/test/net-character-movement.test.js +1 -1
- package/dist/test/node-material-webgpu-resolver.test.js +1 -1
- package/dist/test/parameter-definition.test.js +1 -1
- package/dist/test/physics-hitstop.test.d.ts +2 -0
- package/dist/test/physics-hitstop.test.js +4 -0
- package/dist/test/physics-ray-object.test.js +1 -1
- package/dist/test/post-process-effect.test.js +1 -1
- package/dist/test/rapier-trimesh.test.d.ts +2 -0
- package/dist/test/rapier-trimesh.test.js +4 -0
- package/dist/test/sequence-animation-retiming.test.js +1 -1
- package/dist/test/sequence-vfx.test.js +1 -1
- package/dist/test/surface-reflection.test.d.ts +2 -0
- package/dist/test/surface-reflection.test.js +4 -0
- package/dist/test/vfx-backend-partition.test.d.ts +2 -0
- package/dist/test/vfx-backend-partition.test.js +4 -0
- package/dist/test/vfx-playback-transform.test.js +1 -1
- package/dist/test/vfx-render-preparation.test.js +1 -1
- package/dist/test/water-spline-volume-actor.test.js +1 -1
- package/dist/utils/mesh.d.ts +1 -1
- package/dist/utils/mesh.js +1 -1
- package/dist/utils/three/frame-stats.d.ts +8 -0
- package/dist/utils/three/frame-stats.js +1 -1
- package/package.json +2 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{createAuthoredCollisionShapes as e}from"./collision/authored-colliders.js";import{parseAnimationClips as t}from"./animation-binary.js";import{AudioLoader as r,BufferGeometry as a,Group as i,LoadingManager as s,Mesh as n,Object3D as o,Texture as l,TextureLoader as c,Vector3 as h}from"three";import{MTLLoader as u,OBJLoader as m,LUTCubeLoader as d}from"three-stdlib";import{cloneMesh as p,mergeCompatibleSkinnedMeshes as f}from"../utils/mesh.js";import{pathJoin as g}from"../utils/files.js";import{Subject as y,firstValueFrom as w}from"rxjs";import{importCollisionShapes as x,isCollisionMesh as M}from"./collision/collision-shape-import.js";import*as b from"three";import{iterateMaterials as A}from"../utils/materials.js";import{applyMaterial as L,getConvertedFbxToGlbAssignedMaterialTextureFlipY as v}from"./materializer.js";import{BufferGeometryUtils as S,DRACOLoader as _,EXRLoader as T,FBXLoader as R,GLTFLoader as K,KTX2Loader as B,TGALoader as F,UltraHDRLoader as C}from"three/examples/jsm/Addons.js";import{disposeScene as P}from"../utils/three/cleanup.js";import{resolveAnimationRigBasisRotation as I,setAnimationClipRigId as k}from"../gameplay/animation/retarget.js";import{parseNativeTexture as D,selectNativeTexture as j}from"./native-texture.js";(()=>{const e=new Uint8Array([255,0,0,255]),t=new b.DataTexture(e,1,1,b.RGBAFormat);t.needsUpdate=!0})();const W=new _;W.setDecoderConfig({type:"js"}),W.setDecoderPath("/assets/draco/");const U=["glb","gltf","fbx","obj"];export class AssetResourceLoader{onError(e){console.error(e)}constructor(){this.cache=new Map,this.textureCache=new Map,this.loadingManager=new s,this.glbLoader=new K(this.loadingManager).setDRACOLoader(W),this.fbxLoader=new R(this.loadingManager),this.objLoader=new m(this.loadingManager),this.textureLoader=new c(this.loadingManager),this.tgaLoader=new F(this.loadingManager),this.exrLoader=new T(this.loadingManager),this.cubeLoader=new d(this.loadingManager),this.hdrLoader=new C(this.loadingManager),this.ktx2Loader=new B(this.loadingManager),this.ktx2Ready=Promise.resolve(),this.nativeTextureLoader=new b.FileLoader(this.loadingManager).setResponseType("arraybuffer"),this.nativeBcSupported=!1,this.nativeAstcSupported=!1,this._textureLoader=new b.ImageBitmapLoader(this.loadingManager),this.audioLoader=new r(this.loadingManager),this.initialisedKtx2=!1,this._retrievedMeshes=[],this._retrievedTextures=[],this.makeReady=new y,this.ready=w(this.makeReady),this.directFileLoading=!1,this._texturePromises=new Map,this._arrayTexturesByFileKey=new Map,this._arrayTexturePromisesByFileKey=new Map,this._arrayTextureSettingsByFileKey=new Map,this._animationPromises=new Map,this._animationCache=new Map,this.asyncMeshResults=new Map,this.asyncAudioResults=new Map,this.clipsRequests=new Set,this.timeToLoadClips=0,this.collisionShapeCache=new Map,this.optimizedMeshes=new Set}setDirectFileLoading(e){this.directFileLoading=e}setImageBitmapDecoder(e){this.imageBitmapDecoder=e}decodeImageBitmap(e){return this.imageBitmapDecoder?.(e)}configureKtx2(){this.initialisedKtx2=!0;null!=getElectronArg("windowId")||import.meta.url.includes(".vite")||import.meta.url.includes("hology/packages/core")?this.ktx2Loader.setTranscoderPath("/assets/basis/"):this.ktx2Loader.setTranscoderPath(new URL("../assets/basis/",import.meta.url).href),this.ktx2Loader.setWorkerLimit(6)}initKtx2(e){this.configureKtx2(),this.ktx2Loader.detectSupport(e),this.nativeBcSupported=e.extensions.has("EXT_texture_compression_bptc"),this.nativeAstcSupported=e.extensions.has("WEBGL_compressed_texture_astc")}initKtx2ForWebGpu(e){this.configureKtx2(),this.ktx2Ready=Promise.resolve(e??navigator.gpu?.requestAdapter({powerPreference:"high-performance"})??null).then(e=>{this.nativeBcSupported=!0===e?.features.has("texture-compression-bc"),this.nativeAstcSupported=!0===e?.features.has("texture-compression-astc"),this.ktx2Loader.detectSupport({isWebGPURenderer:!0,hasFeature:t=>!0===e?.features.has(t)})})}setDataDir(e){this.basePath=g(e,"asset-resources"),this.makeReady.next(!0)}getUri(e){if(e.startsWith("asset://"))return e;const t=function(e){return G.get(e)}(e),r=getElectronArg("windowId");return null!=r||import.meta.url.includes(".vite")||import.meta.url.includes("hology/packages/core")?g(this.basePath,e)+(null!=r?`?windowId=${r}${null!=t?`&v=${t}`:""}`:""):new URL(g("..",this.basePath,e),import.meta.url).href}async getTexture(e){if(!e||!e.fileKey)return null;if("png"===e.fileFormat||e.fileFormat,this._texturePromises.has(e.id))return this._texturePromises.get(e.id);const t=this._getTexture(e).finally(()=>{this._texturePromises.delete(e.id)});return this._texturePromises.set(e.id,t),t}async _getTexture(e){if(null==e||null==e.fileKey)return null;if(await this.ready,e.fileKey.toLowerCase().endsWith(".ktx2")&&await this.ktx2Ready,this.textureCache.has(e.id)){const t=this.textureCache.get(e.id);if(null!=e.texture){const r=N(t,e);t.flipY!==r?(t.needsUpdate=!0,t.flipY=r):t.wrapS!==O(e.texture?.wrapS)?(t.needsUpdate=!0,t.wrapS=O(e.texture?.wrapS)):t.wrapT!==O(e.texture?.wrapT)&&(t.wrapT=O(e.texture?.wrapT),t.needsUpdate=!0)}const r=X(q(e));t.colorSpace!==r&&(t.colorSpace=r,t.needsUpdate=!0)}else try{const t=this._getTextureLoader(e.fileKey),r=null!=this.imageBitmapDecoder&&/\.(png|jpe?g)$/i.test(e.fileKey),a=r||this.directFileLoading&&t===this.textureLoader,i=this.getUri(e.fileKey),s=r?this.imageBitmapDecoder(i).catch(t=>(console.warn(`Worker image decode failed for ${e.name}; falling back to ImageBitmapLoader`,t),this._textureLoader.loadAsync(i))):e.texture?.nativeFileKeys&&t===this.ktx2Loader?this.loadKtx2Texture(e.fileKey,e.texture.nativeFileKeys):(a?this._textureLoader:t).loadAsync(i);await s.then(t=>(a&&((t=new l(t)).needsUpdate=!0),t.texture3D?t=t.texture3D:(t.wrapS=O(e.texture?.wrapS),t.wrapT=O(e.texture?.wrapT),t.flipY=N(t,e),t.colorSpace=X(q(e))),this.textureCache.set(e.id,t),this._retrievedTextures.push(t),t))}catch(t){return console.warn("Failed to load texture "+e.name,t),null}const t=this.textureCache.get(e.id);return null!=t&&(t.userData.assetId=e.id),t}async getTextureArray(e){const t=e.texture?.textureArrayFileKey,r=e.texture?.textureArrayLayer;if(!t||null==r)return{texture:null,layerIndex:null};let a=this._arrayTexturesByFileKey.get(t);if(null==a){let r=this._arrayTexturePromisesByFileKey.get(t);null==r&&(r=this.loadTextureArray(t,e.texture?.nativeArrayFileKeys).finally(()=>{this._arrayTexturePromisesByFileKey.delete(t)}),this._arrayTexturePromisesByFileKey.set(t,r)),a=await r}return this.applyTextureArraySettings(t,a,e),{texture:a,layerIndex:r}}async loadKtx2Texture(e,t){await this.ktx2Ready;const r=j(t,this.nativeBcSupported,this.nativeAstcSupported);if(r)try{const e=await this.nativeTextureLoader.loadAsync(this.getUri(r.fileKey));return D(e,r.format)}catch(e){console.warn(`Failed to load native texture ${r.fileKey}; using portable KTX2`,e)}return this._getTextureLoader(e).loadAsync(this.getUri(e))}async loadTextureArray(e,t){let r=await this.loadKtx2Texture(e,t);return r instanceof l||(r=r.texture3D),this._arrayTexturesByFileKey.set(e,r),this._retrievedTextures.push(r),r}applyTextureArraySettings(e,t,r){const a=function(e){return{wrapS:O(e.texture?.wrapS),wrapT:O(e.texture?.wrapT)}}(r),i=this._arrayTextureSettingsByFileKey.get(e);if(null!=i&&(n=a,(s=i).wrapS!==n.wrapS||s.wrapT!==n.wrapT))return void console.warn(`Texture array ${e} is used with incompatible texture settings. Texture arrays share wrapping; rebuild arrays so incompatible textures are grouped separately.`);var s,n;null==i&&this._arrayTextureSettingsByFileKey.set(e,a);let o=!1;t.wrapS!==a.wrapS&&(t.wrapS=a.wrapS,o=!0),t.wrapT!==a.wrapT&&(t.wrapT=a.wrapT,o=!0),!1!==t.flipY&&(t.flipY=!1,o=!0),o&&(t.needsUpdate=!0)}_getTextureLoader(e){return e?.toLowerCase().endsWith(".tga")?this.tgaLoader:e?.toLowerCase().endsWith(".ktx2")?this.ktx2Loader:e?.toLowerCase().endsWith(".exr")?this.exrLoader:e?.toLowerCase().endsWith(".cube")?this.cubeLoader:this.textureLoader}clearCache(e){this.clearCacheByAsset(e)}clearCacheByAsset(e){null!=e&&(this.clearCacheById(e.id),null!=e.fileKey&&(Array.from(this.cache.keys()).forEach(t=>{t.startsWith(e.fileKey)&&this.cache.delete(t)}),Array.from(this.asyncMeshResults.keys()).forEach(t=>{t.startsWith(e.fileKey)&&this.asyncMeshResults.delete(t)}),Array.from(this._animationCache.keys()).forEach(t=>{t.startsWith(e.fileKey+"|")&&this._animationCache.delete(t)}),Array.from(this._animationPromises.keys()).forEach(t=>{t.startsWith(e.fileKey+"|")&&this._animationPromises.delete(t)}),z(e.fileKey)),null!=e.materialLib&&z(e.materialLib))}clearCacheById(e){const t=Array.from(this.cache.values()).find(t=>t.scene.userData.assetId===e);t&&Array.from(this.cache.keys()).forEach(e=>{this.cache.get(e)===t&&this.cache.delete(e)}),this.textureCache.delete(e),this._texturePromises.delete(e),Array.from(this.collisionShapeCache.keys()).forEach(t=>{t.startsWith(e+"|")&&this.collisionShapeCache.delete(t)})}async getMesh(e,t){if(await this.ready,null==e)return console.error("No asset was provided"),{scene:new i,animations:[]};if(!U.includes(e.fileFormat?.toLowerCase()))return console.error("Unsupported mesh file format "+e.fileFormat,e),{scene:new i,animations:[]};const r=e.fileKey+(!0===t?.mergeGeomtries?"1":"0");if(!this.cache.has(r))try{this.asyncMeshResults.has(r)||this.asyncMeshResults.set(r,this.loadMesh(e).finally(()=>this.asyncMeshResults.delete(r))),this.cache.set(r,await this.asyncMeshResults.get(r))}catch(e){return this.onError(e),{scene:new i,animations:[]}}const a=this.cache.get(r),s=a.scene,o=a.animations;if(!1===t?.clone)return{scene:s,animations:o};const l=this.computeCollisionShapes(e,s),c=(e.mesh?.secondaryMotion?.regions.length??0)>0;!0!==t?.mergeGeomtries||c||this.optimizeDrawGroups(s);const h=p(s);h.traverse(e=>{e instanceof n&&e.material instanceof Array&&(e.material=e.material.slice())});const u=new AssetMeshInstance;u.add(h),u.collisionShapes=l,u.animations=o,u.userData.assetId=e.id,u.userData.secondaryMotion=e.mesh?.secondaryMotion,function(e){const t=e.children.length>0?e.children:[e];for(let e=0;e<t.length;e++)r(t[e],"",e);function r(e,t,a){const i=e instanceof n?"Mesh":"Group",s=`${t}/${e.name?.trim()?.length>0?e.name:i}[${a}]`;e.userData.secondaryMotionMeshPath=s;for(let t=0;t<e.children.length;t++)r(e.children[t],s,t)}}(u);const m=e.mesh?.animationRigId??e.anim?.rigId;if(u.userData.animationRigId=m,h.userData.animationRigId=m,null!=m&&null!=this.animationRigProvider){const e=await this.animationRigProvider(m);u.userData.animationRig=e,h.userData.animationRig=e,null!=e&&!1!==t?.applyRigBasis&&u.quaternion.copy(I(e))}const d=e.receiveShadow??!1,f=e.castShadow??!1;if(h.traverse(e=>{e.castShadow=f,e.receiveShadow=d}),!0===t?.rescale&&"number"==typeof e.mesh?.rescale&&u.scale.setScalar(e.mesh?.rescale),!0===t?.applyMaterials){const r=t?.materialResolver??this.materialProvider;if(null!=r)for(const t of e.materialAssignments??[])await L(u,t,r,void 0,{textureFlipY:v(e)});else console.warn(`applyMaterials was set to true for ${e.name} but no material resolver was provided.`)}return{scene:u,animations:o}}async getAudio(e){await this.ready;const t=e.fileKey;if(this.asyncAudioResults.has(t))return await this.asyncAudioResults.get(t);const r=this.audioLoader.loadAsync(this.getUri(e.fileKey)).finally(()=>{setTimeout(()=>{this.asyncAudioResults.delete(t)},1e3)});return this.asyncAudioResults.set(t,r),r}async getAnimationClip(e){if(null==e||null==e.fileKey||null==e.anim?.clip)return console.warn("Asset or animation clip name is not defined",e),null;const r=e.anim.clip,a=e.fileKey,i=a+"|"+r;if(this._animationCache.has(i))return this._animationCache.get(i);if(this._animationPromises.has(i))return this._animationPromises.get(i);this.clipsRequests.add(e.id);const s=(async()=>{try{const s=("anim"===e.fileFormat?t(await new b.FileLoader(this.loadingManager).setResponseType("arraybuffer").loadAsync(this.getUri(a))):(await this.getMesh(e,{mergeGeomtries:!1,clone:!1})).animations).find(e=>e.name===r);return null!=s&&(k(s,e.anim?.rigId),this._animationCache.set(i,s)),s}catch(e){return this.onError(e),null}finally{setTimeout(()=>this._animationPromises.delete(i),1e3)}})();return this._animationPromises.set(i,s),s}computeCollisionShapes(t,r){if(!0!==t.collisionDetection)return[];const a=t.id+"|"+(t.mesh?.collisions?.shapeType??"")+"|"+(t.mesh?.rescale??1)+"|"+JSON.stringify(t.mesh?.colliders??[]);if(!this.collisionShapeCache.has(a)){const i=t.mesh?.colliders??[],s=i.length>0?e(i,t.mesh?.rescale):x(r,t);this.collisionShapeCache.set(a,s)}return this.collisionShapeCache.get(a)}async loadMesh(e){return await this.ready,await this.loadByAsset(e).then(t=>(t.scene.traverse(t=>{if(t instanceof n&&t.geometry instanceof a){const r=removeInvalidFaces(t.geometry);r>0&&console.warn(`Removed ${r} invalid face${1===r?"":"s"} from ${e.name}/${t.name||"unnamed mesh"}`)}t instanceof n&&t.material&&function(e){let t=e.material;if(null==t)return;Array.isArray(t)?e.material=t.map(H):e.material=H(t)}(t),M(t)?t.visible=!1:t instanceof n&&t.geometry instanceof a&&(t.geometry.hasAttribute("normal")||t.geometry.computeVertexNormals())}),this._retrievedMeshes.push(t.scene),t.scene=function(e,t){let r=!1;if(t.traverseVisible(e=>{J.test(e.name)&&(r=!0)}),!r)return t;const a=[],i=[t];for(;i.length>0;){const e=i.shift(),t=e.name.match(J);if(null!=t){const r=parseInt(t[1]);if(0!==r){console.warn(`Skipping LOD level ${r} for now as LOD is not fully supported`),a.push(e);continue}}i.push(...e.children)}for(const e of a)e.removeFromParent();return t}(0,t.scene),function(e){const t=V;V.length=0,e.traverse(e=>{(e instanceof b.Camera||e instanceof b.Light)&&t.push(e)}),t.forEach(e=>e.removeFromParent())}(t.scene),t.scene.userData.assetId=e.id,t))}optimizeDrawGroups(e){if(this.optimizedMeshes.has(e.uuid))return;this.optimizedMeshes.add(e.uuid),e.updateWorldMatrix(!0,!0);const t=[];e.traverse(e=>{e instanceof n&&!(e instanceof b.SkinnedMesh)&&e.geometry instanceof a&&Array.isArray(e.material)&&!M(e)&&t.push({mesh:e,geometry:e.geometry,materialArray:e.material,parent:e.parent})});for(const{mesh:r,geometry:a,materialArray:i,parent:s}of t){r.removeFromParent();for(let t=0;t<i.length;t++){const o=i[t],l=a.groups.filter(e=>e.materialIndex===t);if(0===l.length)continue;const c=this.extractGeometryGroups(a,l);c.groups.length>1&&S.mergeGroups(c);const h=new n(c,o);h.copy(r,!1),h.geometry=c,h.material=o,s?s.add(h):e.add(h)}a.dispose()}f(e,M);let r=!0,i=0,s=0;if(e.traverse(e=>{if(e instanceof n&&e.geometry instanceof a&&!M(e)){i++;const t=Object.keys(e.geometry.attributes).length;t!==s&&0!==s&&(r=!1),s=t}else(e instanceof b.SkinnedMesh||e instanceof b.Bone)&&(r=!1)}),i>1&&r){const t=new Map;e.traverse(r=>{if(r instanceof n&&r.geometry instanceof a&&!M(r)){if(Array.isArray(r.material))return;r.updateWorldMatrix(!0,!0);const a=r.material?.uuid||"default";t.has(a)||t.set(a,{material:r.material,geometries:[],objects:[]});const i=t.get(a),s=e.matrixWorld.clone().invert().multiply(r.matrixWorld);i.geometries.push(r.geometry.clone().applyMatrix4(s)),i.objects.push(r)}});for(const e of t.values())for(const t of e.objects)t.removeFromParent();for(const r of t.values()){let t,a=r.geometries;if(a.some(e=>null!=e.index)&&(a=a.map(e=>{if(null!=e.index)return e;const t=e.getAttribute("position");if(null==t)return e;const r=t.count>65535?new Uint32Array(t.count):new Uint16Array(t.count);for(let e=0;e<r.length;e++)r[e]=e;return e.setIndex(new b.BufferAttribute(r,1)),e})),t=1===r.geometries.length?a[0]:E(a)?S.mergeGeometries(a,!0):null,null!=t)t.groups.length>1&&S.mergeGroups(t),e.add(new n(t,r.material));else for(const t of a)e.add(new n(t,r.material))}}e.traverse(e=>{if(e instanceof n&&e.geometry instanceof a&&!M(e)){const t=e.geometry;!Array.isArray(e.material)&&t.groups.length>1&&S.mergeGroups(t)}})}extractGeometryGroups(e,t){const r=e.index;if(!r){const r=new a;let i=0;for(const e of t)i+=e.count;if(0===i)return r;for(const a in e.attributes){const s=e.attributes[a],n=s.itemSize,o=s.array,l=new(0,o.constructor)(i*n);let c=0;for(const e of t){const t=e.start*n,r=e.count*n;for(let e=0;e<r;e++)l[c+e]=o[t+e];c+=r}const h=new b.BufferAttribute(l,n,s.normalized);r.setAttribute(a,h)}return r.groups=[{start:0,count:i,materialIndex:0}],r}const i=[];for(const e of t)for(let t=0;t<e.count;t++){const a=r.getX(e.start+t);i.push(a)}if(0===i.length)return new a;const s=new Map,n=[];let o=0;for(const e of i)s.has(e)||(s.set(e,o),o++),n.push(s.get(e));if(0===n.length||0===s.size)return new a;const l=new a;for(const t in e.attributes){const r=e.attributes[t],a=r.itemSize,i=r.array,n=new(0,i.constructor)(s.size*a);for(const[e,t]of s)for(let r=0;r<a;r++)n[t*a+r]=i[e*a+r];const o=new b.BufferAttribute(n,a,r.normalized);l.setAttribute(t,o)}const c=new((n.length>0?Math.max(...n):0)>65535?Uint32Array:Uint16Array)(n.length);for(let e=0;e<n.length;e++)c[e]=n[e];return l.setIndex(new b.BufferAttribute(c,1)),l.groups=[{start:0,count:n.length,materialIndex:0}],l}async loadByAsset(e){this.fbxLoader;const t=this.getUri(e.fileKey);switch(e.fileFormat){case"glb":case"gltf":return this.glbLoader.loadAsync(t).then(t=>{const r=!0===e.originalFileKey?.toLowerCase().endsWith(".fbx")&&1===t.scene.children.length?t.scene.children[0]:t.scene;return r!==t.scene&&t.scene.remove(r),{scene:r,animations:t.animations}});case"fbx":if(this.directFileLoading){const e=await fetch(t);if(!e.ok)throw new Error(`Failed to load FBX ${t}: ${e.status}`);const r=await e.arrayBuffer(),a=this.fbxLoader.parse(r,b.LoaderUtils.extractUrlBase(t));return{scene:a,animations:a.animations}}return this.fbxLoader.loadAsync(t).then(e=>({scene:e,animations:e.animations}));case"obj":if(null!=e.materialLib){const t=new u;t.materialOptions={normalizeRGB:!1};const r=await t.loadAsync(this.getUri(e.materialLib));this.objLoader.setMaterials(r)}return this.objLoader.loadAsync(t).then(e=>($(e),e)).then(e=>({scene:e,animations:e.animations}))}}disposeAll(){this.cache.clear(),this._retrievedMeshes.forEach(e=>P(e)),this._retrievedTextures.forEach(e=>e.dispose()),this._retrievedMeshes.length=0,this._retrievedTextures.length=0}}const G=new Map;function z(e){G.set(e,(G.get(e)??0)+1)}function E(e){if(e.length<2)return!0;const t=e[0],r=Object.keys(t.attributes).sort(),a=Object.keys(t.morphAttributes).sort();return e.slice(1).every(e=>{if(null==e.index!=(null==t.index))return!1;if(e.morphTargetsRelative!==t.morphTargetsRelative)return!1;const i=Object.keys(e.attributes).sort(),s=Object.keys(e.morphAttributes).sort();if(i.join("|")!==r.join("|"))return!1;if(s.join("|")!==a.join("|"))return!1;for(const a of r){const r=t.getAttribute(a),i=e.getAttribute(a);if(r.itemSize!==i.itemSize||r.normalized!==i.normalized||r.array.constructor!==i.array.constructor)return!1}for(const r of a){const a=t.morphAttributes[r],i=e.morphAttributes[r];if(a.length!==i.length)return!1;for(let e=0;e<a.length;e++)if(a[e].itemSize!==i[e].itemSize||a[e].normalized!==i[e].normalized||a[e].array.constructor!==i[e].array.constructor)return!1}return!0})}function $(e){if(e instanceof n)for(const t of A(e.material))t instanceof b.MeshPhongMaterial&&!t.color.isLinear&&(t.color.isLinear=!0);e.children?.forEach($)}export class AssetMeshInstance extends o{}export{createAuthoredCollisionShapes}from"./collision/authored-colliders.js";export function getElectronArg(e){const t=`--${e}=`,r=window.process?.argv.find(e=>e.startsWith(t));return r?.substring(t.length)}function O(e){switch(e){case"clamp":return b.ClampToEdgeWrapping;case"repeat":return b.RepeatWrapping;case"mirror":return b.MirroredRepeatWrapping}return b.RepeatWrapping}function N(e,t){const r=e;return!0!==r.isCompressedTexture&&!0!==r.isCompressedArrayTexture&&(t.texture?.flipY??!0)}function q(e){return e.texture?.colorSpace??function(e){if("exr"===e.fileFormat||e.fileKey?.toLowerCase().endsWith(".exr"))return"linear";const t=`${e.name} ${e.fileKey??""}`.toLowerCase();if(Y.some(e=>t.includes(e)))return"linear";return"srgb"}(e)}function X(e){return"linear"===e?b.LinearSRGBColorSpace:b.SRGBColorSpace}const Y=["_normal","_nrm","_nor","_norm","_roughness","_rough","_metalness","_metallic","_metal","_ao","_ambient","_occlusion","_mask","_height","_disp","_displacement","_bump","_opacity"];new b.Matrix4;const V=[];const J=/_LOD(\d+)/;export function removeInvalidFaces(e){const t=e.getAttribute("position");if(null==t||t.itemSize<3)return 0;const r=e.index,a=r?.count??t.count,i=Math.floor(a/3),s=[],n=new Uint32Array(i+1),o=new h,l=new h,c=new h;let u=0;for(let e=0;e<i;e++){const a=3*e,i=r?.getX(a)??a,h=r?.getX(a+1)??a+1,m=r?.getX(a+2)??a+2;let d=Number.isInteger(i)&&Number.isInteger(h)&&Number.isInteger(m)&&i>=0&&h>=0&&m>=0&&i<t.count&&h<t.count&&m<t.count;if(d){o.fromBufferAttribute(t,i),l.fromBufferAttribute(t,h).sub(o),c.fromBufferAttribute(t,m).sub(o);const e=Math.max(l.lengthSq(),c.lengthSq(),l.distanceToSquared(c)),r=l.cross(c).lengthSq();d=Number.isFinite(e)&&Number.isFinite(r)&&e>0&&r>Number.EPSILON*e*e}d?s.push(i,h,m):u++,n[e+1]=s.length}if(0===u)return 0;if(e.setIndex(s),e.groups.length>0){const t=e.groups.slice();e.clearGroups();for(const r of t){const t=Math.max(0,Math.ceil(r.start/3)),a=Math.min(i,Math.floor((r.start+r.count)/3));if(a<=t)continue;const s=n[t],o=n[a]-s;o>0&&e.addGroup(s,o,r.materialIndex)}}const m=Math.max(0,Math.ceil(e.drawRange.start/3)),d=Number.isFinite(e.drawRange.count)?Math.min(i,Math.floor((e.drawRange.start+e.drawRange.count)/3)):i,p=Math.min(m,i),f=Math.min(i,Math.max(p,d)),g=n[p];return e.setDrawRange(g,n[f]-g),u}function H(e){let t=e instanceof b.MeshPhysicalMaterial&&0===e.sheen&&0===e.anisotropy;if("MeshLambertMaterial"===e.type||"MeshPhongMaterial"===e.type||t){let t={};(e instanceof b.MeshLambertMaterial||e instanceof b.MeshPhongMaterial||e instanceof b.MeshPhysicalMaterial)&&(t.color=e.color,t.map=e.map,t.bumpMap=e.bumpMap,t.bumpScale=e.bumpScale,t.lightMap=e.lightMap,t.lightMapIntensity=e.lightMapIntensity,t.aoMap=e.aoMap,t.aoMapIntensity=e.aoMapIntensity,t.emissiveMap=e.emissiveMap,t.alphaMap=e.alphaMap,t.envMap=e.envMap,t.envMapRotation=e.envMapRotation,t.wireframe=e.wireframe,t.wireframeLinewidth=e.wireframeLinewidth,t.fog=e.fog,t.opacity=e.opacity,t.transparent=e.transparent,t.side=e.side,t.depthTest=e.depthTest,t.depthWrite=e.depthWrite,t.alphaTest=e.alphaTest,t.displacementMap=e.displacementMap,t.displacementScale=e.displacementScale,t.displacementBias=e.displacementBias);const r=new b.MeshStandardMaterial(t);return r.userData={...e.userData??{}},r.name=e.name,r}return e}/*
|
|
1
|
+
import{createAuthoredCollisionShapes as e}from"./collision/authored-colliders.js";import{parseAnimationClips as t}from"./animation-binary.js";import{AudioLoader as r,BufferGeometry as i,Group as a,LoadingManager as s,Mesh as n,Object3D as o,Texture as l,TextureLoader as c,Vector3 as h}from"three";import{MTLLoader as u,OBJLoader as m,LUTCubeLoader as d}from"three-stdlib";import{cloneMesh as p,mergeCompatibleSkinnedMeshes as f}from"../utils/mesh.js";import{pathJoin as g}from"../utils/files.js";import{Subject as y,firstValueFrom as x}from"rxjs";import{importCollisionShapes as w,isCollisionMesh as M}from"./collision/collision-shape-import.js";import*as b from"three";import{iterateMaterials as A}from"../utils/materials.js";import{applyMaterial as T,getConvertedFbxToGlbAssignedMaterialTextureFlipY as L}from"./materializer.js";import{BufferGeometryUtils as v,DRACOLoader as S,EXRLoader as _,FBXLoader as R,GLTFLoader as K,KTX2Loader as B,TGALoader as C,UltraHDRLoader as F}from"three/examples/jsm/Addons.js";import{disposeScene as I}from"../utils/three/cleanup.js";import{resolveAnimationRigBasisRotation as P,setAnimationClipRigId as k}from"../gameplay/animation/retarget.js";import{parseNativeTexture as D,selectNativeTexture as j}from"./native-texture.js";(()=>{const e=new Uint8Array([255,0,0,255]),t=new b.DataTexture(e,1,1,b.RGBAFormat);t.needsUpdate=!0})();const W=new S;W.setDecoderConfig({type:"js"}),W.setDecoderPath("/assets/draco/");const U=["glb","gltf","fbx","obj"];export class AssetResourceLoader{onError(e){console.error(e)}constructor(){this.cache=new Map,this.textureCache=new Map,this.loadingManager=new s,this.glbLoader=new K(this.loadingManager).setDRACOLoader(W),this.fbxLoader=new R(this.loadingManager),this.objLoader=new m(this.loadingManager),this.textureLoader=new c(this.loadingManager),this.tgaLoader=new C(this.loadingManager),this.exrLoader=new _(this.loadingManager),this.cubeLoader=new d(this.loadingManager),this.hdrLoader=new F(this.loadingManager),this.ktx2Loader=new B(this.loadingManager),this.ktx2Ready=Promise.resolve(),this.nativeTextureLoader=new b.FileLoader(this.loadingManager).setResponseType("arraybuffer"),this.nativeBcSupported=!1,this.nativeAstcSupported=!1,this._textureLoader=new b.ImageBitmapLoader(this.loadingManager),this.audioLoader=new r(this.loadingManager),this.initialisedKtx2=!1,this._retrievedMeshes=[],this._retrievedTextures=[],this.makeReady=new y,this.ready=x(this.makeReady),this.directFileLoading=!1,this.imageTextureOrientations=new WeakMap,this._texturePromises=new Map,this._arrayTexturesByFileKey=new Map,this._arrayTexturePromisesByFileKey=new Map,this._arrayTextureSettingsByFileKey=new Map,this._animationPromises=new Map,this._animationCache=new Map,this.asyncMeshResults=new Map,this.asyncAudioResults=new Map,this.clipsRequests=new Set,this.timeToLoadClips=0,this.collisionShapeCache=new Map,this.optimizedMeshes=new Set}setDirectFileLoading(e){this.directFileLoading=e}setImageBitmapDecoder(e){this.imageBitmapDecoder=e}setImageTextureDecoder(e){this.imageTextureDecoder=e}decodeImageBitmap(e){return this.imageBitmapDecoder?.(e)}configureKtx2(){this.initialisedKtx2=!0;null!=getElectronArg("windowId")||import.meta.url.includes(".vite")||import.meta.url.includes("hology/packages/core")?this.ktx2Loader.setTranscoderPath("/assets/basis/"):this.ktx2Loader.setTranscoderPath(new URL("../assets/basis/",import.meta.url).href),this.ktx2Loader.setWorkerLimit(6)}initKtx2(e){this.configureKtx2(),this.ktx2Loader.detectSupport(e),this.nativeBcSupported=e.extensions.has("EXT_texture_compression_bptc"),this.nativeAstcSupported=e.extensions.has("WEBGL_compressed_texture_astc")}initKtx2ForWebGpu(e){this.configureKtx2(),this.ktx2Ready=Promise.resolve(e??navigator.gpu?.requestAdapter({powerPreference:"high-performance"})??null).then(e=>{this.nativeBcSupported=!0===e?.features.has("texture-compression-bc"),this.nativeAstcSupported=!0===e?.features.has("texture-compression-astc"),this.ktx2Loader.detectSupport({isWebGPURenderer:!0,hasFeature:t=>"texture-compression-bptc"===t?this.nativeBcSupported:!0===e?.features.has(t)})})}setDataDir(e){this.basePath=g(e,"asset-resources"),this.makeReady.next(!0)}getUri(e){if(e.startsWith("asset://"))return e;const t=function(e){return G.get(e)}(e),r=getElectronArg("windowId");return null!=r||import.meta.url.includes(".vite")||import.meta.url.includes("hology/packages/core")?g(this.basePath,e)+(null!=r?`?windowId=${r}${null!=t?`&v=${t}`:""}`:""):new URL(g("..",this.basePath,e),import.meta.url).href}async getTexture(e){if(!e||!e.fileKey)return null;if("png"===e.fileFormat||e.fileFormat,this._texturePromises.has(e.id))return this._texturePromises.get(e.id);const t=this._getTexture(e).finally(()=>{this._texturePromises.delete(e.id)});return this._texturePromises.set(e.id,t),t}async _getTexture(e){if(null==e||null==e.fileKey)return null;await this.ready,e.fileKey.toLowerCase().endsWith(".ktx2")&&await this.ktx2Ready;const t=e.texture?.flipY??!0,r=this.textureCache.get(e.id);if(r&&this.imageTextureOrientations.has(r)&&this.imageTextureOrientations.get(r)!==t&&this.textureCache.delete(e.id),this.textureCache.has(e.id)){const t=this.textureCache.get(e.id);if(null!=e.texture){const r=!this.imageTextureOrientations.has(t)&&N(t,e);t.flipY!==r?(t.needsUpdate=!0,t.flipY=r):t.wrapS!==O(e.texture?.wrapS)?(t.needsUpdate=!0,t.wrapS=O(e.texture?.wrapS)):t.wrapT!==O(e.texture?.wrapT)&&(t.wrapT=O(e.texture?.wrapT),t.needsUpdate=!0)}const r=Y(q(e));t.colorSpace!==r&&(t.colorSpace=r,t.needsUpdate=!0)}else try{const r=this._getTextureLoader(e.fileKey),i=(null!=this.imageTextureDecoder||null!=this.imageBitmapDecoder)&&/\.(png|jpe?g)$/i.test(e.fileKey),a=i||this.directFileLoading&&r===this.textureLoader,s=this.getUri(e.fileKey),n=i?(this.imageTextureDecoder?.(s,t)??this.imageBitmapDecoder(s)).catch(t=>(console.warn(`Worker image decode failed for ${e.name}; falling back to ImageBitmapLoader`,t),this._textureLoader.loadAsync(s))):e.texture?.nativeFileKeys&&r===this.ktx2Loader?this.loadKtx2Texture(e.fileKey,e.texture.nativeFileKeys):(a?this._textureLoader:r).loadAsync(s);await n.then(r=>(i&&this.imageTextureDecoder&&r.isTexture&&this.imageTextureOrientations.set(r,t),a&&!r.isTexture&&((r=new l(r)).needsUpdate=!0),r.texture3D?r=r.texture3D:(r.wrapS=O(e.texture?.wrapS),r.wrapT=O(e.texture?.wrapT),r.flipY=!this.imageTextureOrientations.has(r)&&N(r,e),r.colorSpace=Y(q(e))),this.textureCache.set(e.id,r),this._retrievedTextures.push(r),r))}catch(t){return console.warn("Failed to load texture "+e.name,t),null}const i=this.textureCache.get(e.id);return null!=i&&(i.userData.assetId=e.id),i}async getTextureArray(e){const t=e.texture?.textureArrayFileKey,r=e.texture?.textureArrayLayer;if(!t||null==r)return{texture:null,layerIndex:null};let i=this._arrayTexturesByFileKey.get(t);if(null==i){let r=this._arrayTexturePromisesByFileKey.get(t);null==r&&(r=this.loadTextureArray(t,e.texture?.nativeArrayFileKeys).finally(()=>{this._arrayTexturePromisesByFileKey.delete(t)}),this._arrayTexturePromisesByFileKey.set(t,r)),i=await r}return this.applyTextureArraySettings(t,i,e),{texture:i,layerIndex:r}}async loadKtx2Texture(e,t){await this.ktx2Ready;const r=j(t,this.nativeBcSupported,this.nativeAstcSupported);if(r)try{const e=await this.nativeTextureLoader.loadAsync(this.getUri(r.fileKey));return D(e,r.format)}catch(e){console.warn(`Failed to load native texture ${r.fileKey}; using portable KTX2`,e)}return this._getTextureLoader(e).loadAsync(this.getUri(e))}async loadTextureArray(e,t){let r=await this.loadKtx2Texture(e,t);return r instanceof l||(r=r.texture3D),this._arrayTexturesByFileKey.set(e,r),this._retrievedTextures.push(r),r}applyTextureArraySettings(e,t,r){const i=function(e){return{wrapS:O(e.texture?.wrapS),wrapT:O(e.texture?.wrapT)}}(r),a=this._arrayTextureSettingsByFileKey.get(e);if(null!=a&&(n=i,(s=a).wrapS!==n.wrapS||s.wrapT!==n.wrapT))return void console.warn(`Texture array ${e} is used with incompatible texture settings. Texture arrays share wrapping; rebuild arrays so incompatible textures are grouped separately.`);var s,n;null==a&&this._arrayTextureSettingsByFileKey.set(e,i);let o=!1;t.wrapS!==i.wrapS&&(t.wrapS=i.wrapS,o=!0),t.wrapT!==i.wrapT&&(t.wrapT=i.wrapT,o=!0),!1!==t.flipY&&(t.flipY=!1,o=!0),o&&(t.needsUpdate=!0)}_getTextureLoader(e){return e?.toLowerCase().endsWith(".tga")?this.tgaLoader:e?.toLowerCase().endsWith(".ktx2")?this.ktx2Loader:e?.toLowerCase().endsWith(".exr")?this.exrLoader:e?.toLowerCase().endsWith(".cube")?this.cubeLoader:this.textureLoader}clearCache(e){this.clearCacheByAsset(e)}clearCacheByAsset(e){null!=e&&(this.clearCacheById(e.id),null!=e.fileKey&&(Array.from(this.cache.keys()).forEach(t=>{t.startsWith(e.fileKey)&&this.cache.delete(t)}),Array.from(this.asyncMeshResults.keys()).forEach(t=>{t.startsWith(e.fileKey)&&this.asyncMeshResults.delete(t)}),Array.from(this._animationCache.keys()).forEach(t=>{t.startsWith(e.fileKey+"|")&&this._animationCache.delete(t)}),Array.from(this._animationPromises.keys()).forEach(t=>{t.startsWith(e.fileKey+"|")&&this._animationPromises.delete(t)}),z(e.fileKey)),null!=e.materialLib&&z(e.materialLib))}clearCacheById(e){const t=Array.from(this.cache.values()).find(t=>t.scene.userData.assetId===e);t&&Array.from(this.cache.keys()).forEach(e=>{this.cache.get(e)===t&&this.cache.delete(e)}),this.textureCache.delete(e),this._texturePromises.delete(e),Array.from(this.collisionShapeCache.keys()).forEach(t=>{t.startsWith(e+"|")&&this.collisionShapeCache.delete(t)})}async getMesh(e,t){if(await this.ready,null==e)return console.error("No asset was provided"),{scene:new a,animations:[]};if(!U.includes(e.fileFormat?.toLowerCase()))return console.error("Unsupported mesh file format "+e.fileFormat,e),{scene:new a,animations:[]};const r=e.fileKey+(!0===t?.mergeGeomtries?"1":"0");if(!this.cache.has(r))try{this.asyncMeshResults.has(r)||this.asyncMeshResults.set(r,this.loadMesh(e).finally(()=>this.asyncMeshResults.delete(r))),this.cache.set(r,await this.asyncMeshResults.get(r))}catch(e){return this.onError(e),{scene:new a,animations:[]}}const i=this.cache.get(r),s=i.scene,o=i.animations;if(!1===t?.clone)return{scene:s,animations:o};const l=this.computeCollisionShapes(e,s),c=(e.mesh?.secondaryMotion?.regions.length??0)>0;!0!==t?.mergeGeomtries||c||this.optimizeDrawGroups(s);const h=p(s);h.traverse(e=>{e instanceof n&&e.material instanceof Array&&(e.material=e.material.slice())});const u=new AssetMeshInstance;u.add(h),u.collisionShapes=l,u.animations=o,u.userData.assetId=e.id,u.userData.secondaryMotion=e.mesh?.secondaryMotion,function(e){const t=e.children.length>0?e.children:[e];for(let e=0;e<t.length;e++)r(t[e],"",e);function r(e,t,i){const a=e instanceof n?"Mesh":"Group",s=`${t}/${e.name?.trim()?.length>0?e.name:a}[${i}]`;e.userData.secondaryMotionMeshPath=s;for(let t=0;t<e.children.length;t++)r(e.children[t],s,t)}}(u);const m=e.mesh?.animationRigId??e.anim?.rigId;if(u.userData.animationRigId=m,h.userData.animationRigId=m,null!=m&&null!=this.animationRigProvider){const e=await this.animationRigProvider(m);u.userData.animationRig=e,h.userData.animationRig=e,null!=e&&!1!==t?.applyRigBasis&&u.quaternion.copy(P(e))}const d=e.receiveShadow??!1,f=e.castShadow??!1;if(h.traverse(e=>{e.castShadow=f,e.receiveShadow=d}),!0===t?.rescale&&"number"==typeof e.mesh?.rescale&&u.scale.setScalar(e.mesh?.rescale),!0===t?.applyMaterials){const r=t?.materialResolver??this.materialProvider;if(null!=r)for(const t of e.materialAssignments??[])await T(u,t,r,void 0,{textureFlipY:L(e)});else console.warn(`applyMaterials was set to true for ${e.name} but no material resolver was provided.`)}return{scene:u,animations:o}}async getAudio(e){await this.ready;const t=e.fileKey;if(this.asyncAudioResults.has(t))return await this.asyncAudioResults.get(t);const r=this.audioLoader.loadAsync(this.getUri(e.fileKey)).finally(()=>{setTimeout(()=>{this.asyncAudioResults.delete(t)},1e3)});return this.asyncAudioResults.set(t,r),r}async getAnimationClip(e){if(null==e||null==e.fileKey||null==e.anim?.clip)return console.warn("Asset or animation clip name is not defined",e),null;const r=e.anim.clip,i=e.fileKey,a=i+"|"+r;if(this._animationCache.has(a))return this._animationCache.get(a);if(this._animationPromises.has(a))return this._animationPromises.get(a);this.clipsRequests.add(e.id);const s=(async()=>{try{const s=("anim"===e.fileFormat?t(await new b.FileLoader(this.loadingManager).setResponseType("arraybuffer").loadAsync(this.getUri(i))):(await this.getMesh(e,{mergeGeomtries:!1,clone:!1})).animations).find(e=>e.name===r);return null!=s&&(k(s,e.anim?.rigId),this._animationCache.set(a,s)),s}catch(e){return this.onError(e),null}finally{setTimeout(()=>this._animationPromises.delete(a),1e3)}})();return this._animationPromises.set(a,s),s}computeCollisionShapes(t,r){if(!0!==t.collisionDetection)return[];const i=t.id+"|"+(t.mesh?.collisions?.shapeType??"")+"|"+(t.mesh?.rescale??1)+"|"+JSON.stringify(t.mesh?.colliders??[]);if(!this.collisionShapeCache.has(i)){const a=t.mesh?.colliders??[],s=a.length>0?e(a,t.mesh?.rescale):w(r,t);this.collisionShapeCache.set(i,s)}return this.collisionShapeCache.get(i)}async loadMesh(e){return await this.ready,await this.loadByAsset(e).then(t=>(t.scene.traverse(t=>{if(t instanceof n&&t.geometry instanceof i){const r=removeInvalidFaces(t.geometry);r>0&&console.warn(`Removed ${r} invalid face${1===r?"":"s"} from ${e.name}/${t.name||"unnamed mesh"}`)}t instanceof n&&t.material&&function(e){let t=e.material;if(null==t)return;Array.isArray(t)?e.material=t.map(H):e.material=H(t)}(t),M(t)?t.visible=!1:t instanceof n&&t.geometry instanceof i&&(t.geometry.hasAttribute("normal")||t.geometry.computeVertexNormals())}),this._retrievedMeshes.push(t.scene),t.scene=function(e,t){let r=!1;if(t.traverseVisible(e=>{J.test(e.name)&&(r=!0)}),!r)return t;const i=[],a=[t];for(;a.length>0;){const e=a.shift(),t=e.name.match(J);if(null!=t){const r=parseInt(t[1]);if(0!==r){console.warn(`Skipping LOD level ${r} for now as LOD is not fully supported`),i.push(e);continue}}a.push(...e.children)}for(const e of i)e.removeFromParent();return t}(0,t.scene),function(e){const t=V;V.length=0,e.traverse(e=>{(e instanceof b.Camera||e instanceof b.Light)&&t.push(e)}),t.forEach(e=>e.removeFromParent())}(t.scene),t.scene.userData.assetId=e.id,t))}optimizeDrawGroups(e){if(this.optimizedMeshes.has(e.uuid))return;this.optimizedMeshes.add(e.uuid),e.updateWorldMatrix(!0,!0);const t=[];e.traverse(e=>{e instanceof n&&!(e instanceof b.SkinnedMesh)&&e.geometry instanceof i&&Array.isArray(e.material)&&!M(e)&&t.push({mesh:e,geometry:e.geometry,materialArray:e.material,parent:e.parent})});for(const{mesh:r,geometry:i,materialArray:a,parent:s}of t){r.removeFromParent();for(let t=0;t<a.length;t++){const o=a[t],l=i.groups.filter(e=>e.materialIndex===t);if(0===l.length)continue;const c=this.extractGeometryGroups(i,l);c.groups.length>1&&v.mergeGroups(c);const h=new n(c,o);h.copy(r,!1),h.geometry=c,h.material=o,s?s.add(h):e.add(h)}i.dispose()}f(e,M);let r=!0,a=0,s=0;if(e.traverse(e=>{if(e instanceof n&&e.geometry instanceof i&&!M(e)){a++;const t=Object.keys(e.geometry.attributes).length;t!==s&&0!==s&&(r=!1),s=t}else(e instanceof b.SkinnedMesh||e instanceof b.Bone)&&(r=!1)}),a>1&&r){const t=new Map;e.traverse(r=>{if(r instanceof n&&r.geometry instanceof i&&!M(r)){if(Array.isArray(r.material))return;r.updateWorldMatrix(!0,!0);const i=r.material?.uuid||"default";t.has(i)||t.set(i,{material:r.material,geometries:[],objects:[]});const a=t.get(i),s=e.matrixWorld.clone().invert().multiply(r.matrixWorld);a.geometries.push(r.geometry.clone().applyMatrix4(s)),a.objects.push(r)}});for(const e of t.values())for(const t of e.objects)t.removeFromParent();for(const r of t.values()){let t,i=r.geometries;if(i.some(e=>null!=e.index)&&(i=i.map(e=>{if(null!=e.index)return e;const t=e.getAttribute("position");if(null==t)return e;const r=t.count>65535?new Uint32Array(t.count):new Uint16Array(t.count);for(let e=0;e<r.length;e++)r[e]=e;return e.setIndex(new b.BufferAttribute(r,1)),e})),t=1===r.geometries.length?i[0]:E(i)?v.mergeGeometries(i,!0):null,null!=t)t.groups.length>1&&v.mergeGroups(t),e.add(new n(t,r.material));else for(const t of i)e.add(new n(t,r.material))}}e.traverse(e=>{if(e instanceof n&&e.geometry instanceof i&&!M(e)){const t=e.geometry;!Array.isArray(e.material)&&t.groups.length>1&&v.mergeGroups(t)}})}extractGeometryGroups(e,t){const r=e.index;if(!r){const r=new i;let a=0;for(const e of t)a+=e.count;if(0===a)return r;for(const i in e.attributes){const s=e.attributes[i],n=s.itemSize,o=s.array,l=new(0,o.constructor)(a*n);let c=0;for(const e of t){const t=e.start*n,r=e.count*n;for(let e=0;e<r;e++)l[c+e]=o[t+e];c+=r}const h=new b.BufferAttribute(l,n,s.normalized);r.setAttribute(i,h)}return r.groups=[{start:0,count:a,materialIndex:0}],r}const a=[];for(const e of t)for(let t=0;t<e.count;t++){const i=r.getX(e.start+t);a.push(i)}if(0===a.length)return new i;const s=new Map,n=[];let o=0;for(const e of a)s.has(e)||(s.set(e,o),o++),n.push(s.get(e));if(0===n.length||0===s.size)return new i;const l=new i;for(const t in e.attributes){const r=e.attributes[t],i=r.itemSize,a=r.array,n=new(0,a.constructor)(s.size*i);for(const[e,t]of s)for(let r=0;r<i;r++)n[t*i+r]=a[e*i+r];const o=new b.BufferAttribute(n,i,r.normalized);l.setAttribute(t,o)}const c=new((n.length>0?Math.max(...n):0)>65535?Uint32Array:Uint16Array)(n.length);for(let e=0;e<n.length;e++)c[e]=n[e];return l.setIndex(new b.BufferAttribute(c,1)),l.groups=[{start:0,count:n.length,materialIndex:0}],l}async loadByAsset(e){this.fbxLoader;const t=this.getUri(e.fileKey);switch(e.fileFormat){case"glb":case"gltf":return this.glbLoader.loadAsync(t).then(t=>{const r=!0===e.originalFileKey?.toLowerCase().endsWith(".fbx")&&1===t.scene.children.length?t.scene.children[0]:t.scene;return r!==t.scene&&t.scene.remove(r),{scene:r,animations:t.animations}});case"fbx":if(this.directFileLoading){const e=await fetch(t);if(!e.ok)throw new Error(`Failed to load FBX ${t}: ${e.status}`);const r=await e.arrayBuffer(),i=this.fbxLoader.parse(r,b.LoaderUtils.extractUrlBase(t));return{scene:i,animations:i.animations}}return this.fbxLoader.loadAsync(t).then(e=>({scene:e,animations:e.animations}));case"obj":if(null!=e.materialLib){const t=new u;t.materialOptions={normalizeRGB:!1};const r=await t.loadAsync(this.getUri(e.materialLib));this.objLoader.setMaterials(r)}return this.objLoader.loadAsync(t).then(e=>($(e),e)).then(e=>({scene:e,animations:e.animations}))}}disposeAll(){this.cache.clear(),this._retrievedMeshes.forEach(e=>I(e)),this._retrievedTextures.forEach(e=>e.dispose()),this._retrievedMeshes.length=0,this._retrievedTextures.length=0}}const G=new Map;function z(e){G.set(e,(G.get(e)??0)+1)}function E(e){if(e.length<2)return!0;const t=e[0],r=Object.keys(t.attributes).sort(),i=Object.keys(t.morphAttributes).sort();return e.slice(1).every(e=>{if(null==e.index!=(null==t.index))return!1;if(e.morphTargetsRelative!==t.morphTargetsRelative)return!1;const a=Object.keys(e.attributes).sort(),s=Object.keys(e.morphAttributes).sort();if(a.join("|")!==r.join("|"))return!1;if(s.join("|")!==i.join("|"))return!1;for(const i of r){const r=t.getAttribute(i),a=e.getAttribute(i);if(r.itemSize!==a.itemSize||r.normalized!==a.normalized||r.array.constructor!==a.array.constructor)return!1}for(const r of i){const i=t.morphAttributes[r],a=e.morphAttributes[r];if(i.length!==a.length)return!1;for(let e=0;e<i.length;e++)if(i[e].itemSize!==a[e].itemSize||i[e].normalized!==a[e].normalized||i[e].array.constructor!==a[e].array.constructor)return!1}return!0})}function $(e){if(e instanceof n)for(const t of A(e.material))t instanceof b.MeshPhongMaterial&&!t.color.isLinear&&(t.color.isLinear=!0);e.children?.forEach($)}export class AssetMeshInstance extends o{}export{createAuthoredCollisionShapes}from"./collision/authored-colliders.js";export function getElectronArg(e){const t=`--${e}=`,r=window.process?.argv.find(e=>e.startsWith(t));return r?.substring(t.length)}function O(e){switch(e){case"clamp":return b.ClampToEdgeWrapping;case"repeat":return b.RepeatWrapping;case"mirror":return b.MirroredRepeatWrapping}return b.RepeatWrapping}function N(e,t){const r=e;return!0!==r.isCompressedTexture&&!0!==r.isCompressedArrayTexture&&(t.texture?.flipY??!0)}function q(e){return e.texture?.colorSpace??function(e){if("exr"===e.fileFormat||e.fileKey?.toLowerCase().endsWith(".exr"))return"linear";const t=`${e.name} ${e.fileKey??""}`.toLowerCase();if(X.some(e=>t.includes(e)))return"linear";return"srgb"}(e)}function Y(e){return"linear"===e?b.LinearSRGBColorSpace:b.SRGBColorSpace}const X=["_normal","_nrm","_nor","_norm","_roughness","_rough","_metalness","_metallic","_metal","_ao","_ambient","_occlusion","_mask","_height","_disp","_displacement","_bump","_opacity"];new b.Matrix4;const V=[];const J=/_LOD(\d+)/;export function removeInvalidFaces(e){const t=e.getAttribute("position");if(null==t||t.itemSize<3)return 0;const r=e.index,i=r?.count??t.count,a=Math.floor(i/3),s=[],n=new Uint32Array(a+1),o=new h,l=new h,c=new h;let u=0;for(let e=0;e<a;e++){const i=3*e,a=r?.getX(i)??i,h=r?.getX(i+1)??i+1,m=r?.getX(i+2)??i+2;let d=Number.isInteger(a)&&Number.isInteger(h)&&Number.isInteger(m)&&a>=0&&h>=0&&m>=0&&a<t.count&&h<t.count&&m<t.count;if(d){o.fromBufferAttribute(t,a),l.fromBufferAttribute(t,h).sub(o),c.fromBufferAttribute(t,m).sub(o);const e=Math.max(l.lengthSq(),c.lengthSq(),l.distanceToSquared(c)),r=l.cross(c).lengthSq();d=Number.isFinite(e)&&Number.isFinite(r)&&e>0&&r>Number.EPSILON*e*e}d?s.push(a,h,m):u++,n[e+1]=s.length}if(0===u)return 0;if(e.setIndex(s),e.groups.length>0){const t=e.groups.slice();e.clearGroups();for(const r of t){const t=Math.max(0,Math.ceil(r.start/3)),i=Math.min(a,Math.floor((r.start+r.count)/3));if(i<=t)continue;const s=n[t],o=n[i]-s;o>0&&e.addGroup(s,o,r.materialIndex)}}const m=Math.max(0,Math.ceil(e.drawRange.start/3)),d=Number.isFinite(e.drawRange.count)?Math.min(a,Math.floor((e.drawRange.start+e.drawRange.count)/3)):a,p=Math.min(m,a),f=Math.min(a,Math.max(p,d)),g=n[p];return e.setDrawRange(g,n[f]-g),u}function H(e){let t=e instanceof b.MeshPhysicalMaterial&&0===e.sheen&&0===e.anisotropy;if("MeshLambertMaterial"===e.type||"MeshPhongMaterial"===e.type||t){let t={};(e instanceof b.MeshLambertMaterial||e instanceof b.MeshPhongMaterial||e instanceof b.MeshPhysicalMaterial)&&(t.color=e.color,t.map=e.map,t.bumpMap=e.bumpMap,t.bumpScale=e.bumpScale,t.lightMap=e.lightMap,t.lightMapIntensity=e.lightMapIntensity,t.aoMap=e.aoMap,t.aoMapIntensity=e.aoMapIntensity,t.emissiveMap=e.emissiveMap,t.alphaMap=e.alphaMap,t.envMap=e.envMap,t.envMapRotation=e.envMapRotation,t.wireframe=e.wireframe,t.wireframeLinewidth=e.wireframeLinewidth,t.fog=e.fog,t.opacity=e.opacity,t.transparent=e.transparent,t.side=e.side,t.depthTest=e.depthTest,t.depthWrite=e.depthWrite,t.alphaTest=e.alphaTest,t.displacementMap=e.displacementMap,t.displacementScale=e.displacementScale,t.displacementBias=e.displacementBias);const r=new b.MeshStandardMaterial(t);return r.userData={...e.userData??{}},r.name=e.name,r}return e}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Euler as e,Quaternion as t,Vector3 as n}from"three";import{BoxCollisionShape as o,CapsuleCollisionShape as s,CollisionShapeSource as i,ConvexPolyhedronCollisionShape as r,CylinderCollisionShape as l,SphereCollisionShape as a,TrimeshCollisionShape as m}from"./collision-shape.js";import*as u from"three";import*as c from"three/examples/jsm/utils/BufferGeometryUtils.js";import{calculateOBB as p}from"../../utils/obb-utils.js";const h=new t,f=new n,d=new n(1,1,1),y=new t,
|
|
1
|
+
import{Euler as e,Quaternion as t,Vector3 as n}from"three";import{BoxCollisionShape as o,CapsuleCollisionShape as s,CollisionShapeSource as i,ConvexPolyhedronCollisionShape as r,CylinderCollisionShape as l,SphereCollisionShape as a,TrimeshCollisionShape as m}from"./collision-shape.js";import*as u from"three";import*as c from"three/examples/jsm/utils/BufferGeometryUtils.js";import{calculateOBB as p}from"../../utils/obb-utils.js";const h=new t,f=new n,d=new n(1,1,1),y=new t,g=new t,x=new n,w=new n;export function importCollisionShapes(t,W){let B=W.mesh?.collisions?.shapeType;if(null==B){let e=!1;t.traverse(t=>{t.isMesh&&isCollisionMesh(t)&&(e=!0)}),B=e?"imported":"box"}const C=[];if("mesh"===B){const e=[];if(t.traverse(n=>{if(n.isMesh&&!isCollisionMesh(n)){const o=n,s=new u.BufferGeometry;s.setAttribute("position",o.geometry.getAttribute("position").clone());const i=o.geometry.getIndex();null!=i&&s.setIndex(i.clone()),o.updateMatrixWorld();const r=t.matrixWorld.clone().invert().multiply(o.matrixWorld);s.applyMatrix4(r),e.push(s)}}),e.length>0){const t=c.mergeGeometries(e),n=new m(null!=t.index?t:c.mergeVertices(t));return n.source=i.rendered,C.push(n),C}}const v=t.matrixWorld.clone().invert(),S=new u.Matrix4;return t.traverse(t=>{if(t.isMesh){const u=t;if("imported"===B&&!isCollisionMesh(u))return;let c;u.updateMatrixWorld(),S.copy(v).multiply(u.matrixWorld),S.decompose(f,h,d),"imported"===B?c=function(t){if(t.name.startsWith(b.convex))return new r(t);if(t.name.startsWith(b.box))return M(t.geometry);if(t.name.startsWith(b.sphere)){t.geometry.computeBoundingSphere();const e=t.geometry.boundingSphere;return null==e?null:new a(e.radius)}if(t.name.startsWith(b.capsule)){t.geometry.computeBoundingBox();const e=t.geometry.boundingBox;if(null==e)return null;const o=new n;e.getSize(o);const i=Math.min(o.x,o.z)/2;return new s(o.y-2*i,i)}if(t.name.startsWith(b.cylinder)){t.geometry.computeBoundingBox();const o=t.geometry.boundingBox;if(null==o)return null;const s=new n;return o.getSize(s),new l(s.x/2,s.x/2,s.y,16,new e)}if(t.name.startsWith(b.trimesh))return new m(t.geometry)}(u):"convex"===B?c=new r(u):"box"===B?c=M(u.geometry):"obb"===B&&(c=function(e){const t=p(e),n=new o(t.size);return n.offset.copy(t.center),n.rotation.copy(t.rotation),n}(u.geometry)),null!=c&&(c.source="imported"===B?i.custom:i.rendered,c.source===i.rendered&&(c.instancedOffset=c.offset.clone(),c.instancedRotation=c.rotation.clone(),c instanceof o&&(c.instancedDimensions=c.dimensions.clone())),function(e,t,n,s){e instanceof o&&(w.set(Math.abs(s.x),Math.abs(s.y),Math.abs(s.z)),e.dimensions.multiply(w));x.copy(e.offset).multiply(s).applyQuaternion(n).add(t),e.offset.copy(x),y.setFromEuler(e.rotation),g.copy(n).multiply(y),e.rotation.setFromQuaternion(g)}(c,f,h,d),C.push(c),isCollisionMesh(u)&&(u.visible=!1))}}),C.length>128&&console.warn(`Asset ${W.name} (${W.id}) has ${C.length} collision shapes. Consider setting Collision Shape Type to 'Mesh' to merge them.`),C}export function isCollisionMesh(e){return e.name.startsWith(b.convex)||e.name.startsWith(b.box)||e.name.startsWith(b.sphere)||e.name.startsWith(b.trimesh)||e.name.startsWith(b.capsule)||e.name.startsWith(b.cylinder)}var b;function M(e){e.computeBoundingBox();const t=e.boundingBox;if(null==t)return null;const s=new n,i=new n;t.getSize(s),t.getCenter(i);const r=new o(s);return r.offset.copy(i),r.rotation.set(0,0,0),r}!function(e){e.convex="UCX_",e.box="UBX_",e.sphere="USP_",e.trimesh="UTM_",e.capsule="UCP_",e.cylinder="UCY_"}(b||(b={}));/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as e from"three";import{Vector3 as t}from"three";import
|
|
1
|
+
import*as e from"three";import{Vector3 as t}from"three";import{calculateEffectiveScale as r}from"../../utils/three/traverse.js";import{BoxCollisionShape as o,CapsuleCollisionShape as n,ConeCollisionShape as i,ConvexPolyhedronCollisionShape as s,CylinderCollisionShape as l,PlaneCollisionShape as a,SphereCollisionShape as c,TrimeshCollisionShape as u}from"./collision-shape.js";const f=new WeakMap;export function createRapierCollisionShape(h,y,m){if(y instanceof o)return h.ColliderDesc.cuboid(y.dimensions.x*m.x/2,y.dimensions.y*m.y/2,y.dimensions.z*m.z/2);if(y instanceof n){return h.ColliderDesc.capsule(y.length/2*m.y,y.radius*Math.max(m.z,m.x))}if(y instanceof u){const e=y.geometry,t=e.getAttribute("position"),r=extractFloat32Array(t);for(let e=0;e<r.length;e+=3)r[e]*=m.x,r[e+1]*=m.y,r[e+2]*=m.z;const o=e.getIndex(),n=null!=o?new Uint32Array(o.array):new Uint32Array(t.count);if(null==o)for(let e=0;e<n.length;e++)n[e]=e;return h.ColliderDesc.trimesh(r,n)}if(y instanceof s){let e;const t=y.mesh;if(t.isMesh)e=t.geometry;else{if(!y.mesh.isBufferGeometry)throw new Error("Unsupported convex collision geometry");e=y.mesh}let o;t.isMesh&&(o=r(t));const n=`${o?.x??1},${o?.y??1},${o?.z??1}|${m.x},${m.y},${m.z}`;let i=f.get(e);i||f.set(e,i=new Map);const s=i.get(n);if(void 0!==s)return s;const l=extractFloat32Array(e.getAttribute("position"));if(null!=o)for(let e=0;e<l.length;e+=3)l[e]*=o.x,l[e+1]*=o.y,l[e+2]*=o.z;for(let e=0;e<l.length;e+=3)l[e]*=m.x,l[e+1]*=m.y,l[e+2]*=m.z;const a=h.ColliderDesc.convexHull(l);return null==a&&console.error("Failed to compute convex hull. Points may be coplanar or too few.",{count:l.length/3}),i.set(n,a),a}if(y instanceof c){const r=2*m.x-m.y-m.z;return Math.abs(r)>.01?createRapierCollisionShape(h,new s(new e.SphereGeometry(y.radius).scale(m.x,m.y,m.z)),new t(1,1,1)):h.ColliderDesc.ball(y.radius*Math.max(m.x,m.y,m.z))}return y instanceof l?h.ColliderDesc.cylinder(y.height/2*m.y,y.radiusTop*Math.max(m.z,m.x)):y instanceof i?h.ColliderDesc.cone(y.height*m.y,y.radiusBottom/2*Math.max(m.z,m.x)):y instanceof a?h.ColliderDesc.cuboid(y.width/2*m.x,y.height/2*m.y,.001):(console.error("Unsupported shape",y),h.ColliderDesc.cuboid(1,1,1))}export function extractFloat32Array(e){if(!e.isInterleavedBufferAttribute&&!e.normalized&&e.array instanceof Float32Array)return e.array.slice();const t=e.itemSize,r=e.count,o=new Float32Array(r*t);for(let n=0;n<r;n++)for(let r=0;r<t;r++)o[n*t+r]=e.getComponent(n,r);return o}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PlaneGeometry } from 'three';
|
|
2
|
+
import type { BufferGeometry } from 'three';
|
|
3
|
+
import { MeshBVH } from 'three-mesh-bvh';
|
|
4
|
+
/** Per-manager scratch storage and one immutable spatial grid template. */
|
|
5
|
+
export declare class LandscapeGeometryBuilder {
|
|
6
|
+
private scratch;
|
|
7
|
+
private template;
|
|
8
|
+
private density;
|
|
9
|
+
private sectionSize;
|
|
10
|
+
private grid;
|
|
11
|
+
private readonly templateSections;
|
|
12
|
+
private readonly normal;
|
|
13
|
+
private readonly tangent;
|
|
14
|
+
private readonly bitangent;
|
|
15
|
+
private readonly orthogonal;
|
|
16
|
+
private readonly cross;
|
|
17
|
+
private prepareGrid;
|
|
18
|
+
createGeometry(sectionSize: number, density: number): PlaneGeometry;
|
|
19
|
+
computeTangents(geometry: BufferGeometry): boolean;
|
|
20
|
+
updateBoundsTree(geometry: BufferGeometry & {
|
|
21
|
+
boundsTree?: MeshBVH;
|
|
22
|
+
}, sectionSize: number, density: number, unmodifiedPlane?: boolean): void;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=landscape-geometry.d.ts.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{Box3 as t,BufferAttribute as e,PlaneGeometry as n,Vector3 as r}from"three";import{MeshBVH as s}from"three-mesh-bvh";export class LandscapeGeometryBuilder{constructor(){this.scratch=new Float64Array(0),this.templateSections=new WeakSet,this.normal=new r,this.tangent=new r,this.bitangent=new r,this.orthogonal=new r,this.cross=new r}prepareGrid(t,e){if(null!=this.grid&&this.density===e&&this.sectionSize===t)return;this.grid?.dispose();const r=new n(t,t,e,e);r.rotateX(-Math.PI/2),this.template=s.serialize(new s(r),{cloneBuffers:!1}),r.computeBoundingSphere(),this.grid=r,this.density=e,this.sectionSize=t}createGeometry(t,e){this.prepareGrid(t,e);const n=this.grid.clone();return this.templateSections.add(n),n}computeTangents(t){const n=t.getAttribute("position"),r=t.getAttribute("uv"),s=t.getAttribute("normal"),i=t.getIndex(),o=n.count,a=n.getY(0);let g=!0;for(let t=0;t<o;t++)if(Math.abs(n.getY(t)-a)>1e-10||Math.abs(s.getX(t))>1e-10||Math.abs(s.getZ(t))>1e-10||s.getY(t)<=0){g=!1;break}if(g){let n=t.getAttribute("tangent");null==n&&(n=new e(new Float32Array(4*o),4),t.setAttribute("tangent",n));for(let t=0;t<o;t++)n.setXYZW(t,1,0,0,1);return n.needsUpdate=!0,Math.abs(a)<=1e-10}this.scratch.length!==6*o?this.scratch=new Float64Array(6*o):this.scratch.fill(0);const h=this.scratch;for(let t=0;t<i.count;t+=3){const e=i.getX(t),s=i.getX(t+1),o=i.getX(t+2),a=n.getX(s)-n.getX(e),g=n.getY(s)-n.getY(e),l=n.getZ(s)-n.getZ(e),c=n.getX(o)-n.getX(e),u=n.getY(o)-n.getY(e),d=n.getZ(o)-n.getZ(e),m=r.getX(s)-r.getX(e),p=r.getY(s)-r.getY(e),b=r.getX(o)-r.getX(e),f=r.getY(o)-r.getY(e),X=1/(m*f-b*p);if(!Number.isFinite(X))continue;const w=(a*f-c*p)*X,A=(g*f-u*p)*X,Y=(l*f-d*p)*X,y=(c*m-a*b)*X,B=(u*m-g*b)*X,S=(d*m-l*b)*X;for(let e=0;e<3;e++){const n=6*i.getX(t+e);h[n]+=w,h[n+1]+=A,h[n+2]+=Y,h[n+3]+=y,h[n+4]+=B,h[n+5]+=S}}let l=t.getAttribute("tangent");null==l&&(l=new e(new Float32Array(4*o),4),t.setAttribute("tangent",l));for(let t=0;t<o;t++){this.normal.fromBufferAttribute(s,t),this.tangent.fromArray(h,6*t),this.bitangent.fromArray(h,6*t+3),this.orthogonal.copy(this.tangent).addScaledVector(this.normal,-this.normal.dot(this.tangent)).normalize();const e=this.cross.crossVectors(this.normal,this.tangent).dot(this.bitangent)<0?-1:1;l.setXYZW(t,this.orthogonal.x,this.orthogonal.y,this.orthogonal.z,e)}return l.needsUpdate=!0,!1}updateBoundsTree(e,n,r,i=!1){if(null==e.boundsTree){this.prepareGrid(n,r);const t=this.templateSections.delete(e);if(e.boundsTree=s.deserialize({...this.template,roots:this.template.roots.map(t=>t.slice(0))},e,{setIndex:!t}),t&&i)return}e.boundsTree.refit(),e.boundingBox??(e.boundingBox=new t),e.boundsTree.getBoundingBox(e.boundingBox),e.computeBoundingSphere()}}/*
|
|
2
|
+
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
|
+
* See the LICENSE.md file for details.
|
|
4
|
+
*/
|
|
@@ -31,6 +31,7 @@ export declare class LandscapeManager {
|
|
|
31
31
|
updateSource(source: SceneObject): void;
|
|
32
32
|
updateShaders(shaders: ShaderImpl[]): void;
|
|
33
33
|
private sectionCache;
|
|
34
|
+
private readonly geometryBuilder;
|
|
34
35
|
private grassGeometry;
|
|
35
36
|
private grassMaterial;
|
|
36
37
|
private loadGrass;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Subject as e,debounceTime as t}from"rxjs";import{Box3 as s,BufferAttribute as o,InstancedMesh as i,MathUtils as n,Matrix4 as a,Mesh as r,MeshStandardMaterial as c,PerspectiveCamera as l,PlaneGeometry as h,ShaderMaterial as u,Triangle as f,Vector2 as p,Vector3 as d}from"three";import{materialFromAsset as m}from"../../scene/materializer.js";import{getMaterialAttribute as w}from"../../scene/materials/utils/material-painting.js";import{whenIdle as g}from"../../utils/async.js";import{indexBy as y}from"../../utils/collections.js";import{meanVectors3withWeight as M}from"../../utils/math.js";import{LandscapeMesh as b,defaultLandscapeMaterial as S}from"./landscape.js";import{smoothNormalsCrossMeshes as x}from"./utils.js";import{grassGeometryTriangleLimit as v}from"../scatter/scatter-limits.js";import{LandscapeStaticDrawSets as A}from"./landscape-static-draw-sets.js";export{v as grassGeometryTriangleLimit};new d,new d;const z=new d,P=new d,D=new d;export class LandscapeManager{constructor(s,o,i,n,r,c,l,h=!1){this.view=o,this.landscape=i,this.assetManagerService=n,this.assetService=r,this.shaders=c,this.applyMaterial=l,this.scatterMeshes=new Map,this.loadedScatterSquares=new Set,this.refreshRequests=new e,this.defaultLandscapeMaterial=S.clone(),this.scatterMeshPool=[],this.onLoopHandler=()=>this.update(),this.stopped=!1,this.sectionCache=new Map,this._matrix=new a,this.scatterGeometryCache=new Map,this._lastUpdatePosition=new d,this._cameraPosition=new d,this.source=JSON.parse(JSON.stringify(s)),h&&"webgpu"===o.requestedBackend&&(this.staticDrawSets=new A(o,i,`hology-landscape-${s.id}`)),this.view.onLoop(this.onLoopHandler),this.defaultLandscapeMaterial.name=S.name,this.defaultLandscapeMaterial.color=S.color,this.refreshSubscription=this.refreshRequests.pipe(t(500)).subscribe(e=>this.refreshScatter(e.origin,e.force,e.predicate))}updateSource(e){this.source=JSON.parse(JSON.stringify(e));for(const e of this.landscape.sections)this.staticDrawSets?.invalidate(e)}updateShaders(e){this.shaders=e;for(const e of this.landscape.sections)this.staticDrawSets?.invalidate(e)}async loadGrass(){const e=await this.assetService.getAsset("6ij937n72g");await this.assetManagerService.getMesh(e);this.grassGeometry=new h(2,2,3,3);const t=this.grassGeometry.getAttribute("normal");for(let e=0;e<t.count;e++)t.setXYZ(e,0,1,0);t.needsUpdate=!0,this.grassMaterial=new c({color:3765785})}refreshGeometry(){const e=this.source.landscape.options,t=(new d,new d);this.view.camera.getWorldPosition(t);const s=[];D.fromArray(this.source.position);const o=this.view.camera instanceof l?Math.min(this.view.camera.far,1e3):1e3,i=1.1*o,n=e.sections.y*e.sectionSize/-2,a=e.sections.x*e.sectionSize/-2;for(const e of this.landscape.sections)if(P.copy(D).add(e.position),P.distanceTo(t)>i){this.staticDrawSets?.remove(e);for(const t of this.scatterMeshes.values()){const s=t.get(e.uuid);null!=s&&(s.visible=!1)}this.landscape.remove(e)}for(let r=0;r<e.sections.x;r++)if(z.x=a+r*e.sectionSize,!(Math.abs(t.x-(D.x+z.x))>i))for(let c=0;c<e.sections.y;c++){z.z=n+c*e.sectionSize,P.copy(D).add(z);const l=P.distanceTo(t),h=`${r},${c}`,u=this.landscape.sections.find(e=>e.x===r&&e.y===c);if(l<=o){if(null==u){this.sectionCache.has(h)||this.sectionCache.set(h,this.createLandscapeMesh(this.source,e,a,n,r,c));const t=this.sectionCache.get(h),o=this.applyMaterial(t);null!=this.staticDrawSets&&Promise.resolve(o).then(()=>{this.stopped||this.staticDrawSets.invalidate(t)}).catch(e=>console.error("Failed to prepare landscape section material",e)),this.landscape.add(t),s.push(t)}}else l>i&&null!=u&&(this.staticDrawSets?.remove(u),this.landscape.remove(u))}x(s)}applyHeightMap(e,t,s,o=1){const i=Math.pow(s+1,2),n=e.getAttribute("position");if(1===o)for(const e of t.points)n.setY(e.i,e.y);else{const e=y(t.points??[],e=>e.i);for(let t=0;t<n.count;t++){const s=$(t,n.count,i);let o=0;o=s%1==0?e.get(s)?.y??0:Math.floor(e.get(s)?.y),n.setY(t,o)}}n.needsUpdate=!0,e.computeVertexNormals()}deleteOldScatterMeshes(){const e=new Set;for(const[t,s]of this.source.grass?.layers.entries()??[])for(const[o,i]of s.meshes.entries()){const s=`${t}-${o}`;e.add(s)}for(const t of this.scatterMeshes.keys())if(!e.has(t)){this.scatterMeshes.get(t).forEach(e=>{e.parent?.remove(e),e.dispose()}),this.scatterMeshes.delete(t)}}queueRefreshScatter(e,t=!1,s=()=>!0){this.refreshRequests.next({origin:e,force:t,predicate:s})}async refreshScatter(e,t=!1,s=()=>!0){t&&this.scatterGeometryCache.clear(),this.deleteOldScatterMeshes();for(const[o,a]of this.source.grass?.layers.entries()??[])for(const[c,l]of a.meshes.entries()){const a=`${o}-${c}`;this.scatterMeshes.has(a)||this.scatterMeshes.set(a,new Map);const h=this.scatterMeshes.get(a),u=await this.assetService.getAsset(l.assetId);if(null==u){console.error(`Can not find asset with id ${l.assetId}`);continue}let p;try{p=await this.assetManagerService.getMesh(u)}catch(e){console.error(`Failed to load mesh in landscape manager for asset with name ${u.name}`,e);continue}const w=[];if(p.scene.traverse(e=>{e instanceof r&&w.push(e)}),1!==w.length){console.log(p),console.warn("Dynamic grass only works for meshes with a single geometry.");continue}if(!(w[0]instanceof r)){console.warn("Only meshes can be used for dynamic grass. Found:",p.scene);continue}const b=w[0];let S=b.geometry;if(this.scatterGeometryCache.has(b.geometry.uuid))S=this.scatterGeometryCache.get(b.geometry.uuid);else if(S=b.geometry.clone(),!0===l.normalsUp&&Y(S),this.scatterGeometryCache.set(b.geometry.uuid,S),null==S.userData.updatedMatrix){p.scene.updateMatrixWorld(),S.applyMatrix4(b.matrixWorld),S.userData.updatedMatrix=!0;const e=u.mesh?.rescale??1;1!==e&&S.scale(e,e,e)}const x=S.getIndex()??S.getAttribute("position"),A=null!=x?x.count/3:0;if(A>v){console.warn(`The triangle count of ${u.name} is too big ${A}. Keep it below ${v}`);continue}const z=null!=u.materialAssignments&&u.materialAssignments.length>0?u.materialAssignments[0].materialId:null,P=null!=z&&"null"!==z?await m(await this.assetService.getAsset(z),null,this.assetService,this.assetManagerService,this.shaders,!1):null;let D=null!=P?P:b.material;if(Array.isArray(D))for(const e of D)e.userData.disableAO=!0;else D.userData.disableAO=!0;const $=n.degToRad(l.maxSlope??90),j=Math.cos($),_=this.landscape.sections,q=_.filter(O(e,l.viewDistance)),k=q.filter(e=>!h.has(e.uuid)||t).filter(e=>s(e));_.filter(L(e,2*l.viewDistance)).forEach(e=>{const t=h.get(e.uuid);null!=t&&(t.visible=!1)});for(const e of q){const t=h.get(e.uuid);null!=t&&(t.visible=!0)}performance.now();const I=this.source.landscape.options,R=I.sectionSize,H=l.density??1??1,W=I.density,F=R/W,E=H,J=F/Math.sqrt(E),Z=Math.pow(W,2),K=J/F,V=Math.floor(Z*E),Q=[0,0,0];for(const e of k)await g(async()=>{e.updateWorldMatrix(!0,!1);const s=this._matrix,n=new d,a=e.geometry.getAttribute("position"),r=e.geometry.getAttribute("normal"),c=(this.source.vertexMaterials??[]).filter(t=>t.m===e.name),u=y(c,e=>e.i);let p=h.get(e.uuid);if(null==p||p.count==V&&!t||(p.parent?.remove(p),this.scatterMeshPool.push(p),h.delete(e.uuid),p=null),null==p){const e=this.scatterMeshPool.findIndex(e=>e.count>=V);e>-1?(p=this.scatterMeshPool[e],p.geometry=S,p.material=D,this.scatterMeshPool.splice(e,1)):p=new i(S,D,V),p.raycast=()=>{},p.receiveShadow=!0}p.visible=!0;const m=new f(new d,new d,new d);let[w,g,b,x]=[new d,new d,new d,new d],[v,A,z]=[[],[],[]],[P,$,L]=[new d,new d,new d,new d];const O=new d,_=new d,q=new d,k=new d,I=new f(new d,new d,new d),R=new f(new d,new d,new d),H=new f(new d,new d,new d),F=new f(new d,new d,new d);let Y=0;e:for(let t=0;t<Z;t++){const i=Math.floor(t/W);w.fromBufferAttribute(a,t+i),k.copy(w).applyMatrix4(e.matrixWorld),I.a.copy(w),I.b.fromBufferAttribute(a,t+1+i),I.c.fromBufferAttribute(a,t+W+1+i),R.a.copy(I.b),R.b.copy(I.c),R.c.fromBufferAttribute(a,t+W+2+i),H.a.fromBufferAttribute(r,t+i),H.b.fromBufferAttribute(r,t+1+i),H.c.fromBufferAttribute(r,t+W+1+i),F.a.copy(H.b),F.b.copy(H.c),F.c.fromBufferAttribute(r,t+W+2+i);const c=[];c[0]=u.get(t+i)?.w,c[1]=u.get(t+1+i)?.w,c[2]=u.get(t+W+1+i)?.w,c[3]=u.get(t+W+2+i)?.w;let h=0;for(let e=0;e<=1+K;e+=K)for(let t=0;t<=1+K;t+=K){if(Y>V)break e;if(h++,h>E)continue e;1-e>t?(g=I.a,b=I.b,x=I.c,P=H.a,$=H.b,L=H.c,v=c[0],A=c[1],z=c[2]):(g=R.a,b=R.b,x=R.c,P=F.a,$=F.b,L=F.c,v=c[1],A=c[2],z=c[3]),m.a.copy(g),m.b.copy(b),m.c.copy(x),B(m),O.set(w.x,0,w.z),U(m,O),m.getBarycoord(O,n).toArray(Q),C[0]=v,C[1]=A,C[2]=z;if(G(C,Q,.2)!==o-1)continue;if(M([g,b,x],Q,_),M([P,$,L],Q,q),null!=l.maxSlope&&l.maxSlope<90&&q.y<j)continue;const i=_;i.y+=T(l.offsetMin,l.offsetMax);const a=T(l.scaleMin??1,l.scaleMax??1);s.makeScale(a,a,a);const r=s.elements;r[12]=i.x,r[13]=i.y,r[14]=i.z,!1!==l.randomRotation&&X(s,a),l.alignToNormal&&N(s,i,p.matrixWorld,q);const u=p.instanceMatrix.array,f=16*Y;u[f]=r[0],u[f+1]=r[1],u[f+2]=r[2],u[f+3]=r[3],u[f+4]=r[4],u[f+5]=r[5],u[f+6]=r[6],u[f+7]=r[7],u[f+8]=r[8],u[f+9]=r[9],u[f+10]=r[10],u[f+11]=r[11],u[f+12]=r[12],u[f+13]=r[13],u[f+14]=r[14],u[f+15]=r[15],Y++}}p.count=Y,p.instanceMatrix.needsUpdate=!0,p.position.copy(e.position),p.updateMatrix(),h.has(e.uuid)||this.landscape?.add(p),h.set(e.uuid,p),p.userData.meshConfig=l});performance.now()}}stop(){this.stopped=!0,this.refreshSubscription.unsubscribe(),this.staticDrawSets?.dispose(),this.view.removeOnLoop(this.onLoopHandler)}update(){this.view.camera&&(this.view.camera.getWorldPosition(this._cameraPosition),this._cameraPosition.distanceTo(this._lastUpdatePosition)>10&&(this._lastUpdatePosition.copy(this._cameraPosition),this.refreshGeometry(),this.refreshScatter(this._cameraPosition)),this.staticDrawSets?.flush())}clear(){this.scatterMeshes.forEach(e=>e.forEach(e=>e.parent?.remove(e)))}createLandscapeMesh(e,t,s,o,i,n){const a=new h(t.sectionSize,t.sectionSize,t.density,t.density);a.rotateX(Math.PI/-2);const r=this.defaultLandscapeMaterial,c=new b(a,r);c.position.x=s+i*t.sectionSize,c.position.z=o+n*t.sectionSize,c.receiveShadow=!0,c.castShadow=!1,c.userData.landscape={x:i,y:n},c.x=i,c.y=n,c.name=`${i},${n}`,w(c,0,!0),w(c,4,!0);const l=e.landscape.heightMaps.find(e=>e.x===i&&e.y===n);if(null!=l&&this.applyHeightMap(a,l,t.density,1),a.computeTangents(),a.computeBoundsTree(),null!=e.landscape.holes&&e.landscape.holes.length>0){const t=getHoleAttribute(c,!0);for(const s of e.landscape.holes)s.m===c.name&&t.setX(s.i,s.w[0])}return c}}export function getHoleAttribute(e,t=!1){if(!e.geometry.hasAttribute("hole")||t){const t=new Float32Array(e.geometry.getAttribute("position").array.length);e.geometry.setAttribute("hole",new o(t,1))}return e.geometry.getAttribute("hole")}function $(e,t,s){const o=Math.sqrt(t),i=Math.floor(e/o)/(o-1),n=e%o/(o-1),a=Math.sqrt(s);return(s-1)*i-(a-1)*i+(a-1)*n}new Map,new p(0,0),new p(1,0),new p(0,1),new p(1,0),new p(0,1),new p(1,1),new d;const j=new s;function L(e,t){return function(s){return j.setFromObject(s).distanceToPoint(e)>t}}function O(e,t){return function(s){return j.setFromObject(s).distanceToPoint(e)<t}}function B(e){e.a.y=0,e.b.y=0,e.c.y=0}const C=[];function G(e,t,s=.5){const o=C;let i=-1,n=-1;for(let e=0;e<o.length;e++)if(null!=o[e])for(let a=0;a<o[e].length;a++){const r=o[e][a]*t[e];r>s&&r>n&&(n=r,i=a)}return i}function T(e,t){let s=t-e,o=k();return o*=s,o+=e,o}const _=[];let q=1e3;for(;q--;)_.push(Math.random());function k(){return++q>=_.length?_[q=0]:_[q]}const I=[];let R=20;for(;R--;)I.push((new a).makeRotationY(k()*Math.PI/2));function U(e,t){let s=k(),o=k();s+o>1&&(s=1-s,o=1-o);const i=e.a,n=e.b,a=e.c;t.x=i.x+s*(n.x-i.x)+o*(a.x-i.x),t.z=i.z+s*(n.z-i.z)+o*(a.z-i.z)}new d;new d;const H=new d,W=new d(0,1,0),F=(new a).makeRotationX(Math.PI/-2);function N(e,t,s,o){e.lookAt(H,o,W).multiply(F)}new a;function X(e,t=1){const s=(++R>=I.length?I[R=0]:I[R]).elements,o=e.elements;o[0]=s[0]*t,o[4]=s[4]*t,o[8]=s[8]*t,o[1]=s[1]*t,o[5]=s[5]*t,o[9]=s[9]*t,o[2]=s[2]*t,o[6]=s[6]*t,o[10]=s[10]*t}function Y(e){const t=e.getAttribute("normal");for(let e=0;e<t.count;e++)t.setXYZ(e,0,1,0);t.normalized=!0,t.needsUpdate=!0}/*
|
|
1
|
+
import{Subject as e,debounceTime as t}from"rxjs";import{Box3 as s,BufferAttribute as o,InstancedMesh as i,MathUtils as n,Matrix4 as a,Mesh as r,MeshStandardMaterial as c,PerspectiveCamera as l,PlaneGeometry as h,ShaderMaterial as u,Triangle as f,Vector2 as d,Vector3 as p}from"three";import{materialFromAsset as m}from"../../scene/materializer.js";import{getMaterialAttribute as w}from"../../scene/materials/utils/material-painting.js";import{whenIdle as g}from"../../utils/async.js";import{indexBy as y}from"../../utils/collections.js";import{meanVectors3withWeight as M}from"../../utils/math.js";import{LandscapeMesh as b,defaultLandscapeMaterial as S}from"./landscape.js";import{smoothNormalsCrossMeshes as x}from"./utils.js";import{grassGeometryTriangleLimit as v}from"../scatter/scatter-limits.js";import{LandscapeStaticDrawSets as A}from"./landscape-static-draw-sets.js";import{LandscapeGeometryBuilder as z}from"./landscape-geometry.js";export{v as grassGeometryTriangleLimit};new p,new p;const P=new p,D=new p,B=new p;export class LandscapeManager{constructor(s,o,i,n,r,c,l,h=!1){this.view=o,this.landscape=i,this.assetManagerService=n,this.assetService=r,this.shaders=c,this.applyMaterial=l,this.scatterMeshes=new Map,this.loadedScatterSquares=new Set,this.refreshRequests=new e,this.defaultLandscapeMaterial=S.clone(),this.scatterMeshPool=[],this.onLoopHandler=()=>this.update(),this.stopped=!1,this.sectionCache=new Map,this.geometryBuilder=new z,this._matrix=new a,this.scatterGeometryCache=new Map,this._lastUpdatePosition=new p,this._cameraPosition=new p,this.source=JSON.parse(JSON.stringify(s)),h&&"webgpu"===o.requestedBackend&&(this.staticDrawSets=new A(o,i,`hology-landscape-${s.id}`)),this.view.onLoop(this.onLoopHandler),this.defaultLandscapeMaterial.name=S.name,this.defaultLandscapeMaterial.color=S.color,this.refreshSubscription=this.refreshRequests.pipe(t(500)).subscribe(e=>this.refreshScatter(e.origin,e.force,e.predicate))}updateSource(e){this.source=JSON.parse(JSON.stringify(e));for(const e of this.landscape.sections)this.staticDrawSets?.invalidate(e)}updateShaders(e){this.shaders=e;for(const e of this.landscape.sections)this.staticDrawSets?.invalidate(e)}async loadGrass(){const e=await this.assetService.getAsset("6ij937n72g");await this.assetManagerService.getMesh(e);this.grassGeometry=new h(2,2,3,3);const t=this.grassGeometry.getAttribute("normal");for(let e=0;e<t.count;e++)t.setXYZ(e,0,1,0);t.needsUpdate=!0,this.grassMaterial=new c({color:3765785})}refreshGeometry(){const e=this.source.landscape.options,t=(new p,new p);this.view.camera.getWorldPosition(t);const s=[];B.fromArray(this.source.position);const o=this.view.camera instanceof l?Math.min(this.view.camera.far,1e3):1e3,i=1.1*o,n=e.sections.y*e.sectionSize/-2,a=e.sections.x*e.sectionSize/-2,r=new Map;for(const e of this.landscape.sections)if(D.copy(B).add(e.position),D.distanceTo(t)>i){this.staticDrawSets?.remove(e);for(const t of this.scatterMeshes.values()){const s=t.get(e.uuid);null!=s&&(s.visible=!1)}this.landscape.remove(e)}else r.set(`${e.x},${e.y}`,e);for(let c=0;c<e.sections.x;c++)if(P.x=a+c*e.sectionSize,!(Math.abs(t.x-(B.x+P.x))>i))for(let l=0;l<e.sections.y;l++){P.z=n+l*e.sectionSize,D.copy(B).add(P);const h=D.distanceTo(t),u=`${c},${l}`,f=r.get(u);if(h<=o){if(null==f){this.sectionCache.has(u)||this.sectionCache.set(u,this.createLandscapeMesh(this.source,e,a,n,c,l));const t=this.sectionCache.get(u),o=this.applyMaterial(t);null!=this.staticDrawSets&&Promise.resolve(o).then(()=>{this.stopped||this.staticDrawSets.invalidate(t)}).catch(e=>console.error("Failed to prepare landscape section material",e)),this.landscape.add(t),s.push(t)}}else h>i&&null!=f&&(this.staticDrawSets?.remove(f),this.landscape.remove(f))}x(s);for(const t of s){t.geometry.getAttribute("position").needsUpdate=!0;const s=this.geometryBuilder.computeTangents(t.geometry);this.geometryBuilder.updateBoundsTree(t.geometry,e.sectionSize,e.density,s)}}applyHeightMap(e,t,s,o=1){const i=Math.pow(s+1,2),n=e.getAttribute("position");if(1===o)for(const e of t.points)n.setY(e.i,e.y);else{const e=y(t.points??[],e=>e.i);for(let t=0;t<n.count;t++){const s=$(t,n.count,i);let o=0;o=s%1==0?e.get(s)?.y??0:Math.floor(e.get(s)?.y),n.setY(t,o)}}n.needsUpdate=!0,e.computeVertexNormals()}deleteOldScatterMeshes(){const e=new Set;for(const[t,s]of this.source.grass?.layers.entries()??[])for(const[o,i]of s.meshes.entries()){const s=`${t}-${o}`;e.add(s)}for(const t of this.scatterMeshes.keys())if(!e.has(t)){this.scatterMeshes.get(t).forEach(e=>{e.parent?.remove(e),e.dispose()}),this.scatterMeshes.delete(t)}}queueRefreshScatter(e,t=!1,s=()=>!0){this.refreshRequests.next({origin:e,force:t,predicate:s})}async refreshScatter(e,t=!1,s=()=>!0){t&&this.scatterGeometryCache.clear(),this.deleteOldScatterMeshes();for(const[o,a]of this.source.grass?.layers.entries()??[])for(const[c,l]of a.meshes.entries()){const a=`${o}-${c}`;this.scatterMeshes.has(a)||this.scatterMeshes.set(a,new Map);const h=this.scatterMeshes.get(a),u=await this.assetService.getAsset(l.assetId);if(null==u){console.error(`Can not find asset with id ${l.assetId}`);continue}let d;try{d=await this.assetManagerService.getMesh(u)}catch(e){console.error(`Failed to load mesh in landscape manager for asset with name ${u.name}`,e);continue}const w=[];if(d.scene.traverse(e=>{e instanceof r&&w.push(e)}),1!==w.length){console.log(d),console.warn("Dynamic grass only works for meshes with a single geometry.");continue}if(!(w[0]instanceof r)){console.warn("Only meshes can be used for dynamic grass. Found:",d.scene);continue}const b=w[0];let S=b.geometry;if(this.scatterGeometryCache.has(b.geometry.uuid))S=this.scatterGeometryCache.get(b.geometry.uuid);else if(S=b.geometry.clone(),!0===l.normalsUp&&J(S),this.scatterGeometryCache.set(b.geometry.uuid,S),null==S.userData.updatedMatrix){d.scene.updateMatrixWorld(),S.applyMatrix4(b.matrixWorld),S.userData.updatedMatrix=!0;const e=u.mesh?.rescale??1;1!==e&&S.scale(e,e,e)}const x=S.getIndex()??S.getAttribute("position"),A=null!=x?x.count/3:0;if(A>v){console.warn(`The triangle count of ${u.name} is too big ${A}. Keep it below ${v}`);continue}const z=null!=u.materialAssignments&&u.materialAssignments.length>0?u.materialAssignments[0].materialId:null,P=null!=z&&"null"!==z?await m(await this.assetService.getAsset(z),null,this.assetService,this.assetManagerService,this.shaders,!1):null;let D=null!=P?P:b.material;if(Array.isArray(D))for(const e of D)e.userData.disableAO=!0;else D.userData.disableAO=!0;const B=n.degToRad(l.maxSlope??90),$=Math.cos(B),j=this.landscape.sections,_=j.filter(O(e,l.viewDistance)),k=_.filter(e=>!h.has(e.uuid)||t).filter(e=>s(e));j.filter(L(e,2*l.viewDistance)).forEach(e=>{const t=h.get(e.uuid);null!=t&&(t.visible=!1)});for(const e of _){const t=h.get(e.uuid);null!=t&&(t.visible=!0)}performance.now();const U=this.source.landscape.options,R=U.sectionSize,H=l.density??1??1,W=U.density,F=R/W,N=H,X=F/Math.sqrt(N),Z=Math.pow(W,2),K=X/F,V=Math.floor(Z*N),Q=[0,0,0];for(const e of k)await g(async()=>{e.updateWorldMatrix(!0,!1);const s=this._matrix,n=new p,a=e.geometry.getAttribute("position"),r=e.geometry.getAttribute("normal"),c=(this.source.vertexMaterials??[]).filter(t=>t.m===e.name),u=y(c,e=>e.i);let d=h.get(e.uuid);if(null==d||d.count==V&&!t||(d.parent?.remove(d),this.scatterMeshPool.push(d),h.delete(e.uuid),d=null),null==d){const e=this.scatterMeshPool.findIndex(e=>e.count>=V);e>-1?(d=this.scatterMeshPool[e],d.geometry=S,d.material=D,this.scatterMeshPool.splice(e,1)):d=new i(S,D,V),d.raycast=()=>{},d.receiveShadow=!0}d.visible=!0;const m=new f(new p,new p,new p);let[w,g,b,x]=[new p,new p,new p,new p],[v,A,z]=[[],[],[]],[P,B,j]=[new p,new p,new p,new p];const L=new p,O=new p,_=new p,k=new p,U=new f(new p,new p,new p),R=new f(new p,new p,new p),H=new f(new p,new p,new p),F=new f(new p,new p,new p);let J=0;e:for(let t=0;t<Z;t++){const i=Math.floor(t/W);w.fromBufferAttribute(a,t+i),k.copy(w).applyMatrix4(e.matrixWorld),U.a.copy(w),U.b.fromBufferAttribute(a,t+1+i),U.c.fromBufferAttribute(a,t+W+1+i),R.a.copy(U.b),R.b.copy(U.c),R.c.fromBufferAttribute(a,t+W+2+i),H.a.fromBufferAttribute(r,t+i),H.b.fromBufferAttribute(r,t+1+i),H.c.fromBufferAttribute(r,t+W+1+i),F.a.copy(H.b),F.b.copy(H.c),F.c.fromBufferAttribute(r,t+W+2+i);const c=[];c[0]=u.get(t+i)?.w,c[1]=u.get(t+1+i)?.w,c[2]=u.get(t+W+1+i)?.w,c[3]=u.get(t+W+2+i)?.w;let h=0;for(let e=0;e<=1+K;e+=K)for(let t=0;t<=1+K;t+=K){if(J>V)break e;if(h++,h>N)continue e;1-e>t?(g=U.a,b=U.b,x=U.c,P=H.a,B=H.b,j=H.c,v=c[0],A=c[1],z=c[2]):(g=R.a,b=R.b,x=R.c,P=F.a,B=F.b,j=F.c,v=c[1],A=c[2],z=c[3]),m.a.copy(g),m.b.copy(b),m.c.copy(x),C(m),L.set(w.x,0,w.z),I(m,L),m.getBarycoord(L,n).toArray(Q),G[0]=v,G[1]=A,G[2]=z;if(q(G,Q,.2)!==o-1)continue;if(M([g,b,x],Q,O),M([P,B,j],Q,_),null!=l.maxSlope&&l.maxSlope<90&&_.y<$)continue;const i=O;i.y+=T(l.offsetMin,l.offsetMax);const a=T(l.scaleMin??1,l.scaleMax??1);s.makeScale(a,a,a);const r=s.elements;r[12]=i.x,r[13]=i.y,r[14]=i.z,!1!==l.randomRotation&&E(s,a),l.alignToNormal&&Y(s,i,d.matrixWorld,_);const u=d.instanceMatrix.array,f=16*J;u[f]=r[0],u[f+1]=r[1],u[f+2]=r[2],u[f+3]=r[3],u[f+4]=r[4],u[f+5]=r[5],u[f+6]=r[6],u[f+7]=r[7],u[f+8]=r[8],u[f+9]=r[9],u[f+10]=r[10],u[f+11]=r[11],u[f+12]=r[12],u[f+13]=r[13],u[f+14]=r[14],u[f+15]=r[15],J++}}d.count=J,d.instanceMatrix.needsUpdate=!0,d.position.copy(e.position),d.updateMatrix(),h.has(e.uuid)||this.landscape?.add(d),h.set(e.uuid,d),d.userData.meshConfig=l});performance.now()}}stop(){this.stopped=!0,this.refreshSubscription.unsubscribe(),this.staticDrawSets?.dispose(),this.view.removeOnLoop(this.onLoopHandler)}update(){this.view.camera&&(this.view.camera.getWorldPosition(this._cameraPosition),this._cameraPosition.distanceTo(this._lastUpdatePosition)>10&&(this._lastUpdatePosition.copy(this._cameraPosition),this.refreshGeometry(),this.refreshScatter(this._cameraPosition)),this.staticDrawSets?.flush())}clear(){this.scatterMeshes.forEach(e=>e.forEach(e=>e.parent?.remove(e)))}createLandscapeMesh(e,t,s,o,i,n){const a=this.geometryBuilder.createGeometry(t.sectionSize,t.density),r=this.defaultLandscapeMaterial,c=new b(a,r);c.position.x=s+i*t.sectionSize,c.position.z=o+n*t.sectionSize,c.receiveShadow=!0,c.castShadow=!1,c.userData.landscape={x:i,y:n},c.x=i,c.y=n,c.name=`${i},${n}`,"webgpu"!==this.view.requestedBackend&&(w(c,0,!0),w(c,4,!0));const l=e.landscape.heightMaps.find(e=>e.x===i&&e.y===n);if(null!=l&&this.applyHeightMap(a,l,t.density,1),null!=e.landscape.holes&&e.landscape.holes.length>0){const t=getHoleAttribute(c,!0);for(const s of e.landscape.holes)s.m===c.name&&t.setX(s.i,s.w[0])}return c}}export function getHoleAttribute(e,t=!1){if(!e.geometry.hasAttribute("hole")||t){const t=new Float32Array(e.geometry.getAttribute("position").array.length);e.geometry.setAttribute("hole",new o(t,1))}return e.geometry.getAttribute("hole")}function $(e,t,s){const o=Math.sqrt(t),i=Math.floor(e/o)/(o-1),n=e%o/(o-1),a=Math.sqrt(s);return(s-1)*i-(a-1)*i+(a-1)*n}new Map,new d(0,0),new d(1,0),new d(0,1),new d(1,0),new d(0,1),new d(1,1),new p;const j=new s;function L(e,t){return function(s){return j.setFromObject(s).distanceToPoint(e)>t}}function O(e,t){return function(s){return j.setFromObject(s).distanceToPoint(e)<t}}function C(e){e.a.y=0,e.b.y=0,e.c.y=0}const G=[];function q(e,t,s=.5){const o=G;let i=-1,n=-1;for(let e=0;e<o.length;e++)if(null!=o[e])for(let a=0;a<o[e].length;a++){const r=o[e][a]*t[e];r>s&&r>n&&(n=r,i=a)}return i}function T(e,t){let s=t-e,o=U();return o*=s,o+=e,o}const _=[];let k=1e3;for(;k--;)_.push(Math.random());function U(){return++k>=_.length?_[k=0]:_[k]}const R=[];let H=20;for(;H--;)R.push((new a).makeRotationY(U()*Math.PI/2));function I(e,t){let s=U(),o=U();s+o>1&&(s=1-s,o=1-o);const i=e.a,n=e.b,a=e.c;t.x=i.x+s*(n.x-i.x)+o*(a.x-i.x),t.z=i.z+s*(n.z-i.z)+o*(a.z-i.z)}new p;new p;const W=new p,F=new p(0,1,0),N=(new a).makeRotationX(Math.PI/-2);function Y(e,t,s,o){e.lookAt(W,o,F).multiply(N)}new a;function E(e,t=1){const s=(++H>=R.length?R[H=0]:R[H]).elements,o=e.elements;o[0]=s[0]*t,o[4]=s[4]*t,o[8]=s[8]*t,o[1]=s[1]*t,o[5]=s[5]*t,o[9]=s[9]*t,o[2]=s[2]*t,o[6]=s[6]*t,o[10]=s[10]*t}function J(e){const t=e.getAttribute("normal");for(let e=0;e<t.count;e++)t.setXYZ(e,0,1,0);t.normalized=!0,t.needsUpdate=!0}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -319,7 +319,7 @@ export interface AttachedComponent {
|
|
|
319
319
|
value: any;
|
|
320
320
|
}[];
|
|
321
321
|
}
|
|
322
|
-
export type ChangeCallback = (o: SceneObject, remote: boolean) => void;
|
|
322
|
+
export type ChangeCallback = (o: SceneObject, remote: boolean, recreate?: boolean) => void;
|
|
323
323
|
export interface SceneDataProvider {
|
|
324
324
|
getObjects(): SceneObject[];
|
|
325
325
|
onCreate(callback: ChangeCallback): any;
|
|
@@ -364,6 +364,11 @@ export declare class SceneMaterializer {
|
|
|
364
364
|
private navigationRevision;
|
|
365
365
|
private objectMap;
|
|
366
366
|
private sceneObjectMap;
|
|
367
|
+
private materializedSourceKinds;
|
|
368
|
+
private replacementQueue;
|
|
369
|
+
private pendingReplacements;
|
|
370
|
+
/** Resolves after explicitly requested object recreations, including their asset loads. */
|
|
371
|
+
whenReplacementsComplete(): Promise<void>;
|
|
367
372
|
private updateSubscription;
|
|
368
373
|
private createAssetSubscription;
|
|
369
374
|
readonly components: GameComponent[];
|
|
@@ -382,6 +387,7 @@ export declare class SceneMaterializer {
|
|
|
382
387
|
registerPersistentActors(): BaseActor[];
|
|
383
388
|
idToSceneObject: Map<string, SceneObject>;
|
|
384
389
|
inEditor: boolean;
|
|
390
|
+
private readonly staticSpatialDisposers;
|
|
385
391
|
netMode: NetMode;
|
|
386
392
|
updated$: Subject<{
|
|
387
393
|
object: Object3D;
|