@hology/core 0.0.240 → 0.0.241
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/gameplay/actors/builtin/navmesh-actor.js +1 -1
- package/dist/gameplay/actors/camera/first-person-camera-component.d.ts +0 -1
- package/dist/gameplay/actors/camera/first-person-camera-component.js +1 -1
- package/dist/gameplay/actors/camera/third-person-camera-component.d.ts +0 -1
- package/dist/gameplay/actors/camera/third-person-camera-component.js +1 -1
- package/dist/gameplay/ai/build-tile.js +1 -1
- package/dist/gameplay/ai/dynamic-tiled-navmesh.d.ts +2 -1
- package/dist/gameplay/ai/dynamic-tiled-navmesh.js +1 -1
- package/dist/gameplay/ai/dynamic-tiled-navmesh.worker.js +1 -1
- package/dist/rendering/webgpu-experimental-backend.d.ts +2 -0
- package/dist/rendering/webgpu-experimental-backend.js +1 -1
- package/dist/scene/asset-resource-loader.js +1 -1
- package/dist/scene/model.d.ts +51 -0
- package/dist/scene/runtime-backend-service.js +1 -1
- package/dist/scene/runtime-bundled-backend-service.js +1 -1
- package/dist/scene/storage/packed-json.d.ts +7 -0
- package/dist/scene/storage/packed-json.js +4 -0
- package/dist/scene/storage/storage.d.ts +1 -1
- package/dist/scene/storage/storage.js +1 -1
- package/dist/secondary-motion/data.d.ts +28 -0
- package/dist/secondary-motion/data.js +4 -0
- package/dist/secondary-motion/generation.d.ts +22 -0
- package/dist/secondary-motion/generation.js +4 -0
- package/dist/secondary-motion/runtime.d.ts +111 -0
- package/dist/secondary-motion/runtime.js +4 -0
- package/dist/test/build-tile.test.d.ts +2 -0
- package/dist/test/build-tile.test.js +4 -0
- package/dist/test/first-person-camera-component.test.js +1 -1
- package/dist/test/navmesh-worker-pool.test.js +1 -1
- package/dist/test/packed-json.test.d.ts +2 -0
- package/dist/test/packed-json.test.js +4 -0
- package/dist/test/secondary-motion.test.d.ts +2 -0
- package/dist/test/secondary-motion.test.js +4 -0
- package/package.json +6 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{__decorate as e,__metadata as t}from"tslib";import{Ball as n,Capsule as s,Cone as o,ConvexPolyhedron as i,Cuboid as r,Cylinder as a,Heightfield as l,ShapeType as c,TriMesh as h}from"@dimforge/rapier3d-simd-compat";import{init as p}from"@recast-navigation/core";import{DebugDrawer as u,getPositionsAndIndices as d}from"@recast-navigation/three";import{BehaviorSubject as m,Subject as f,debounceTime as
|
|
1
|
+
import{__decorate as e,__metadata as t}from"tslib";import{Ball as n,Capsule as s,Cone as o,ConvexPolyhedron as i,Cuboid as r,Cylinder as a,Heightfield as l,ShapeType as c,TriMesh as h}from"@dimforge/rapier3d-simd-compat";import{init as p}from"@recast-navigation/core";import{DebugDrawer as u,getPositionsAndIndices as d}from"@recast-navigation/three";import{BehaviorSubject as m,Subject as f,debounceTime as b,filter as w,firstValueFrom as g,takeUntil as y}from"rxjs";import*as x from"three";import{BufferGeometryUtils as v,ConvexHull as M}from"three/examples/jsm/Addons.js";import{Actor as B,BaseActor as S,Parameter as j,PhysicsSystem as z,ViewController as A,World as k,attach as C,inject as D}from"../../index.js";import{sleepDelay as P}from"../../../utils/async.js";import{hasSharedArrayBufferSupport as F,toSharedFloat32Array as I,toSharedUint32Array as V}from"../../../utils/buffer.js";import{DynamicTiledNavMesh as G,MAX_NAV_MESH_WORKERS as N}from"../../ai/dynamic-tiled-navmesh.js";import{TriggerVolumeMesh as H}from"./components/volume-editor-component.js";var E;!function(e){e[e.none=0]="none",e[e.starting=1]="starting",e[e.started=2]="started"}(E||(E={}));let T=E.none,R=new m(!1);export async function safeRecastInit(){return T===E.none?(T=E.starting,p().then(()=>{R.next(!0),T=E.started})):g(R.pipe(w(e=>e)))}new x.Box3(new x.Vector3(-100,-100,-100),new x.Vector3(100,100,100));const U=Math.max(1,Math.min(N,(navigator.hardwareConcurrency??2)-1));let q=!1,O=class extends S{constructor(){super(...arguments),this.physics=D(z),this.view=D(A),this.world=D(k),this.editorVisualisation=C(H),this.debug=!0,this.refreshMs=4e3,this.tileSize=50,this.walkableClimb=.3,this.walkableSlopeAngle=45,this.walkableHeight=2,this.cellSize=.2,this.bounds=new x.Box3}async onInit(){q||(await safeRecastInit(),q=!0),setTimeout(()=>{this.init()},10),this.recreateInterval=setInterval(()=>{if("_hology_transform_group"===this.object.parent?.name)return;J.setFromCenterAndSize(this.position,this.object.scale).equals(this.bounds)||(this.disposed.next(!0),this.disposed=new f,this.init())},2e3)}onEndPlay(){clearInterval(this.recreateInterval)}applySceneParameters(e,t){return!(null==this.debugDrawer||!t.has("debug")||"boolean"!=typeof e.debug)&&(null==this.debugDrawer.parent&&this.object.parent?.add(this.debugDrawer),this.debugDrawer.visible=e.debug,!0)}init(){this.bounds.setFromCenterAndSize(this.position,this.object.scale);const e=this.bounds.max.x-this.bounds.min.x,t=this.bounds.max.z-this.bounds.min.z,n=Math.max(1,Math.min(e,t)),s=Math.min(400,Math.max(50,Math.ceil(n/2))),o=Math.max(this.cellSize,.01),i={tileSize:Math.max(32,Math.floor(s/o)),walkableClimb:this.walkableClimb/o,walkableSlopeAngle:this.walkableSlopeAngle,walkableRadius:2,walkableHeight:this.walkableHeight/o,detailSampleDist:1,minRegionArea:6,mergeRegionArea:400,cs:o,ch:o,maxSimplificationError:1.3,maxEdgeLen:200},r=new G({navMeshBounds:this.bounds,recastConfig:i,maxTiles:1024,workers:U,cacheId:"nav"+this.object.userData?.src?.id});this.navMesh=r.navMesh;const a=this.tileSize*i.cs*2,l=performance.now(),c=new Map,h=new u;h.userData.isDebugDrawer=!0,this.debugDrawer=h;r.navMesh;const p=this.bounds,m=()=>{const e=this.view.getCamera().getWorldPosition(new x.Vector3),t=new x.Box3((new x.Vector3).copy(e).subScalar(a),(new x.Vector3).copy(e).addScalar(a)),n=[],s=this.physics.world.bodies;if(null==s)return[];const o=new x.Box3;for(const e of s.getAll())for(let s=0,i=e.numColliders();s<i;s++){const i=e.collider(s);if(i.isSensor()||null!=i.parent().userData&&!0===i.parent().userData.ignoreForNavMesh)continue;const r=e.handle+","+s,a=c.get(r)?.mesh,l=a??X(i);if($(i,l),null!=l){o.copy(l.geometry.boundingBox),o.min.add(l.position),o.max.add(l.position);const e=o.intersectsBox(t)||!0,s=o.intersectsBox(this.bounds);c.set(r,{pos:i.translation(),mesh:l}),e&&s&&n.push(l)}}return n},f=new x.Box3,w=new Map,g=new Map;let v=!0,M=performance.now(),B=!1;const S=function(e,t){let n=!1;return(async()=>{for(;!n;)await e(),await P(t)})(),()=>{n=!0}}(async()=>{if(B)return;const e=new x.Box3,t=m();for(const n of t){const t=w.get(n);!0!==t?.equals(n.position)&&(null!=t&&e.expandByPoint(t),e.expandByObject(n),w.set(n,n.position.clone()))}e.min.subScalar(50),e.max.addScalar(50);const n=r.getTilesForBounds(e);n.length>500&&console.warn("Too many tiles to update. Consider increasing tile size");const s=n.slice(0,500);if(0!=s.length){const n=[];for(const s of t)f.setFromObject(s),f.intersectsBox(e)&&n.push(s);let[o,i]=d(n);i=function(e,t,n){const s=new x.Vector3,o=[];for(let i=0;i<t.length;i+=3){let r=!0;for(let o=0;o<3;o++){const a=3*t[i+o];if(s.fromArray(e,a),!n.containsPoint(s)){r=!1;break}}r&&o.push(t[i],t[i+1],t[i+2])}return new Uint32Array(o)}(o,i,p),F&&(o=I(o),i=V(i));const a=v;v=!1,await Promise.all(s.map(e=>(M=performance.now(),r.buildTile(o,i,e,a).then(()=>{const t=e[0]+","+e[1];g.set(t,(g.get(t)??0)+1),this.debug})))).then(()=>{this.debug,B=!1})}else B=!1},this.refreshMs??1e4);this.disposed.subscribe(()=>S()),r.onNavMeshUpdate.pipe(y(this.disposed),b(200)).subscribe(()=>{h.clear(),h.drawNavMesh(r.navMesh)}),console.log("Create navmesh with debug",this.debug),h.visible=this.debug,this.object.rotation.set(0,0,0),this.object.updateMatrix(),this.object.updateMatrixWorld(),this.object.parent?.add(h),this.disposed.subscribe(()=>{r?.destroy(),h.removeFromParent(),h.dispose()});const j=performance.now()-l;j>1e3&&console.warn(`NavMesh update took ${j} ms. Consider changing tileSize or other parameter that may affect performance`)}};e([j(),t("design:type",Boolean)],O.prototype,"debug",void 0),e([j(),t("design:type",Number)],O.prototype,"walkableClimb",void 0),e([j({range:[0,89]}),t("design:type",Number)],O.prototype,"walkableSlopeAngle",void 0),e([j(),t("design:type",Number)],O.prototype,"walkableHeight",void 0),e([j({range:[.01,10]}),t("design:type",Number)],O.prototype,"cellSize",void 0),O=e([B()],O);export default O;const W=new Map,_=new WeakMap;function L(e){if(e.shape instanceof l)return function(e){const t=e.shape;if(t.type!==c.HeightField)throw new Error("The provided collider is not a height field.");let n=!1;const s=t,o=s.heights,i=s.nrows,r=s.ncols,a=s.scale.x,l=s.scale.z,h=s.scale.y,p=i+1,u=new x.PlaneGeometry(l,a,r,i);u.rotateX(-Math.PI/2);const d=u.attributes.position.array;let m=0;for(let e=0;e<p;e++)for(let t=0;t<p;t++)d[m+1]=o[t*p+e]*h,m+=3,0!=d[m+1]&&(n=!0);if(!n){const e=new x.PlaneGeometry(a,l,2,2);return e.rotateX(-Math.PI/2),e}return u}(e);if(e.shape instanceof n)return new x.SphereGeometry(e.shape.radius);if(e.shape instanceof r){const t=e.shape.halfExtents;return new x.BoxGeometry(2*t.x,2*t.y,2*t.z)}if(e.shape instanceof i){const t=function(e){const t=_.get(e);if(void 0!==t)return t;const n=new Uint32Array(e.buffer,e.byteOffset,e.byteLength>>>2);let s=2166136261;for(let e=0;e<n.length;e++)s=Math.imul(s^n[e],16777619)>>>0;return _.set(e,s),s}(e.shape.vertices);let n=W.get(t);return n||(n=function(e){const t=[];for(let n=0;n<e.length;n+=3)t.push(new x.Vector3(e[n],e[n+1],e[n+2]));const n=(new M).setFromPoints(t),s=[];n.faces.forEach(e=>{const t=e.edge.head().point,n=e.edge.next.head().point,o=e.edge.next.next.head().point;s.push(t.x,t.y,t.z),s.push(n.x,n.y,n.z),s.push(o.x,o.y,o.z)});const o=new x.BufferGeometry;return o.setAttribute("position",new x.Float32BufferAttribute(s,3)),o}(e.shape.vertices),W.set(t,n)),n}if(e.shape instanceof h){const t=e.shape.vertices,n=e.shape.indices;let s=new x.BufferGeometry;return s.setAttribute("position",new x.Float32BufferAttribute(t,3)),null!=n?s.setIndex(new x.Uint16BufferAttribute(n,1)):s=v.mergeVertices(s),s.computeVertexNormals(),s}if(e.shape instanceof a){const t=e.shape.halfHeight,n=e.shape.radius;return new x.CylinderGeometry(n,n,2*t)}if(e.shape instanceof o){const t=e.shape.halfHeight,n=e.shape.radius;return new x.ConeGeometry(n,2*t)}if(e.shape instanceof s){const t=e.shape.halfHeight,n=e.shape.radius;return new x.CapsuleGeometry(n,2*t)}return console.warn("Unsupported shape",e.shape.type,e),null}function X(e){const t=L(e);if(null==t)return null;const n=K,s=new x.Mesh(t,n);return s.geometry.computeBoundingBox(),s.geometry.scale(1.01,1.01,1.01),s}function $(e,t){const n=e.translation(),s=e.rotation();t.position.set(n.x,n.y,n.z),t.quaternion.set(s.x,s.y,s.z,s.w)}const J=new x.Box3,K=new x.MeshBasicMaterial({wireframe:!1,color:16711680,side:x.FrontSide});/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -26,7 +26,6 @@ export declare class FirstPersonCameraComponent extends ActorComponent {
|
|
|
26
26
|
readonly additivePositionOffset: Vector3;
|
|
27
27
|
readonly additiveRotationOffset: Euler;
|
|
28
28
|
additiveFov: number;
|
|
29
|
-
private canvas;
|
|
30
29
|
private pointerLockInactivatedAt;
|
|
31
30
|
private pointerEventsRegistered;
|
|
32
31
|
private readonly hiddenObjects;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{__decorate as t,__metadata as e}from"tslib";import{Euler as i,Object3D as
|
|
1
|
+
import{__decorate as t,__metadata as e}from"tslib";import{Euler as i,Object3D as o,PerspectiveCamera as s,Quaternion as n,Vector3 as r}from"three";import{Parameter as a}from"../../../shader/parameter.js";import{inject as h}from"../../inject.js";import{RestrictedRotationInput as c}from"../../input/index.js";import{ViewController as d}from"../../services/render.js";import{World as m}from"../../services/world.js";import{ActorComponent as l,Component as p}from"../component.js";const u="undefined"!=typeof window&&/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),v=u?65:75,f=Math.PI/2-.01;let g=class extends l{constructor(){super(...arguments),this.viewController=h(d),this.world=h(m),this.aspect=this.viewController.htmlElement.clientWidth/this.viewController.htmlElement.clientHeight,this.near=.05,this.far=500,this.viewAngle=v,this.eyeHeight=1.7,this.offsetX=0,this.offsetZ=0,this.autoActivate=!0,this.activated=!1,this.isMouseLocked=!1,this.rotationInput=new c(-f,f),this.camera=new s(this.viewAngle,this.aspect,this.near,this.far),this.viewModelRoot=new o,this.additivePositionOffset=new r,this.additiveRotationOffset=new i,this.additiveFov=0,this.pointerLockInactivatedAt=null,this.pointerEventsRegistered=!1,this.hiddenObjects=new Map,this.onMouseDown=t=>{this.isMouseLocked||"mouse"!==t.pointerType||this.hideCursor()},this.onKeyDown=t=>{"Escape"===t.key&&this.showCursor()},this.onPointerLockChange=()=>{this.isMouseLocked=null!=document.pointerLockElement||null!=document.mozPointerLockElement,this.isMouseLocked||(this.element.style.cursor="default",this.pointerLockInactivatedAt=performance.now())}}async onInit(){this.applyCameraSettings(),this.camera.add(this.viewModelRoot),this.world.scene.add(this.camera),this.rotationInput.rotation.x=0,this.updateCameraTransform(),this.disposed.subscribe(()=>{this.unregisterPointerEvents(),this.restoreHiddenObjects(),this.camera.removeFromParent()}),this.autoActivate&&this.activate()}activate(){this.activated=!0,this.updateCameraTransform(),this.viewController.setCamera(this.camera),this.isMouseLocked=null!=document.pointerLockElement||null!=document.mozPointerLockElement,this.registerPointerEvents()}deactivate(){this.activated=!1,this.isMouseLocked=!1,this.unregisterPointerEvents()}onLateUpdate(){this.activated&&this.updateCameraTransform()}hideCursor(){if(null!=this.pointerLockInactivatedAt&&performance.now()-this.pointerLockInactivatedAt<1600)return;this.element.style.cursor="none";const t=this.element.getElementsByTagName("canvas")[0];if(null!=t&&t.isConnected&&t.ownerDocument===document&&null!=t.requestPointerLock)try{const e=t.requestPointerLock({unadjustedMovement:!0});e?.catch(this.onPointerLockChange)}catch{this.onPointerLockChange()}else this.onPointerLockChange()}showCursor(){this.pointerLockInactivatedAt=performance.now(),this.element.style.cursor="default",null==document.pointerLockElement&&null==document.mozPointerLockElement||window.document.exitPointerLock(),this.isMouseLocked=!1}getAimOrigin(t){return this.camera.updateWorldMatrix(!0,!1),this.camera.getWorldPosition(t)}getAimDirection(t){return this.camera.updateWorldMatrix(!0,!1),this.camera.getWorldDirection(t)}getAimRay(t){return this.getAimOrigin(t.origin),this.getAimDirection(t.direction),t}hideObjects(...t){for(const e of t)null==e||this.hiddenObjects.has(e)||(this.hiddenObjects.set(e,e.visible),e.visible=!1)}restoreHiddenObjects(){for(const[t,e]of this.hiddenObjects)t.visible=e;this.hiddenObjects.clear()}get element(){return this.viewController.htmlElement}registerPointerEvents(){if(this.pointerEventsRegistered||null==document.body.requestPointerLock)return;const t=this.element;t.addEventListener("pointerdown",this.onMouseDown),t.addEventListener("keydown",this.onKeyDown),document.addEventListener("pointerlockchange",this.onPointerLockChange,!1),this.pointerEventsRegistered=!0}unregisterPointerEvents(){if(!this.pointerEventsRegistered)return;const t=this.element;t.removeEventListener("pointerdown",this.onMouseDown),t.removeEventListener("keydown",this.onKeyDown),document.removeEventListener("pointerlockchange",this.onPointerLockChange,!1),this.pointerEventsRegistered=!1}applyCameraSettings(){const t=this.viewAngle+this.additiveFov;this.camera.near===this.near&&this.camera.far===this.far&&this.camera.fov===t&&this.camera.aspect===this.aspect||(this.camera.near=this.near,this.camera.far=this.far,this.camera.fov=t,this.camera.aspect=this.aspect,this.camera.updateProjectionMatrix())}updateCameraTransform(){this.aspect=this.viewController.htmlElement.clientWidth/this.viewController.htmlElement.clientHeight,this.applyCameraSettings(),this.actor.object.updateWorldMatrix(!0,!1),w.set(this.offsetX,this.eyeHeight,this.offsetZ),this.camera.position.copy(w.applyMatrix4(this.actor.object.matrixWorld)),this.actor.object.getWorldQuaternion(L),b.set(-this.rotationInput.rotation.x,0,0),E.setFromEuler(b),this.camera.quaternion.copy(L).multiply(k).multiply(E),this.additivePositionOffset.lengthSq()>0&&(y.copy(this.additivePositionOffset).applyQuaternion(this.camera.quaternion),this.camera.position.add(y)),0===this.additiveRotationOffset.x&&0===this.additiveRotationOffset.y&&0===this.additiveRotationOffset.z||(P.setFromEuler(this.additiveRotationOffset),this.camera.quaternion.multiply(P)),this.camera.updateMatrixWorld(!0)}};t([a(),e("design:type",Number)],g.prototype,"near",void 0),t([a(),e("design:type",Number)],g.prototype,"far",void 0),t([a(),e("design:type",Number)],g.prototype,"viewAngle",void 0),t([a(),e("design:type",Number)],g.prototype,"eyeHeight",void 0),t([a(),e("design:type",Number)],g.prototype,"offsetX",void 0),t([a(),e("design:type",Number)],g.prototype,"offsetZ",void 0),g=t([p()],g);export{g as FirstPersonCameraComponent};const w=new r,y=new r,L=new n,k=(new n).setFromEuler(new i(0,Math.PI,0)),E=new n,P=new n,b=new i;/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -76,7 +76,6 @@ export declare class ThirdPersonCameraComponent extends ActorComponent {
|
|
|
76
76
|
private unregisterPointerEvents;
|
|
77
77
|
onLateUpdate(deltaTime: number): void;
|
|
78
78
|
private get element();
|
|
79
|
-
private canvas;
|
|
80
79
|
hideCursor({ ignoreRecentUnlock }?: {
|
|
81
80
|
ignoreRecentUnlock?: boolean;
|
|
82
81
|
}): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{__decorate as t,__metadata as o}from"tslib";import{ActorComponent as e,Component as i}from"../component.js";import{Vector3 as s,MathUtils as r,PerspectiveCamera as n,Object3D as h}from"three";import{ViewController as a}from"../../services/render.js";import{DecimalInput as l,RestrictedRotationInput as c}from"../../input/index.js";import{PhysicsSystem as p}from"../../services/physics/physics-system.js";import{Parameter as m}from"../../../shader/parameter.js";import{World as d}from"../../services/world.js";import{inject as u}from"../../inject.js";import{ease as y}from"@hology/nebula";const f=void 0!==window&&/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);let v=class extends e{constructor(){super(),this.viewController=u(a),this.physicsSystem=u(p),this.aspect=this.viewController.htmlElement.clientWidth/this.viewController.htmlElement.clientHeight,this.near=.5,this.far=500,this.viewAngle=f?30:45,this.collision=!0,this.collisionSphereRadius=.25,this.smoothCamera=!1,this.smoothSpeed=10,this.teleportSnapDistance=5,this.camera=new n(this.viewAngle,this.aspect,this.near,this.far),this.distance=9,this.minDistance=1.5,this.maxDistance=this.distance,this.height=3,this.offsetX=-1,this.offsetY=2,this.offsetZ=1.5,this.autoActivate=!0,this.bounceBackSpeed=3,this.restrictedDistance=0,this.hasCollisionRestriction=!1,this.rotationInput=new c(-Math.PI/4,Math.PI/2-.7),this.zoomInput=new l(1,0,1),this.offset=new s,this.lookAtOffset=new s(this.offsetX,0,this.offsetZ),this.fixedBehind=!0,this.world=u(d),this.activated=!1,this.isMouseLocked=!1,this.prevFixedBehind=!1,this.blendDurationLeft=0,this.pointerEventsRegistered=!1,this.temporaryControlActive=!1,this.temporaryControlWeight=0,this.temporaryControlTargetWeight=0,this.temporaryControlBlendInDuration=.5,this.temporaryControlBlendOutDuration=.5,this.temporaryControlPositionEnabled=!1,this.temporaryControlBaseFieldOfView=this.camera.fov,this.temporaryControlFieldOfView=null,this.temporaryControlPosition=new s,this.temporaryControlLookAt=new s,this.canvas=null,this.pointerLockInactivatedAt=null,this.onMouseDown=t=>{this.isMouseLocked||"mouse"!==t.pointerType||this.hideCursor()},this.onKeyDown=t=>{"Escape"===t.key&&this.showCursor()},this.onPointerLockChange=()=>{this.isMouseLocked=null!=document.pointerLockElement||null!=document.mozPointerLockElement,this.element.style.cursor=this.isMouseLocked?"none":"default",this.isMouseLocked||(this.pointerLockInactivatedAt=performance.now())},this.prevLookAt=new s,this.smoothedLookAt=new s,this.previousTargetLookAt=new s,this.hasSmoothedLookAt=!1,this.smoothedRotX=0,this.smoothedRotY=0,this.smoothZoom=0}async onInit(){this.prevFixedBehind=this.fixedBehind,this.world.scene.add(this.camera),this.rotationInput.rotation.copy(this.actor.rotation),this.smoothedRotX=this.rotationInput.rotation.x,this.smoothedRotY=this.rotationInput.rotation.y,this.hasCollisionRestriction=!1,this.lookAtOffset.set(this.offsetX,0,this.offsetZ),this.smoothZoom=this.zoomInput.value,this.autoActivate&&this.activate(),this.disposed.subscribe(()=>{this.unregisterPointerEvents()})}activate(){this.activated=!0,this.hasSmoothedLookAt=!1,this.viewController.setCamera(this.camera),this.isMouseLocked=null!=document.pointerLockElement||null!=document.mozPointerLockElement,this.registerPointerEvents()}deactivate(){this.activated=!1,this.isMouseLocked=!1,this.unregisterPointerEvents()}setTemporaryControl(t,o,e={}){this.temporaryControlPosition.copy(t),this.temporaryControlLookAt.copy(o),this.temporaryControlPositionEnabled=!0,this.activateTemporaryControl(e)}setTemporaryLookAt(t,o={}){this.temporaryControlLookAt.copy(t),this.temporaryControlPositionEnabled=!1,this.activateTemporaryControl(o)}activateTemporaryControl(t){this.temporaryControlActive||(this.temporaryControlBaseFieldOfView=this.camera.fov),this.temporaryControlFieldOfView=null==t.fieldOfView?null:r.clamp(t.fieldOfView,1,179),null!=t.blendInDuration&&(this.temporaryControlBlendInDuration=Math.max(0,t.blendInDuration)),null!=t.blendOutDuration&&(this.temporaryControlBlendOutDuration=Math.max(0,t.blendOutDuration)),this.temporaryControlActive=!0,this.temporaryControlTargetWeight=1}clearTemporaryControl(t){this.temporaryControlActive&&(null!=t&&(this.temporaryControlBlendOutDuration=Math.max(0,t)),this.temporaryControlTargetWeight=0)}registerPointerEvents(){if(this.pointerEventsRegistered||null==document.body.requestPointerLock)return;const t=this.element;t.addEventListener("pointerdown",this.onMouseDown),t.addEventListener("keydown",this.onKeyDown),document.addEventListener("pointerlockchange",this.onPointerLockChange,!1),this.pointerEventsRegistered=!0}unregisterPointerEvents(){if(!this.pointerEventsRegistered)return;const t=this.element;t.removeEventListener("pointerdown",this.onMouseDown),t.removeEventListener("keydown",this.onKeyDown),document.removeEventListener("pointerlockchange",this.onPointerLockChange,!1),this.pointerEventsRegistered=!1}onLateUpdate(t){this.activated&&this.setFromRotation(t)}get element(){return this.viewController.htmlElement}hideCursor({ignoreRecentUnlock:t=!1}={}){!t&&null!=this.pointerLockInactivatedAt&&performance.now()-this.pointerLockInactivatedAt<1600||(null==this.canvas&&(this.canvas=this.element.getElementsByTagName("canvas")[0]),this.canvas&&this.canvas.requestPointerLock&&this.requestPointerLock())}requestPointerLock(){try{const t=this.canvas.requestPointerLock({unadjustedMovement:!0});t?.catch(()=>this.onPointerLockChange())}catch{this.onPointerLockChange()}}showCursor(){this.pointerLockInactivatedAt=performance.now(),this.element.style.cursor="default",null==document.pointerLockElement&&null==document.mozPointerLockElement||window.document.exitPointerLock(),this.isMouseLocked=!1}setFromRotation(t){this.lookAtOffset.set(this.offsetX,0,this.offsetZ),this.fixedBehind!==this.prevFixedBehind?(this.blendDurationLeft=1,this.prevFixedBehind=this.fixedBehind):this.blendDurationLeft>0&&(this.blendDurationLeft-=t);let o=this.zoomInput.value,e=this.rotationInput.rotation.x,i=this.rotationInput.rotation.y;if(this.smoothCamera){const s=1-Math.exp(-this.smoothSpeed*t*2);this.smoothedRotX=r.lerp(this.smoothedRotX,e,s),this.smoothedRotY=r.lerp(this.smoothedRotY,i,s),e=this.smoothedRotX,i=this.smoothedRotY,this.smoothZoom=r.lerp(this.smoothZoom,o,.4*s),o=this.smoothZoom}else this.smoothedRotX=e,this.smoothedRotY=i;const s=r.clamp(this.distance,this.minDistance,Math.max(this.distance*o,this.minDistance)),n=Math.cos(e)*s,h=this.fixedBehind?0:i;this.offset.x=Math.sin(-h)*n,this.offset.y=Math.sin(e)*s+this.offsetY,this.offset.z=Math.cos(-h)*-n,this.fixedBehind&&this.offset.add(this.lookAtOffset),this.updateCameraPosition(t)}checkForCollision(t,o){const e=g.subVectors(this.camera.position,t),i=e.length();if(i<.001)return;e.divideScalar(i);const s=this.physicsSystem.sphereCast(t,this.collisionSphereRadius,e,i,void 0,{excludeActor:this.actor,excludeTriggers:!0,collisionFilter:-2}),n=s.hasHit?Math.max(this.minDistance,s.distance-.05):i;if(!this.hasCollisionRestriction||n<=this.restrictedDistance)this.restrictedDistance=n,this.hasCollisionRestriction=!0;else{const t=1-Math.exp(-this.bounceBackSpeed*o);this.restrictedDistance=r.lerp(this.restrictedDistance,n,t)}this.camera.position.copy(t).addScaledVector(e,Math.min(i,this.restrictedDistance))}getLookAtPosition(){const t=k;return t.set(0,0,0),t.y=this.height,this.fixedBehind&&t.add(this.lookAtOffset),t.applyMatrix4(this.actor.object.matrixWorld),t}updateCameraPosition(t){this.actor.object.updateWorldMatrix(!0,!1),this.fixedBehind?(C.position.set(this.offset.x,this.offset.y,this.offset.z),C.rotation.set(0,0,0),C.scale.set(1,1,1),C.applyMatrix4(this.actor.object.matrix)):C.position.copy(this.actor.position).add(this.offset);const o=y.easeInOutCubic(1-this.blendDurationLeft);if(this.blendDurationLeft>0){const t=this.getLookAtPosition();this.prevLookAt.lerp(t,o),this.camera.lookAt(this.prevLookAt),this.camera.position.lerp(C.position,o),this.smoothedLookAt.copy(this.prevLookAt),this.previousTargetLookAt.copy(t),this.hasSmoothedLookAt=!0}else{const o=this.getLookAtPosition();if(this.smoothCamera){const e=1-Math.exp(-this.smoothSpeed*t),i=Math.max(0,this.teleportSnapDistance);!this.hasSmoothedLookAt||this.previousTargetLookAt.distanceToSquared(o)>i*i?this.smoothedLookAt.copy(o):this.smoothedLookAt.lerp(o,e)}else this.smoothedLookAt.copy(o);this.previousTargetLookAt.copy(o),this.hasSmoothedLookAt=!0,L.subVectors(C.position,o),this.camera.position.copy(this.smoothedLookAt).add(L),this.camera.lookAt(this.smoothedLookAt),this.prevLookAt.copy(this.smoothedLookAt)}this.collision&&(this.checkForCollision(this.prevLookAt,t),this.camera.lookAt(this.prevLookAt)),this.applyTemporaryControl(t)}applyTemporaryControl(t){if(!this.temporaryControlActive)return;this.temporaryControlTargetWeight>this.temporaryControlWeight?this.temporaryControlWeight=0===this.temporaryControlBlendInDuration?1:Math.min(1,this.temporaryControlWeight+t/this.temporaryControlBlendInDuration):this.temporaryControlTargetWeight<this.temporaryControlWeight&&(this.temporaryControlWeight=0===this.temporaryControlBlendOutDuration?0:Math.max(0,this.temporaryControlWeight-t/this.temporaryControlBlendOutDuration));const o=y.easeInOutCubic(this.temporaryControlWeight);this.temporaryControlPositionEnabled&&this.camera.position.lerp(this.getTemporaryControlPosition(),o),x.position.copy(this.camera.position),x.up.copy(this.camera.up),x.lookAt(this.temporaryControlLookAt),this.camera.quaternion.slerp(x.quaternion,o);const e=this.temporaryControlFieldOfView??this.temporaryControlBaseFieldOfView,i=r.lerp(this.temporaryControlBaseFieldOfView,e,o);this.camera.fov!==i&&(this.camera.fov=i,this.camera.updateProjectionMatrix()),0===this.temporaryControlTargetWeight&&0===this.temporaryControlWeight&&(this.temporaryControlActive=!1)}getTemporaryControlPosition(){if(!this.collision)return this.temporaryControlPosition;A.subVectors(this.temporaryControlPosition,this.temporaryControlLookAt);const t=A.length();if(t<.001)return this.temporaryControlPosition;A.divideScalar(t);const o=this.physicsSystem.sphereCast(this.temporaryControlLookAt,this.collisionSphereRadius,A,t,void 0,{excludeActor:this.actor,excludeTriggers:!0,collisionFilter:-2});return!o.hasHit||o.distance>=t?this.temporaryControlPosition:w.copy(this.temporaryControlLookAt).addScaledVector(A,Math.max(0,o.distance-.05))}};t([m(),o("design:type",Number)],v.prototype,"near",void 0),t([m(),o("design:type",Number)],v.prototype,"far",void 0),t([m(),o("design:type",Number)],v.prototype,"viewAngle",void 0),t([m(),o("design:type",Boolean)],v.prototype,"collision",void 0),t([m(),o("design:type",Number)],v.prototype,"collisionSphereRadius",void 0),t([m(),o("design:type",Boolean)],v.prototype,"smoothCamera",void 0),t([m(),o("design:type",Number)],v.prototype,"smoothSpeed",void 0),t([m(),o("design:type",Number)],v.prototype,"teleportSnapDistance",void 0),v=t([i(),o("design:paramtypes",[])],v);export{v as ThirdPersonCameraComponent};const C=new h,k=new s,L=new s,g=new s,A=new s,w=new s,x=new n;export class ThirdPartyCameraComponent extends v{}/*
|
|
1
|
+
import{__decorate as t,__metadata as o}from"tslib";import{ActorComponent as e,Component as i}from"../component.js";import{Vector3 as s,MathUtils as r,PerspectiveCamera as n,Object3D as h}from"three";import{ViewController as a}from"../../services/render.js";import{DecimalInput as l,RestrictedRotationInput as c}from"../../input/index.js";import{PhysicsSystem as p}from"../../services/physics/physics-system.js";import{Parameter as m}from"../../../shader/parameter.js";import{World as d}from"../../services/world.js";import{inject as u}from"../../inject.js";import{ease as y}from"@hology/nebula";const f=void 0!==window&&/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);let C=class extends e{constructor(){super(),this.viewController=u(a),this.physicsSystem=u(p),this.aspect=this.viewController.htmlElement.clientWidth/this.viewController.htmlElement.clientHeight,this.near=.5,this.far=500,this.viewAngle=f?30:45,this.collision=!0,this.collisionSphereRadius=.25,this.smoothCamera=!1,this.smoothSpeed=10,this.teleportSnapDistance=5,this.camera=new n(this.viewAngle,this.aspect,this.near,this.far),this.distance=9,this.minDistance=1.5,this.maxDistance=this.distance,this.height=3,this.offsetX=-1,this.offsetY=2,this.offsetZ=1.5,this.autoActivate=!0,this.bounceBackSpeed=3,this.restrictedDistance=0,this.hasCollisionRestriction=!1,this.rotationInput=new c(-Math.PI/4,Math.PI/2-.7),this.zoomInput=new l(1,0,1),this.offset=new s,this.lookAtOffset=new s(this.offsetX,0,this.offsetZ),this.fixedBehind=!0,this.world=u(d),this.activated=!1,this.isMouseLocked=!1,this.prevFixedBehind=!1,this.blendDurationLeft=0,this.pointerEventsRegistered=!1,this.temporaryControlActive=!1,this.temporaryControlWeight=0,this.temporaryControlTargetWeight=0,this.temporaryControlBlendInDuration=.5,this.temporaryControlBlendOutDuration=.5,this.temporaryControlPositionEnabled=!1,this.temporaryControlBaseFieldOfView=this.camera.fov,this.temporaryControlFieldOfView=null,this.temporaryControlPosition=new s,this.temporaryControlLookAt=new s,this.pointerLockInactivatedAt=null,this.onMouseDown=t=>{this.isMouseLocked||"mouse"!==t.pointerType||this.hideCursor()},this.onKeyDown=t=>{"Escape"===t.key&&this.showCursor()},this.onPointerLockChange=()=>{this.isMouseLocked=null!=document.pointerLockElement||null!=document.mozPointerLockElement,this.element.style.cursor=this.isMouseLocked?"none":"default",this.isMouseLocked||(this.pointerLockInactivatedAt=performance.now())},this.prevLookAt=new s,this.smoothedLookAt=new s,this.previousTargetLookAt=new s,this.hasSmoothedLookAt=!1,this.smoothedRotX=0,this.smoothedRotY=0,this.smoothZoom=0}async onInit(){this.prevFixedBehind=this.fixedBehind,this.world.scene.add(this.camera),this.rotationInput.rotation.copy(this.actor.rotation),this.smoothedRotX=this.rotationInput.rotation.x,this.smoothedRotY=this.rotationInput.rotation.y,this.hasCollisionRestriction=!1,this.lookAtOffset.set(this.offsetX,0,this.offsetZ),this.smoothZoom=this.zoomInput.value,this.autoActivate&&this.activate(),this.disposed.subscribe(()=>{this.unregisterPointerEvents()})}activate(){this.activated=!0,this.hasSmoothedLookAt=!1,this.viewController.setCamera(this.camera),this.isMouseLocked=null!=document.pointerLockElement||null!=document.mozPointerLockElement,this.registerPointerEvents()}deactivate(){this.activated=!1,this.isMouseLocked=!1,this.unregisterPointerEvents()}setTemporaryControl(t,o,e={}){this.temporaryControlPosition.copy(t),this.temporaryControlLookAt.copy(o),this.temporaryControlPositionEnabled=!0,this.activateTemporaryControl(e)}setTemporaryLookAt(t,o={}){this.temporaryControlLookAt.copy(t),this.temporaryControlPositionEnabled=!1,this.activateTemporaryControl(o)}activateTemporaryControl(t){this.temporaryControlActive||(this.temporaryControlBaseFieldOfView=this.camera.fov),this.temporaryControlFieldOfView=null==t.fieldOfView?null:r.clamp(t.fieldOfView,1,179),null!=t.blendInDuration&&(this.temporaryControlBlendInDuration=Math.max(0,t.blendInDuration)),null!=t.blendOutDuration&&(this.temporaryControlBlendOutDuration=Math.max(0,t.blendOutDuration)),this.temporaryControlActive=!0,this.temporaryControlTargetWeight=1}clearTemporaryControl(t){this.temporaryControlActive&&(null!=t&&(this.temporaryControlBlendOutDuration=Math.max(0,t)),this.temporaryControlTargetWeight=0)}registerPointerEvents(){if(this.pointerEventsRegistered||null==document.body.requestPointerLock)return;const t=this.element;t.addEventListener("pointerdown",this.onMouseDown),t.addEventListener("keydown",this.onKeyDown),document.addEventListener("pointerlockchange",this.onPointerLockChange,!1),this.pointerEventsRegistered=!0}unregisterPointerEvents(){if(!this.pointerEventsRegistered)return;const t=this.element;t.removeEventListener("pointerdown",this.onMouseDown),t.removeEventListener("keydown",this.onKeyDown),document.removeEventListener("pointerlockchange",this.onPointerLockChange,!1),this.pointerEventsRegistered=!1}onLateUpdate(t){this.activated&&this.setFromRotation(t)}get element(){return this.viewController.htmlElement}hideCursor({ignoreRecentUnlock:t=!1}={}){!t&&null!=this.pointerLockInactivatedAt&&performance.now()-this.pointerLockInactivatedAt<1600||this.requestPointerLock()}requestPointerLock(){const t=this.element.getElementsByTagName("canvas")[0];if(null!=t&&t.isConnected&&t.ownerDocument===document&&null!=t.requestPointerLock)try{const o=t.requestPointerLock({unadjustedMovement:!0});o?.catch(()=>this.onPointerLockChange())}catch{this.onPointerLockChange()}else this.onPointerLockChange()}showCursor(){this.pointerLockInactivatedAt=performance.now(),this.element.style.cursor="default",null==document.pointerLockElement&&null==document.mozPointerLockElement||window.document.exitPointerLock(),this.isMouseLocked=!1}setFromRotation(t){this.lookAtOffset.set(this.offsetX,0,this.offsetZ),this.fixedBehind!==this.prevFixedBehind?(this.blendDurationLeft=1,this.prevFixedBehind=this.fixedBehind):this.blendDurationLeft>0&&(this.blendDurationLeft-=t);let o=this.zoomInput.value,e=this.rotationInput.rotation.x,i=this.rotationInput.rotation.y;if(this.smoothCamera){const s=1-Math.exp(-this.smoothSpeed*t*2);this.smoothedRotX=r.lerp(this.smoothedRotX,e,s),this.smoothedRotY=r.lerp(this.smoothedRotY,i,s),e=this.smoothedRotX,i=this.smoothedRotY,this.smoothZoom=r.lerp(this.smoothZoom,o,.4*s),o=this.smoothZoom}else this.smoothedRotX=e,this.smoothedRotY=i;const s=r.clamp(this.distance,this.minDistance,Math.max(this.distance*o,this.minDistance)),n=Math.cos(e)*s,h=this.fixedBehind?0:i;this.offset.x=Math.sin(-h)*n,this.offset.y=Math.sin(e)*s+this.offsetY,this.offset.z=Math.cos(-h)*-n,this.fixedBehind&&this.offset.add(this.lookAtOffset),this.updateCameraPosition(t)}checkForCollision(t,o){const e=g.subVectors(this.camera.position,t),i=e.length();if(i<.001)return;e.divideScalar(i);const s=this.physicsSystem.sphereCast(t,this.collisionSphereRadius,e,i,void 0,{excludeActor:this.actor,excludeTriggers:!0,collisionFilter:-2}),n=s.hasHit?Math.max(this.minDistance,s.distance-.05):i;if(!this.hasCollisionRestriction||n<=this.restrictedDistance)this.restrictedDistance=n,this.hasCollisionRestriction=!0;else{const t=1-Math.exp(-this.bounceBackSpeed*o);this.restrictedDistance=r.lerp(this.restrictedDistance,n,t)}this.camera.position.copy(t).addScaledVector(e,Math.min(i,this.restrictedDistance))}getLookAtPosition(){const t=k;return t.set(0,0,0),t.y=this.height,this.fixedBehind&&t.add(this.lookAtOffset),t.applyMatrix4(this.actor.object.matrixWorld),t}updateCameraPosition(t){this.actor.object.updateWorldMatrix(!0,!1),this.fixedBehind?(v.position.set(this.offset.x,this.offset.y,this.offset.z),v.rotation.set(0,0,0),v.scale.set(1,1,1),v.applyMatrix4(this.actor.object.matrix)):v.position.copy(this.actor.position).add(this.offset);const o=y.easeInOutCubic(1-this.blendDurationLeft);if(this.blendDurationLeft>0){const t=this.getLookAtPosition();this.prevLookAt.lerp(t,o),this.camera.lookAt(this.prevLookAt),this.camera.position.lerp(v.position,o),this.smoothedLookAt.copy(this.prevLookAt),this.previousTargetLookAt.copy(t),this.hasSmoothedLookAt=!0}else{const o=this.getLookAtPosition();if(this.smoothCamera){const e=1-Math.exp(-this.smoothSpeed*t),i=Math.max(0,this.teleportSnapDistance);!this.hasSmoothedLookAt||this.previousTargetLookAt.distanceToSquared(o)>i*i?this.smoothedLookAt.copy(o):this.smoothedLookAt.lerp(o,e)}else this.smoothedLookAt.copy(o);this.previousTargetLookAt.copy(o),this.hasSmoothedLookAt=!0,L.subVectors(v.position,o),this.camera.position.copy(this.smoothedLookAt).add(L),this.camera.lookAt(this.smoothedLookAt),this.prevLookAt.copy(this.smoothedLookAt)}this.collision&&(this.checkForCollision(this.prevLookAt,t),this.camera.lookAt(this.prevLookAt)),this.applyTemporaryControl(t)}applyTemporaryControl(t){if(!this.temporaryControlActive)return;this.temporaryControlTargetWeight>this.temporaryControlWeight?this.temporaryControlWeight=0===this.temporaryControlBlendInDuration?1:Math.min(1,this.temporaryControlWeight+t/this.temporaryControlBlendInDuration):this.temporaryControlTargetWeight<this.temporaryControlWeight&&(this.temporaryControlWeight=0===this.temporaryControlBlendOutDuration?0:Math.max(0,this.temporaryControlWeight-t/this.temporaryControlBlendOutDuration));const o=y.easeInOutCubic(this.temporaryControlWeight);this.temporaryControlPositionEnabled&&this.camera.position.lerp(this.getTemporaryControlPosition(),o),x.position.copy(this.camera.position),x.up.copy(this.camera.up),x.lookAt(this.temporaryControlLookAt),this.camera.quaternion.slerp(x.quaternion,o);const e=this.temporaryControlFieldOfView??this.temporaryControlBaseFieldOfView,i=r.lerp(this.temporaryControlBaseFieldOfView,e,o);this.camera.fov!==i&&(this.camera.fov=i,this.camera.updateProjectionMatrix()),0===this.temporaryControlTargetWeight&&0===this.temporaryControlWeight&&(this.temporaryControlActive=!1)}getTemporaryControlPosition(){if(!this.collision)return this.temporaryControlPosition;A.subVectors(this.temporaryControlPosition,this.temporaryControlLookAt);const t=A.length();if(t<.001)return this.temporaryControlPosition;A.divideScalar(t);const o=this.physicsSystem.sphereCast(this.temporaryControlLookAt,this.collisionSphereRadius,A,t,void 0,{excludeActor:this.actor,excludeTriggers:!0,collisionFilter:-2});return!o.hasHit||o.distance>=t?this.temporaryControlPosition:w.copy(this.temporaryControlLookAt).addScaledVector(A,Math.max(0,o.distance-.05))}};t([m(),o("design:type",Number)],C.prototype,"near",void 0),t([m(),o("design:type",Number)],C.prototype,"far",void 0),t([m(),o("design:type",Number)],C.prototype,"viewAngle",void 0),t([m(),o("design:type",Boolean)],C.prototype,"collision",void 0),t([m(),o("design:type",Number)],C.prototype,"collisionSphereRadius",void 0),t([m(),o("design:type",Boolean)],C.prototype,"smoothCamera",void 0),t([m(),o("design:type",Number)],C.prototype,"smoothSpeed",void 0),t([m(),o("design:type",Number)],C.prototype,"teleportSnapDistance",void 0),C=t([i(),o("design:paramtypes",[])],C);export{C as ThirdPersonCameraComponent};const v=new h,k=new s,L=new s,g=new s,A=new s,w=new s,x=new n;export class ThirdPartyCameraComponent extends C{}/*
|
|
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{NavMeshCreateParams as e,Raw as t,RecastBuildContext as i,TriangleAreasArray as l,TrianglesArray as o,VerticesArray as r,allocCompactHeightfield as a,allocContourSet as s,allocHeightfield as n,allocPolyMesh as d,allocPolyMeshDetail as c,buildCompactHeightfield as h,buildContours as g,buildDistanceField as m,buildPolyMesh as u,buildPolyMeshDetail as b,buildRegions as f,calcGridSize as S,cloneRcConfig as R,createHeightfield as w,createNavMeshData as p,createRcConfig as C,erodeWalkableArea as M,filterLedgeSpans as _,filterLowHangingWalkableObstacles as x,filterWalkableLowHeightSpans as A,freeCompactHeightfield as
|
|
1
|
+
import{NavMeshCreateParams as e,Raw as t,RecastBuildContext as i,TriangleAreasArray as l,TrianglesArray as o,VerticesArray as r,allocCompactHeightfield as a,allocContourSet as s,allocHeightfield as n,allocPolyMesh as d,allocPolyMeshDetail as c,buildCompactHeightfield as h,buildContours as g,buildDistanceField as m,buildPolyMesh as u,buildPolyMeshDetail as b,buildRegions as f,calcGridSize as S,cloneRcConfig as R,createHeightfield as w,createNavMeshData as p,createRcConfig as C,erodeWalkableArea as M,filterLedgeSpans as _,filterLowHangingWalkableObstacles as x,filterWalkableLowHeightSpans as A,freeCompactHeightfield as y,freeContourSet as k,freeHeightfield as z,markWalkableTriangles as E,rasterizeTriangles as v,vec3 as T}from"@recast-navigation/core";import{dtIlog2 as B,dtNextPow2 as O}from"@recast-navigation/generators";import*as P from"three";export const buildConfig=({recastConfig:e,navMeshBounds:[t,i]})=>{const l=C(e),o=S(t,i,l.cs);l.width=o.width,l.height=o.height,l.minRegionArea=l.minRegionArea*l.minRegionArea,l.mergeRegionArea=l.mergeRegionArea*l.mergeRegionArea,l.tileSize=Math.floor(l.tileSize),l.borderSize=l.walkableRadius+3,l.width=l.tileSize+2*l.borderSize,l.height=l.tileSize+2*l.borderSize,l.detailSampleDist=l.detailSampleDist<.9?0:l.cs*l.detailSampleDist,l.detailSampleMaxError=l.ch*l.detailSampleMaxError;const r=Math.floor(l.tileSize),a=Math.floor((o.width+r-1)/r),s=Math.floor((o.height+r-1)/r),n=l.tileSize*l.cs,d=T.fromArray(t);let c=Math.min(Math.floor(B(O(a*s))),14);c>14&&(c=14);const h=22-c;return{config:l,orig:d,tileBits:c,polyBits:h,maxTiles:1<<c,maxPolysPerTile:1<<h,tcs:n,tileWidth:a,tileHeight:s}};export const buildTile=({positions:S,indices:C,navMeshBounds:T,recastConfig:B,tileX:O,tileY:H,tileBoundsMin:L,tileBoundsMax:G,keepIntermediates:D})=>{const $=new i,F=S,V=S.length/3,W=new r;W.copy(F);const I=C,K=C.length/3;(new o).copy(I);const X={tileX:O,tileY:H},Y=()=>{D||(X.compactHeightfield&&y(X.compactHeightfield),X.heightfield&&z(X.heightfield),X.contourSet&&k(X.contourSet))},N=e=>($.log(t.Module.RC_LOG_ERROR,e),Y(),{success:!1,error:e,tileIntermediates:X,buildContext:$}),{config:U}=buildConfig({recastConfig:B,navMeshBounds:T}),j=R(U),q=[...L],J=[...G];q[0]-=j.borderSize*j.cs,q[2]-=j.borderSize*j.cs,J[0]+=j.borderSize*j.cs,J[2]+=j.borderSize*j.cs,j.set_bmin(0,q[0]),j.set_bmin(1,q[1]),j.set_bmin(2,q[2]),j.set_bmax(0,J[0]),j.set_bmax(1,J[1]),j.set_bmax(2,J[2]),$.resetTimers(),$.startTimer(t.Module.RC_TIMER_TOTAL),$.log(t.Module.RC_LOG_PROGRESS,`Building tile at x: ${O}, y: ${H}`),$.log(t.Module.RC_LOG_PROGRESS,` - ${U.width} x ${U.height} cells`),$.log(t.Module.RC_LOG_PROGRESS,` - ${V/1e3}fK verts, ${K/1e3}K tris`);const Q=n();if(X.heightfield=Q,!w($,Q,j.width,j.height,q,J,j.cs,j.ch))return N("Could not create heightfield");const Z=((e,t,i)=>{const l=[],o=new P.Vector3,r=new P.Vector3,a=new P.Vector3,s=new P.Triangle;for(let n=0;n<t.length;n+=3){const d=t[n],c=t[n+1],h=t[n+2];o.fromArray(e,3*d),r.fromArray(e,3*c),a.fromArray(e,3*h),s.set(o,r,a),s.intersectsBox(i)&&l.push(d,c,h)}return l})(S,C,new P.Box3(new P.Vector3(q[0],q[1],q[2]),new P.Vector3(J[0],J[1],J[2]))),ee=Z.length/3,te=new o;te.copy(Z);const ie=new l;ie.resize(ee),E($,j.walkableSlopeAngle,W,V,te,ee,ie);const le=v($,W,V,te,ie,ee,Q,j.walkableClimb);if(ie.destroy(),!le)return N("Could not rasterize triangles");x($,j.walkableClimb,Q),_($,j.walkableHeight,j.walkableClimb,Q),A($,j.walkableHeight,Q);const oe=a();if(X.compactHeightfield=oe,!h($,j.walkableHeight,j.walkableClimb,Q,oe))return N("Could not build compact heightfield");if(D||(z(X.heightfield),X.heightfield=void 0),!M($,j.walkableRadius,oe))return N("Could not erode walkable area");if(!m($,oe))return N("Failed to build distance field");if(!f($,oe,j.borderSize,j.minRegionArea,j.mergeRegionArea))return N("Failed to build regions");const re=s();if(X.contourSet=re,!g($,oe,j.maxSimplificationError,j.maxEdgeLen,re,t.Module.RC_CONTOUR_TESS_WALL_EDGES))return N("Failed to create contours");const ae=d();if(!u($,re,j.maxVertsPerPoly,ae))return N("Failed to triangulate contours");if(0===ae.npolys())return Y(),{success:!0,tileIntermediates:X,buildContext:$};const se=c();if(!b($,ae,oe,j.detailSampleDist,j.detailSampleMaxError,se))return N("Failed to build detail mesh");D||(y(oe),X.compactHeightfield=void 0,k(re),X.contourSet=void 0);for(let e=0;e<ae.npolys();e++)ae.areas(e)==t.Recast.WALKABLE_AREA&&ae.setAreas(e,0),0==ae.areas(e)&&ae.setFlags(e,1);const ne=new e;ne.setPolyMeshCreateParams(ae),ne.setPolyMeshDetailCreateParams(se),ne.setWalkableHeight(j.walkableHeight),ne.setWalkableRadius(j.walkableRadius),ne.setWalkableClimb(j.walkableClimb),ne.setCellSize(j.cs),ne.setCellHeight(j.ch),ne.setBuildBvTree(!0),ne.setTileX(O),ne.setTileY(H);const de=p(ne);return de.success?($.log(t.Module.RC_LOG_PROGRESS,`>> Polymesh: ${ae.nverts()} vertices ${ae.npolys()} polygons`),Y(),{success:!0,data:de.navMeshData,tileIntermediates:X,buildContext:$}):N("Failed to create Detour navmesh data")};/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -14,7 +14,7 @@ export declare const MAX_NAV_MESH_WORKERS = 4;
|
|
|
14
14
|
type NavMeshWorkerResult = {
|
|
15
15
|
tileX: number;
|
|
16
16
|
tileY: number;
|
|
17
|
-
navMeshData
|
|
17
|
+
navMeshData?: Uint8Array;
|
|
18
18
|
error?: string;
|
|
19
19
|
};
|
|
20
20
|
export declare class NavMeshWorkerPool {
|
|
@@ -28,6 +28,7 @@ export declare class NavMeshWorkerPool {
|
|
|
28
28
|
private drain;
|
|
29
29
|
private createSlot;
|
|
30
30
|
}
|
|
31
|
+
export declare const getTileBounds: (navMeshBoundsMin: THREE.Vector3, navMeshBoundsMax: THREE.Vector3, tileX: number, tileY: number, tileWorldSize: number) => [min: Vector3Tuple, max: Vector3Tuple];
|
|
31
32
|
export declare class DynamicTiledNavMesh {
|
|
32
33
|
navMesh: NavMesh;
|
|
33
34
|
navMeshVersion: number;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{NavMesh as t,NavMeshParams as e,Raw as s,UnsignedCharArray as i,recastConfigDefaults as o,statusToReadableString as r}from"recast-navigation";import{buildConfig as n,buildTile as a}from"./build-tile.js";import{Subject as
|
|
1
|
+
import{NavMesh as t,NavMeshParams as e,Raw as s,UnsignedCharArray as i,recastConfigDefaults as o,statusToReadableString as r}from"recast-navigation";import{buildConfig as n,buildTile as a}from"./build-tile.js";import{Subject as l}from"rxjs";import{hasWorkerBuilder as h,createWorker as d}from"../../worker/index.js";import{hasSharedArrayBufferSupport as c}from"../../utils/buffer.js";export const MAX_NAV_MESH_WORKERS=4;export class NavMeshWorkerPool{constructor(t=d){this.workerFactory=t,this.slots=[],this.queue=[],this.workerLimit=0}schedule(t,e,s,i,o){this.workerLimit=Math.max(this.workerLimit,Math.min(4,Math.max(1,o))),this.queue.push({owner:t,data:e,transfer:s,onResult:i,cancelled:!1}),this.drain()}cancel(t){this.queue=this.queue.filter(e=>e.owner!==t);for(const e of this.slots)e.job?.owner===t&&(e.job.cancelled=!0)}drain(){for(;this.queue.length>0;){let t=this.slots.find(t=>null==t.job);if(null==t){if(this.slots.length>=this.workerLimit)return;t=this.createSlot(),this.slots.push(t)}const e=this.queue.shift();t.job=e,t.worker.postMessage(e.data,e.transfer)}}createSlot(){const t={worker:this.workerFactory()};return t.worker.onmessage=e=>{const s=t.job;t.job=void 0;try{null==s||s.cancelled||s.onResult(e)}finally{this.drain()}},t.worker.onerror=e=>{t.job=void 0,t.worker.terminate();const s=this.slots.indexOf(t);s>=0&&this.slots.splice(s,1),console.error("Navmesh worker failed",e),this.drain()},t}}const u=new NavMeshWorkerPool;export const getTileBounds=(t,e,s,i,o)=>[[t.x+s*o,t.y,t.z+i*o],[t.x+(s+1)*o,e.y,t.z+(i+1)*o]];export class DynamicTiledNavMesh{constructor(s){this.navMeshVersion=0,this.onNavMeshUpdate=new l;const i=s.navMeshBounds.min,r=s.navMeshBounds.max,a=[i.toArray(),r.toArray()],h=s.navMeshBounds.min;this.navMeshBoundsMin=i,this.navMeshBoundsMax=r,this.navMeshBounds=a,this.navMeshOrigin=h,this.cacheId=s.cacheId,this.workerLimit=Math.min(4,Math.max(0,s.workers));const d={...o,...s.recastConfig};this.recastConfig=d;const c=new t,{tileWidth:u,tileHeight:M,tcs:f,maxPolysPerTile:v}=n({recastConfig:d,navMeshBounds:a});this.tileWidth=u,this.tileHeight=M,this.tcs=f;const m=e.create({orig:h,tileWidth:d.tileSize*d.cs,tileHeight:d.tileSize*d.cs,maxTiles:s.maxTiles,maxPolys:v});c.initTiled(m),this.navMesh=c}onResult(t){if(t.data.error)return void console.error(t.data.error);const{tileX:e,tileY:o,navMeshData:n}=t.data;if(this.navMesh.removeTile(this.navMesh.getTileRefAt(e,o,0)),null==n)return this.navMeshVersion++,void this.onNavMeshUpdate.next([this.navMeshVersion,[e,o]]);const a=new i;a.copy(n);const l=this.navMesh.addTile(a,s.Module.DT_TILE_FREE_DATA,0);s.Detour.statusFailed(l.status)&&(console.error(s.Module.RC_LOG_WARNING,`Failed to add tile to nav mesh\n\ttx: ${e}, ty: ${o},status: ${r(l.status)} (${l.status})`),a.destroy()),this.navMeshVersion++,this.onNavMeshUpdate.next([this.navMeshVersion,[e,o]])}buildTile(t,e,[s,i],o=!1){const r=c&&t.buffer instanceof window.SharedArrayBuffer,n=r?t:new Float32Array(t),l=r?e:new Uint32Array(e),[d,v]=getTileBounds(this.navMeshBoundsMin,this.navMeshBoundsMax,s,i,this.tcs),m={tileX:s,tileY:i,tileBoundsMin:d,tileBoundsMax:v,recastConfig:this.recastConfig,navMeshBounds:this.navMeshBounds,keepIntermediates:!1,positions:n,indices:l};return this.workerLimit>0&&h()?(u.schedule(this,m,r?void 0:[n.buffer,l.buffer],t=>this.onResult(t),this.workerLimit),Promise.resolve()):new Promise(t=>{requestIdleCallback(()=>{const e=this.cacheId+JSON.stringify({tileX:m.tileX,tileY:m.tileY});if(o){const s=localStorage.getItem(e);if(null!=s){const e=f(s);return this.onResult({data:{tileX:m.tileX,tileY:m.tileY,navMeshData:e}}),void t({})}}else localStorage.removeItem(e);const s=a(m);if(!s.success)return console.error("error"in s?s.error:"Unknown navmesh tile build error"),void t(s);if(!s.data)return localStorage.removeItem(e),this.onResult({data:{tileX:m.tileX,tileY:m.tileY}}),void t(s);const i=s.data.toTypedArray();localStorage.setItem(e,M(i)),this.onResult({data:{tileX:m.tileX,tileY:m.tileY,navMeshData:i}}),t(s)})})}buildAllTiles(t,e){const{tileWidth:s,tileHeight:i}=this;for(let o=0;o<i;o++)for(let i=0;i<s;i++)this.buildTile(t,e,[i,o])}getTileForWorldPosition(t){return[Math.floor((t.x-this.navMeshBoundsMin.x)/this.tcs),Math.floor((t.z-this.navMeshBoundsMin.z)/this.tcs)]}getTilesForBounds(t){const e=this.getTileForWorldPosition(t.min),s=this.getTileForWorldPosition(t.max),i=[];for(let t=e[1];t<=s[1];t++)for(let o=e[0];o<=s[0];o++)i.push([o,t]);return i}destroy(){u.cancel(this),this.navMesh.destroy(),this.onNavMeshUpdate.complete()}}const M=function(t){return btoa(String.fromCharCode(...t))},f=function(t){if(!t)return null;const e=atob(t),s=e.length,i=new Uint8Array(s);for(let t=0;t<s;t++)i[t]=e.charCodeAt(t);return i};/*
|
|
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{init as e}from"recast-navigation";import{buildTile as t}from"./build-tile.js";let
|
|
1
|
+
import{init as e}from"recast-navigation";import{buildTile as t}from"./build-tile.js";let i=!1;const s=[];export function setupNavMeshWorker(){e().then(()=>{i=!0;for(const e of s)o(e)}),self.onmessage=e=>{i?o(e.data):s.push(e.data)}}const o=e=>{const i=t(e);if(!i.success){const t="error"in i?i.error:"Unknown navmesh tile build error";return console.error("NavMesh tile build failed",{tileX:e.tileX,tileY:e.tileY,error:t,positions:e.positions.length,indices:e.indices.length}),void self.postMessage({tileX:e.tileX,tileY:e.tileY,error:t})}if(!i.data)return void self.postMessage({tileX:e.tileX,tileY:e.tileY});const s=i.data.toTypedArray();i.data.destroy(),self.postMessage({tileX:e.tileX,tileY:e.tileY,navMeshData:s},[s.buffer])};/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -8,6 +8,8 @@ type SupportedCamera = THREE.PerspectiveCamera | THREE.OrthographicCamera;
|
|
|
8
8
|
export type AntiAliasingMode = 'off' | 'fxaa' | 'smaa' | 'taa';
|
|
9
9
|
export type ExperimentalWebGpuRenderGraphExtension = ((graph: FrameGraphBuilder, color: string) => void) & {
|
|
10
10
|
getSceneTextureReads?: () => readonly string[];
|
|
11
|
+
/** Pre-scene extensions run before shadow, depth and color geometry passes. */
|
|
12
|
+
stage?: 'beforeScene' | 'afterScene';
|
|
11
13
|
};
|
|
12
14
|
export interface ExperimentalWebGpuPostProcessingOptions {
|
|
13
15
|
readonly bloom?: false | BloomOptions;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as e from"three";import{NodeMaterialWebGpuResolver as t}from"./node-material-webgpu-resolver.js";import{float as i,varyingTransformed as n,vec4 as o}from"three-shader-graph";import{NodeShaderMaterial as r}from"../shader-nodes/index.js";import{sceneMapUniformName as s}from"../shader-nodes/scene-sample.js";import{MATERIAL_FLAG_ALPHA_TEST as a,MATERIAL_FLAG_RECEIVE_DECALS as l}from"../shader-nodes/material-flags.js";import{createMotionVectorOutput as d}from"./motion-vector-node.js";export class ExperimentalWebGpuBackend{constructor(e,t,i,n,o,r,s,a,l,d,c,h,u,p,m,g,f,v,S){this.renderer=e,this.fallbackMaterial=t,this.nodeMaterialResolver=i,this.gBufferFallbackSource=n,this.bloom=o,this.fxaa=r,this.smaa=s,this.gtao=a,this.ssr=l,this.taa=d,this.motionBlur=c,this.volumetricFog=h,this.colorGrading=u,this.depthOfField=p,this.outline=m,this.postProcessEffects=g,this.renderGraphExtensions=f,this.antiAliasingState=v,this.renderScale=1,this.pmremGenerator=null,this.pmremResults=new WeakMap,this.ownedPmremResults=new Set,this.warnedUnsupportedPostProcessStages=new Set,this.canvas=S}static async create(u={}){const p=await import("@hology/webgpu-renderer"),m=document.createElement("canvas");m.dataset.hologyRenderer="webgpu-experimental";const g=new p.WGSLShaderMaterial({vertexShader:c,fragmentShader:h});g.side=p.DoubleSide;const f=p,v=null!=u.motionBlur&&!1!==u.motionBlur,S=!1!==u.gtao&&(null==u.gtao||!1!==u.gtao.temporal),P=!1!==u.volumetricFog&&(null==u.volumetricFog||!1!==u.volumetricFog.temporal),x=null!=(w=u).antiAliasing?w.antiAliasing:null!=w.taa&&!1!==w.taa?"taa":null!=w.smaa&&!1!==w.smaa?"smaa":null!=w.fxaa&&!1!==w.fxaa?"fxaa":!1===w.taa?"off":"taa";var w;const b=null!=u.antiAliasing,y=v||S||P||(b||"taa"===x),F=new t(f,(e,t)=>"opaque"===t?[e.outputColor.xyz.rgba(i(e.alphaTest>0?a:0).add(!1===e.userData.receiveDecals?0:l)),o((e.outputNormal??n.normal).multiplyScalar(.5).addScalar(.5),e.outputRoughness??i(1)),...y?[d(e)]:[]]:void 0),C=new e.MeshStandardMaterial({color:8688803,roughness:1});if(null==F.resolve(C,"opaque"))throw g.dispose(),C.dispose(),new Error("Failed to compile the WebGPU G-buffer fallback material.");const L=!1===u.gtao?null:new p.GTAOEffect({normalSpace:"view",...p.GTAO_PRESETS.high,...u.gtao??{}}),M=!1===u.ssr||null==u.ssr?null:new p.SSREffect({normalSpace:"view",resolutionScale:.5,...u.ssr??{}}),O=!1===u.bloom?null:new p.BloomEffect({threshold:1,strength:.9,radius:.2,...u.bloom??{}}),E=b||!1!==u.fxaa&&"fxaa"===x?new p.FXAAEffect(!1===u.fxaa?{}:u.fxaa):null,G=b||!1!==u.smaa&&"smaa"===x?new p.SMAAEffect(!1===u.smaa?{}:u.smaa):null,R=b||!1!==u.taa&&"taa"===x?new p.TemporalAAEffect({renderScale:1,...!1===u.taa?{}:u.taa}):null,D={mode:x},B=!1===u.motionBlur||null==u.motionBlur?null:new p.MotionBlurEffect(u.motionBlur),A=u.volumetricFog||void 0,T=!1===u.volumetricFog?null:new p.VolumetricFogEffect(void 0,{...p.VOLUMETRIC_FOG_PRESETS.high,maxDistance:64,spatialJitter:!0,scatteringIntensity:Math.PI,...A,temporal:!1!==A?.temporal&&{...p.VOLUMETRIC_FOG_PRESETS.high.temporal,...A?.temporal},blur:!1!==A?.blur&&{...!1===p.VOLUMETRIC_FOG_PRESETS.high.blur?{}:p.VOLUMETRIC_FOG_PRESETS.high.blur,depthAwareUpsample:!0,...A?.blur}}),V=!1===u.colorGrading?null:new p.ColorGradingEffect({enabled:!1,...u.colorGrading??{}}),W=!1===u.depthOfField?null:new p.DepthOfFieldEffect({enabled:!1,...u.depthOfField??{}}),k=!1===u.outline?null:new p.OutlineEffect({enabled:!1,...u.outline??{}}),I=[],N=new WeakMap,U=new Set,j=[],H=(e,t,i)=>{let n=i;const o=I.filter(e=>e.enabled&&e.stage===t).sort((e,t)=>e.priority-t.priority||e.id-t.id);for(const t of o){if(!(t.material instanceof r)){const e=`${t.id}:material`;U.has(e)||(U.add(e),console.warn(`[Hology WebGPU experiment] Skipping custom post-process effect ${t.id}: only NodeShaderMaterial effects are supported.`));continue}try{let i=N.get(t.material);null!=i&&i.version===t.material.version||(i={version:t.material.version,shader:F.compileFullscreen(t.material)},N.set(t.material,i));const o=e.createColorTarget(`hologyCustomPostProcess${t.id}`);e.addNodeFullscreenPass({name:`HologyCustomPostProcess${t.id}`,shader:i.shader,inputOverrides:{[s]:n},outputs:o}),n=o}catch(e){const i=`${t.id}:compile:${t.material.version}`;U.has(i)||(U.add(i),console.warn(`[Hology WebGPU experiment] Skipping custom post-process effect ${t.id}: ${e instanceof Error?e.message:String(e)}`,e))}}return n},$=new p.WebGpuRenderer({canvas:m,fallbackMaterial:g,materialResolver:(e,t)=>F.resolve(e,t),invokeObjectRenderCallbacks:!1,hiZOcclusionCulling:!0,renderGraphProfiling:!1,sampleCount:1,multiDrawIndirect:!1});$.setRenderScale("taa"===x?R?.renderScale??1:1),$.setRenderGraphBuilder((e,t)=>{const i=new p.FrameGraphBuilder(t),n=i.createColorTarget("hologyNormalRoughness",{format:"rgba16float"}),o=y?i.createColorTarget("hologyMotionVectors",{format:"rgba16float"}):null;i.addDirectionalShadowPass(),i.addPointShadowPass(),i.addSpotShadowPass(),i.addDepthPrepass(),i.addHiZPasses(),i.addVisibilityPass(),i.addMaterialPass({name:"OpaqueGBufferPass",outputs:null==o?[i.sceneColor,n]:[i.sceneColor,n,o],drawStage:"opaque",loadDepth:!0}),i.addHiZHistoryPass("OpaqueGBufferPass");let r=i.sceneColor;if(L?.enabled){const e=L.addPasses(i,{depth:i.mainDepth,normal:n,velocity:o??void 0});r=L.addCompositePass(i,r,e)}null!=M&&(r=M.addPasses(i,{sceneColor:r,depth:i.mainDepth,normalRoughness:n}));const s=new Set(j.flatMap(e=>e.getSceneTextureReads?.()??[]));i.addSceneTextureCopyPass(r,{sceneColor:s.has("sceneColorSnapshot"),sceneDepth:s.has("sceneDepthSnapshot")}),i.addLatePass(r);for(const e of j)e(i,r);r=H(i,"beforeFog",r),null!=T&&(r=T.addPasses(i,{sceneColor:r,depth:i.mainDepth,velocity:o??void 0,volumeObjects:t.fogVolumeObjects,lights:t.extractedLights})),null!=B&&null!=o&&(r=B.addPass(i,r,o,i.mainDepth)),null!=O&&(r=O.addPass(i,r)),r=H(i,"beforeOutline",r),null!=k&&(r=k.addPass(i,r)),r=H(i,"beforeDepthOfField",r),null!=W&&(r=W.addPass(i,r,i.mainDepth)),r=H(i,"beforeColorAdjustment",r),null!=V&&(r=V.addPass(i,r)),r=H(i,"beforeAntiAliasing",r);let a="taa"===D.mode&&null!=R?R.addPass(i,r,i.mainDepth,o??void 0):r;"smaa"===D.mode&&null!=G?a=G.addPass(i,a):"fxaa"===D.mode&&null!=E&&(a=E.addPass(i,a)),a=H(i,"beforeOutput",a),i.present(a)});try{return await $.initialize(),new ExperimentalWebGpuBackend($,g,F,C,O,E,G,L,M,R,B,T,V,W,k,I,j,D,m)}catch(e){throw $.destroy(),g.dispose(),F.dispose(),C.dispose(),L?.dispose(),G?.dispose(),B?.dispose(),T?.dispose(),k?.dispose(),e}}static supportsCamera(t){return t instanceof e.PerspectiveCamera||t instanceof e.OrthographicCamera}setSize(e,t,i,n=1){const o=this.canvas.width,r=this.canvas.height,s=Math.min(1,Math.max(.25,n)),a=s!==this.renderScale;this.renderScale=s,this.renderer.setRenderScale(s),this.taa?.setRenderScale(s),this.renderer.setSize(e,t,i),(a||this.canvas.width!==o||this.canvas.height!==r)&&this.resetViewHistory()}initTexture(e){this.renderer.initTexture(e)}setPostProcessEffects(e){this.postProcessEffects.splice(0,this.postProcessEffects.length,...e);for(const t of e){if("beforeLut"!==t.stage)continue;const e=`${t.id}:${t.stage}`;this.warnedUnsupportedPostProcessStages.has(e)||(this.warnedUnsupportedPostProcessStages.add(e),console.warn(`[Hology WebGPU experiment] Skipping custom post-process effect ${t.id}: stage "${t.stage}" has no native WebGPU anchor.`))}}getDevice(){return this.renderer.getDevice()}setRenderGraphExtensions(e){this.renderGraphExtensions.length=0;for(const t of e)this.renderGraphExtensions.push(t)}setEnableOutlines(e){null!=this.outline&&(this.outline.enabled=e)}setDynamicBatchingEnabled(e){this.renderer.setDynamicBatchingEnabled(e)}setSelectedObjects(e){this.outline?.setSelectedObjects(e)}setSelectedObjectInstances(e){this.outline?.setSelectedInstances(e)}setCascadedShadowRanges(e,t=[]){this.renderer.setCascadedShadowRanges(e,t),this.volumetricFog?.setCascadedDirectionalLights(t)}setWorldEnvironment(t){if(null==t)return void this.nodeMaterialResolver.setWorldEnvironment(null);let i=t.source;if(i.mapping!==e.CubeUVReflectionMapping){let e=this.pmremResults.get(i);null==e&&(this.pmremGenerator??(this.pmremGenerator=this.renderer.createPMREMGenerator()),e=this.pmremGenerator.fromEquirectangular(i),this.pmremResults.set(i,e),this.ownedPmremResults.add(e)),i=e.texture}this.nodeMaterialResolver.setWorldEnvironment({texture:i,intensity:t.intensity})}resetViewHistory(){this.renderer.resetViewHistory(),this.taa?.reset(),this.motionBlur?.reset(),this.gtao?.reset(),this.volumetricFog?.reset()}async compile(e,t){await this.renderer.compileAsync(e,t)}createStaticDrawSet(e,t,i=!1){this.renderer.setHiZOcclusionCullingEnabled(!0);const n=this.renderer.createStaticDrawSet(e,{label:t,onUnsupported:"skip",allowUnsortedLateDraws:i});return 0===n.drawCount?(n.dispose(),null):n}render(e,t,i){const n="taa"===this.antiAliasingState.mode?this.taa:null;this.volumetricFog?.setScene(e),this.renderer.setMotionVectorProjection(t.projectionMatrix),this.motionBlur?.prepareCamera(t,this.canvas.width,this.canvas.height,i),this.motionBlur?.requiresRendererHistoryReset()&&(this.renderer.resetViewHistory(),this.gtao?.reset(),this.volumetricFog?.reset()),n?.prepareCamera(t,this.canvas.width,this.canvas.height),this.motionBlur?.setJitterUv(n?.currentJitterUv.x??0,n?.currentJitterUv.y??0);let o=!1;try{this.renderer.render(e,t,i),o=!0}finally{n?.finishCamera(t),o&&this.motionBlur?.commitCamera()}}setMotionBlurOptions(e){null!=this.motionBlur&&Object.assign(this.motionBlur.options,e)}setGTAOOptions(e){this.gtao?.setOptions(e)}setAntiAliasingMode(e){e!==this.antiAliasingState.mode&&("taa"===e&&null==this.taa||"smaa"===e&&null==this.smaa||"fxaa"===e&&null==this.fxaa?console.warn(`[Hology WebGPU experiment] Anti-aliasing mode "${e}" was not initialized. Set webgpuPostProcessing.antiAliasing in the initial rendering configuration to enable runtime switching.`):(this.antiAliasingState.mode=e,this.resetTemporalHistory()))}setGTAOQuality(e){this.gtao?.applyPreset(e)}setVolumetricFogOptions(e){this.volumetricFog?.setOptions(e)}setVolumetricFogQuality(e){this.volumetricFog?.applyPreset(e)}pushMotionBlurOverride(e){return this.motionBlur?.pushOverride(e)??null}invalidateObjectMotion(e){this.renderer.invalidateObjectMotion(e)}getLastFrameProfile(){return this.renderer.getLastFrameProfile()}setPostProcessState(e){null!=this.colorGrading&&(Object.assign(this.colorGrading.options,e??{enabled:!1}),this.colorGrading.options.enabled=!0===e?.enabled),null!=this.depthOfField&&(this.depthOfField.options.enabled=null!=e?.depthFocus||null!=e?.depthAperture||null!=e?.depthMaxBlur,this.depthOfField.options.focus=e?.depthFocus,this.depthOfField.options.aperture=e?.depthAperture,this.depthOfField.options.maxBlur=e?.depthMaxBlur)}setProfilingEnabled(e){this.renderer.setRenderGraphProfilingEnabled(e)}resetTemporalHistory(){this.taa?.reset(),this.motionBlur?.reset(),this.gtao?.reset(),this.volumetricFog?.reset(),this.renderer.resetViewHistory()}inspect(e,t){return inspectWebGpuScene(e,t,this.nodeMaterialResolver)}destroy(){this.nodeMaterialResolver.dispose(),this.gBufferFallbackSource.dispose(),this.gtao?.dispose(),this.smaa?.dispose(),this.motionBlur?.dispose(),this.volumetricFog?.dispose();for(const e of this.ownedPmremResults)e.dispose();this.ownedPmremResults.clear(),this.renderer.destroy(),this.fallbackMaterial.dispose()}}export function inspectWebGpuScene(e,t,i){const n={meshes:0,skinnedMeshes:0,instancedMeshes:0,batchedMeshes:0,sprites:0,points:0,lines:0,missingPositionGeometry:0,transparentMaterials:0,shaderMaterialsReplaced:0,materialsReplaced:0,unsupportedCamera:ExperimentalWebGpuBackend.supportsCamera(t)?0:1};return e.traverse(e=>{if(e.isSprite)return void n.sprites++;if(e.isPoints)return void n.points++;if(e.isLine)return void n.lines++;if(!e.isMesh)return;const t=e;t.isBatchedMesh?n.batchedMeshes++:t.isInstancedMesh?n.instancedMeshes++:t.isSkinnedMesh?n.skinnedMeshes++:n.meshes++,null==t.geometry?.getAttribute("position")&&n.missingPositionGeometry++;const o=Array.isArray(t.material)?t.material:[t.material];for(const e of o){if(null==e)continue;e.transparent&&n.transparentMaterials++;const t=!0===e.isWGSLShaderMaterial||!0===e.isLineMaterial,o=!t&&!0===i?.canResolve(e);t||o||(n.materialsReplaced++,e.isShaderMaterial&&n.shaderMaterialsReplaced++)}}),n}export function formatWebGpuSceneCapabilitySummary(e){return`[Hology WebGPU experiment] ${JSON.stringify(e)}`}const c="\nstruct VertexInput {\n @location(0) position: vec3<f32>,\n @location(1) normal: vec3<f32>,\n @location(2) uv: vec2<f32>,\n @location(3) color: vec4<f32>,\n#ifdef USE_SKINNING\n @location(4) skinIndex: vec4<u32>,\n @location(5) skinWeight: vec4<f32>,\n#endif\n @builtin(instance_index) instanceIndex: u32,\n};\n\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) worldPosition: vec3<f32>,\n @location(1) worldNormal: vec3<f32>,\n @location(2) color: vec4<f32>,\n};\n\n@vertex\nfn vsMain(input: VertexInput) -> VertexOutput {\n var output: VertexOutput;\n#ifdef USE_SKINNING\n let localPosition = rendererSkinPosition(input.position, input.skinIndex, input.skinWeight);\n let localNormal = rendererSkinNormal(input.normal, input.skinIndex, input.skinWeight);\n#else\n let localPosition = input.position;\n let localNormal = input.normal;\n#endif\n let world = rendererWorldPosition(localPosition, input.instanceIndex);\n output.position = rendererClipPosition(localPosition, input.instanceIndex);\n output.worldPosition = world.xyz;\n output.worldNormal = rendererWorldNormal(localNormal, input.instanceIndex);\n output.color = input.color * rendererObjectColor(input.instanceIndex);\n return output;\n}\n",h="\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) worldPosition: vec3<f32>,\n @location(1) worldNormal: vec3<f32>,\n @location(2) color: vec4<f32>,\n};\n\nfn distanceAttenuation(distanceToLight: f32, cutoffDistance: f32, decayExponent: f32) -> f32 {\n var distanceFalloff = 1.0 / max(pow(distanceToLight, decayExponent), 0.01);\n if (cutoffDistance > 0.0) {\n let ratio = distanceToLight / cutoffDistance;\n let cutoff = clamp(1.0 - ratio * ratio * ratio * ratio, 0.0, 1.0);\n distanceFalloff *= cutoff * cutoff;\n }\n return distanceFalloff;\n}\n\n@fragment\nfn fsMain(input: VertexOutput) -> @location(0) vec4<f32> {\n let normal = normalize(input.worldNormal);\n let normalTint = normal * 0.5 + vec3<f32>(0.5);\n let base = mix(vec3<f32>(0.52, 0.58, 0.64), normalTint, 0.22) * input.color.rgb;\n var lighting = rendererIndirectDiffuse(normal) + vec3<f32>(0.08);\n var directionalLighting = vec3<f32>(0.0);\n let directionalCount = rendererDirectionalLightCount();\n\n for (var index: u32 = 0u; index < directionalCount; index = index + 1u) {\n let lightDirection = rendererDirectionalLightDirectionForLight(index);\n let shadow = rendererSampleDirectionalShadowForLightWithNormal(index, input.worldPosition, normal);\n directionalLighting += rendererDirectionalLightColorForLight(index) * max(dot(normal, lightDirection), 0.0) * shadow;\n }\n if (directionalCount > 0u) {\n lighting += directionalLighting / f32(directionalCount);\n }\n\n for (var index: u32 = 0u; index < rendererPointLightCount(); index = index + 1u) {\n let lightVector = rendererPointLightPositionForLight(index) - input.worldPosition;\n let distanceToLight = length(lightVector);\n let lightDirection = normalize(lightVector);\n let shadow = rendererSamplePointShadowForLightWithNormal(index, input.worldPosition, normal);\n lighting += rendererPointLightColorForLight(index) * max(dot(normal, lightDirection), 0.0) * distanceAttenuation(distanceToLight, rendererPointLightRangeForLight(index), rendererPointLightDecayForLight(index)) * shadow;\n }\n\n for (var index: u32 = 0u; index < rendererSpotLightCount(); index = index + 1u) {\n let lightVector = rendererSpotLightPositionForLight(index) - input.worldPosition;\n let distanceToLight = length(lightVector);\n let lightDirection = normalize(lightVector);\n let angleCos = dot(-lightDirection, rendererSpotLightDirectionForLight(index));\n let cone = smoothstep(rendererSpotLightConeCosForLight(index), rendererSpotLightPenumbraCosForLight(index), angleCos);\n let shadow = rendererSampleSpotShadowForLightWithNormal(index, input.worldPosition, normal);\n lighting += rendererSpotLightColorForLight(index) * max(dot(normal, lightDirection), 0.0) * cone * distanceAttenuation(distanceToLight, rendererSpotLightRangeForLight(index), rendererSpotLightDecayForLight(index)) * shadow;\n }\n\n return vec4<f32>(base * max(lighting, vec3<f32>(0.08)), input.color.a);\n}\n";/*
|
|
1
|
+
import*as e from"three";import{NodeMaterialWebGpuResolver as t}from"./node-material-webgpu-resolver.js";import{float as i,varyingTransformed as n,vec4 as o}from"three-shader-graph";import{NodeShaderMaterial as r}from"../shader-nodes/index.js";import{sceneMapUniformName as s}from"../shader-nodes/scene-sample.js";import{MATERIAL_FLAG_ALPHA_TEST as a,MATERIAL_FLAG_RECEIVE_DECALS as l}from"../shader-nodes/material-flags.js";import{createMotionVectorOutput as d}from"./motion-vector-node.js";export class ExperimentalWebGpuBackend{constructor(e,t,i,n,o,r,s,a,l,d,c,h,u,p,m,g,f,v,S){this.renderer=e,this.fallbackMaterial=t,this.nodeMaterialResolver=i,this.gBufferFallbackSource=n,this.bloom=o,this.fxaa=r,this.smaa=s,this.gtao=a,this.ssr=l,this.taa=d,this.motionBlur=c,this.volumetricFog=h,this.colorGrading=u,this.depthOfField=p,this.outline=m,this.postProcessEffects=g,this.renderGraphExtensions=f,this.antiAliasingState=v,this.renderScale=1,this.pmremGenerator=null,this.pmremResults=new WeakMap,this.ownedPmremResults=new Set,this.warnedUnsupportedPostProcessStages=new Set,this.canvas=S}static async create(u={}){const p=await import("@hology/webgpu-renderer"),m=document.createElement("canvas");m.dataset.hologyRenderer="webgpu-experimental";const g=new p.WGSLShaderMaterial({vertexShader:c,fragmentShader:h});g.side=p.DoubleSide;const f=p,v=null!=u.motionBlur&&!1!==u.motionBlur,S=!1!==u.gtao&&(null==u.gtao||!1!==u.gtao.temporal),P=!1!==u.volumetricFog&&(null==u.volumetricFog||!1!==u.volumetricFog.temporal),x=null!=(w=u).antiAliasing?w.antiAliasing:null!=w.taa&&!1!==w.taa?"taa":null!=w.smaa&&!1!==w.smaa?"smaa":null!=w.fxaa&&!1!==w.fxaa?"fxaa":!1===w.taa?"off":"taa";var w;const b=null!=u.antiAliasing,y=v||S||P||(b||"taa"===x),F=new t(f,(e,t)=>"opaque"===t?[e.outputColor.xyz.rgba(i(e.alphaTest>0?a:0).add(!1===e.userData.receiveDecals?0:l)),o((e.outputNormal??n.normal).multiplyScalar(.5).addScalar(.5),e.outputRoughness??i(1)),...y?[d(e)]:[]]:void 0),C=new e.MeshStandardMaterial({color:8688803,roughness:1});if(null==F.resolve(C,"opaque"))throw g.dispose(),C.dispose(),new Error("Failed to compile the WebGPU G-buffer fallback material.");const L=!1===u.gtao?null:new p.GTAOEffect({normalSpace:"view",...p.GTAO_PRESETS.high,...u.gtao??{}}),M=!1===u.ssr||null==u.ssr?null:new p.SSREffect({normalSpace:"view",resolutionScale:.5,...u.ssr??{}}),O=!1===u.bloom?null:new p.BloomEffect({threshold:1,strength:.9,radius:.2,...u.bloom??{}}),E=b||!1!==u.fxaa&&"fxaa"===x?new p.FXAAEffect(!1===u.fxaa?{}:u.fxaa):null,G=b||!1!==u.smaa&&"smaa"===x?new p.SMAAEffect(!1===u.smaa?{}:u.smaa):null,R=b||!1!==u.taa&&"taa"===x?new p.TemporalAAEffect({renderScale:1,...!1===u.taa?{}:u.taa}):null,D={mode:x},B=!1===u.motionBlur||null==u.motionBlur?null:new p.MotionBlurEffect(u.motionBlur),A=u.volumetricFog||void 0,T=!1===u.volumetricFog?null:new p.VolumetricFogEffect(void 0,{...p.VOLUMETRIC_FOG_PRESETS.high,maxDistance:64,spatialJitter:!0,scatteringIntensity:Math.PI,...A,temporal:!1!==A?.temporal&&{...p.VOLUMETRIC_FOG_PRESETS.high.temporal,...A?.temporal},blur:!1!==A?.blur&&{...!1===p.VOLUMETRIC_FOG_PRESETS.high.blur?{}:p.VOLUMETRIC_FOG_PRESETS.high.blur,depthAwareUpsample:!0,...A?.blur}}),V=!1===u.colorGrading?null:new p.ColorGradingEffect({enabled:!1,...u.colorGrading??{}}),W=!1===u.depthOfField?null:new p.DepthOfFieldEffect({enabled:!1,...u.depthOfField??{}}),k=!1===u.outline?null:new p.OutlineEffect({enabled:!1,...u.outline??{}}),I=[],N=new WeakMap,U=new Set,j=[],H=(e,t,i)=>{let n=i;const o=I.filter(e=>e.enabled&&e.stage===t).sort((e,t)=>e.priority-t.priority||e.id-t.id);for(const t of o){if(!(t.material instanceof r)){const e=`${t.id}:material`;U.has(e)||(U.add(e),console.warn(`[Hology WebGPU experiment] Skipping custom post-process effect ${t.id}: only NodeShaderMaterial effects are supported.`));continue}try{let i=N.get(t.material);null!=i&&i.version===t.material.version||(i={version:t.material.version,shader:F.compileFullscreen(t.material)},N.set(t.material,i));const o=e.createColorTarget(`hologyCustomPostProcess${t.id}`);e.addNodeFullscreenPass({name:`HologyCustomPostProcess${t.id}`,shader:i.shader,inputOverrides:{[s]:n},outputs:o}),n=o}catch(e){const i=`${t.id}:compile:${t.material.version}`;U.has(i)||(U.add(i),console.warn(`[Hology WebGPU experiment] Skipping custom post-process effect ${t.id}: ${e instanceof Error?e.message:String(e)}`,e))}}return n},$=new p.WebGpuRenderer({canvas:m,fallbackMaterial:g,materialResolver:(e,t)=>F.resolve(e,t),invokeObjectRenderCallbacks:!1,hiZOcclusionCulling:!0,renderGraphProfiling:!1,sampleCount:1,multiDrawIndirect:!1});$.setRenderScale("taa"===x?R?.renderScale??1:1),$.setRenderGraphBuilder((e,t)=>{const i=new p.FrameGraphBuilder(t),n=i.createColorTarget("hologyNormalRoughness",{format:"rgba16float"}),o=y?i.createColorTarget("hologyMotionVectors",{format:"rgba16float"}):null;for(const e of j)"beforeScene"===e.stage&&e(i,i.sceneColor);i.addDirectionalShadowPass(),i.addPointShadowPass(),i.addSpotShadowPass(),i.addDepthPrepass(),i.addHiZPasses(),i.addVisibilityPass(),i.addMaterialPass({name:"OpaqueGBufferPass",outputs:null==o?[i.sceneColor,n]:[i.sceneColor,n,o],drawStage:"opaque",loadDepth:!0}),i.addHiZHistoryPass("OpaqueGBufferPass");let r=i.sceneColor;if(L?.enabled){const e=L.addPasses(i,{depth:i.mainDepth,normal:n,velocity:o??void 0});r=L.addCompositePass(i,r,e)}null!=M&&(r=M.addPasses(i,{sceneColor:r,depth:i.mainDepth,normalRoughness:n}));const s=new Set(j.flatMap(e=>e.getSceneTextureReads?.()??[]));i.addSceneTextureCopyPass(r,{sceneColor:s.has("sceneColorSnapshot"),sceneDepth:s.has("sceneDepthSnapshot")}),i.addLatePass(r);for(const e of j)"beforeScene"!==e.stage&&e(i,r);r=H(i,"beforeFog",r),null!=T&&(r=T.addPasses(i,{sceneColor:r,depth:i.mainDepth,velocity:o??void 0,volumeObjects:t.fogVolumeObjects,lights:t.extractedLights})),null!=B&&null!=o&&(r=B.addPass(i,r,o,i.mainDepth)),null!=O&&(r=O.addPass(i,r)),r=H(i,"beforeOutline",r),null!=k&&(r=k.addPass(i,r)),r=H(i,"beforeDepthOfField",r),null!=W&&(r=W.addPass(i,r,i.mainDepth)),r=H(i,"beforeColorAdjustment",r),null!=V&&(r=V.addPass(i,r)),r=H(i,"beforeAntiAliasing",r);let a="taa"===D.mode&&null!=R?R.addPass(i,r,i.mainDepth,o??void 0):r;"smaa"===D.mode&&null!=G?a=G.addPass(i,a):"fxaa"===D.mode&&null!=E&&(a=E.addPass(i,a)),a=H(i,"beforeOutput",a),i.present(a)});try{return await $.initialize(),new ExperimentalWebGpuBackend($,g,F,C,O,E,G,L,M,R,B,T,V,W,k,I,j,D,m)}catch(e){throw $.destroy(),g.dispose(),F.dispose(),C.dispose(),L?.dispose(),G?.dispose(),B?.dispose(),T?.dispose(),k?.dispose(),e}}static supportsCamera(t){return t instanceof e.PerspectiveCamera||t instanceof e.OrthographicCamera}setSize(e,t,i,n=1){const o=this.canvas.width,r=this.canvas.height,s=Math.min(1,Math.max(.25,n)),a=s!==this.renderScale;this.renderScale=s,this.renderer.setRenderScale(s),this.taa?.setRenderScale(s),this.renderer.setSize(e,t,i),(a||this.canvas.width!==o||this.canvas.height!==r)&&this.resetViewHistory()}initTexture(e){this.renderer.initTexture(e)}setPostProcessEffects(e){this.postProcessEffects.splice(0,this.postProcessEffects.length,...e);for(const t of e){if("beforeLut"!==t.stage)continue;const e=`${t.id}:${t.stage}`;this.warnedUnsupportedPostProcessStages.has(e)||(this.warnedUnsupportedPostProcessStages.add(e),console.warn(`[Hology WebGPU experiment] Skipping custom post-process effect ${t.id}: stage "${t.stage}" has no native WebGPU anchor.`))}}getDevice(){return this.renderer.getDevice()}setRenderGraphExtensions(e){this.renderGraphExtensions.length=0;for(const t of e)this.renderGraphExtensions.push(t)}setEnableOutlines(e){null!=this.outline&&(this.outline.enabled=e)}setDynamicBatchingEnabled(e){this.renderer.setDynamicBatchingEnabled(e)}setSelectedObjects(e){this.outline?.setSelectedObjects(e)}setSelectedObjectInstances(e){this.outline?.setSelectedInstances(e)}setCascadedShadowRanges(e,t=[]){this.renderer.setCascadedShadowRanges(e,t),this.volumetricFog?.setCascadedDirectionalLights(t)}setWorldEnvironment(t){if(null==t)return void this.nodeMaterialResolver.setWorldEnvironment(null);let i=t.source;if(i.mapping!==e.CubeUVReflectionMapping){let e=this.pmremResults.get(i);null==e&&(this.pmremGenerator??(this.pmremGenerator=this.renderer.createPMREMGenerator()),e=this.pmremGenerator.fromEquirectangular(i),this.pmremResults.set(i,e),this.ownedPmremResults.add(e)),i=e.texture}this.nodeMaterialResolver.setWorldEnvironment({texture:i,intensity:t.intensity})}resetViewHistory(){this.renderer.resetViewHistory(),this.taa?.reset(),this.motionBlur?.reset(),this.gtao?.reset(),this.volumetricFog?.reset()}async compile(e,t){await this.renderer.compileAsync(e,t)}createStaticDrawSet(e,t,i=!1){this.renderer.setHiZOcclusionCullingEnabled(!0);const n=this.renderer.createStaticDrawSet(e,{label:t,onUnsupported:"skip",allowUnsortedLateDraws:i});return 0===n.drawCount?(n.dispose(),null):n}render(e,t,i){const n="taa"===this.antiAliasingState.mode?this.taa:null;this.volumetricFog?.setScene(e),this.renderer.setMotionVectorProjection(t.projectionMatrix),this.motionBlur?.prepareCamera(t,this.canvas.width,this.canvas.height,i),this.motionBlur?.requiresRendererHistoryReset()&&(this.renderer.resetViewHistory(),this.gtao?.reset(),this.volumetricFog?.reset()),n?.prepareCamera(t,this.canvas.width,this.canvas.height),this.motionBlur?.setJitterUv(n?.currentJitterUv.x??0,n?.currentJitterUv.y??0);let o=!1;try{this.renderer.render(e,t,i),o=!0}finally{n?.finishCamera(t),o&&this.motionBlur?.commitCamera()}}setMotionBlurOptions(e){null!=this.motionBlur&&Object.assign(this.motionBlur.options,e)}setGTAOOptions(e){this.gtao?.setOptions(e)}setAntiAliasingMode(e){e!==this.antiAliasingState.mode&&("taa"===e&&null==this.taa||"smaa"===e&&null==this.smaa||"fxaa"===e&&null==this.fxaa?console.warn(`[Hology WebGPU experiment] Anti-aliasing mode "${e}" was not initialized. Set webgpuPostProcessing.antiAliasing in the initial rendering configuration to enable runtime switching.`):(this.antiAliasingState.mode=e,this.resetTemporalHistory()))}setGTAOQuality(e){this.gtao?.applyPreset(e)}setVolumetricFogOptions(e){this.volumetricFog?.setOptions(e)}setVolumetricFogQuality(e){this.volumetricFog?.applyPreset(e)}pushMotionBlurOverride(e){return this.motionBlur?.pushOverride(e)??null}invalidateObjectMotion(e){this.renderer.invalidateObjectMotion(e)}getLastFrameProfile(){return this.renderer.getLastFrameProfile()}setPostProcessState(e){null!=this.colorGrading&&(Object.assign(this.colorGrading.options,e??{enabled:!1}),this.colorGrading.options.enabled=!0===e?.enabled),null!=this.depthOfField&&(this.depthOfField.options.enabled=null!=e?.depthFocus||null!=e?.depthAperture||null!=e?.depthMaxBlur,this.depthOfField.options.focus=e?.depthFocus,this.depthOfField.options.aperture=e?.depthAperture,this.depthOfField.options.maxBlur=e?.depthMaxBlur)}setProfilingEnabled(e){this.renderer.setRenderGraphProfilingEnabled(e)}resetTemporalHistory(){this.taa?.reset(),this.motionBlur?.reset(),this.gtao?.reset(),this.volumetricFog?.reset(),this.renderer.resetViewHistory()}inspect(e,t){return inspectWebGpuScene(e,t,this.nodeMaterialResolver)}destroy(){this.nodeMaterialResolver.dispose(),this.gBufferFallbackSource.dispose(),this.gtao?.dispose(),this.smaa?.dispose(),this.motionBlur?.dispose(),this.volumetricFog?.dispose();for(const e of this.ownedPmremResults)e.dispose();this.ownedPmremResults.clear(),this.renderer.destroy(),this.fallbackMaterial.dispose()}}export function inspectWebGpuScene(e,t,i){const n={meshes:0,skinnedMeshes:0,instancedMeshes:0,batchedMeshes:0,sprites:0,points:0,lines:0,missingPositionGeometry:0,transparentMaterials:0,shaderMaterialsReplaced:0,materialsReplaced:0,unsupportedCamera:ExperimentalWebGpuBackend.supportsCamera(t)?0:1};return e.traverse(e=>{if(e.isSprite)return void n.sprites++;if(e.isPoints)return void n.points++;if(e.isLine)return void n.lines++;if(!e.isMesh)return;const t=e;t.isBatchedMesh?n.batchedMeshes++:t.isInstancedMesh?n.instancedMeshes++:t.isSkinnedMesh?n.skinnedMeshes++:n.meshes++,null==t.geometry?.getAttribute("position")&&n.missingPositionGeometry++;const o=Array.isArray(t.material)?t.material:[t.material];for(const e of o){if(null==e)continue;e.transparent&&n.transparentMaterials++;const t=!0===e.isWGSLShaderMaterial||!0===e.isLineMaterial,o=!t&&!0===i?.canResolve(e);t||o||(n.materialsReplaced++,e.isShaderMaterial&&n.shaderMaterialsReplaced++)}}),n}export function formatWebGpuSceneCapabilitySummary(e){return`[Hology WebGPU experiment] ${JSON.stringify(e)}`}const c="\nstruct VertexInput {\n @location(0) position: vec3<f32>,\n @location(1) normal: vec3<f32>,\n @location(2) uv: vec2<f32>,\n @location(3) color: vec4<f32>,\n#ifdef USE_SKINNING\n @location(4) skinIndex: vec4<u32>,\n @location(5) skinWeight: vec4<f32>,\n#endif\n @builtin(instance_index) instanceIndex: u32,\n};\n\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) worldPosition: vec3<f32>,\n @location(1) worldNormal: vec3<f32>,\n @location(2) color: vec4<f32>,\n};\n\n@vertex\nfn vsMain(input: VertexInput) -> VertexOutput {\n var output: VertexOutput;\n#ifdef USE_SKINNING\n let localPosition = rendererSkinPosition(input.position, input.skinIndex, input.skinWeight);\n let localNormal = rendererSkinNormal(input.normal, input.skinIndex, input.skinWeight);\n#else\n let localPosition = input.position;\n let localNormal = input.normal;\n#endif\n let world = rendererWorldPosition(localPosition, input.instanceIndex);\n output.position = rendererClipPosition(localPosition, input.instanceIndex);\n output.worldPosition = world.xyz;\n output.worldNormal = rendererWorldNormal(localNormal, input.instanceIndex);\n output.color = input.color * rendererObjectColor(input.instanceIndex);\n return output;\n}\n",h="\nstruct VertexOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) worldPosition: vec3<f32>,\n @location(1) worldNormal: vec3<f32>,\n @location(2) color: vec4<f32>,\n};\n\nfn distanceAttenuation(distanceToLight: f32, cutoffDistance: f32, decayExponent: f32) -> f32 {\n var distanceFalloff = 1.0 / max(pow(distanceToLight, decayExponent), 0.01);\n if (cutoffDistance > 0.0) {\n let ratio = distanceToLight / cutoffDistance;\n let cutoff = clamp(1.0 - ratio * ratio * ratio * ratio, 0.0, 1.0);\n distanceFalloff *= cutoff * cutoff;\n }\n return distanceFalloff;\n}\n\n@fragment\nfn fsMain(input: VertexOutput) -> @location(0) vec4<f32> {\n let normal = normalize(input.worldNormal);\n let normalTint = normal * 0.5 + vec3<f32>(0.5);\n let base = mix(vec3<f32>(0.52, 0.58, 0.64), normalTint, 0.22) * input.color.rgb;\n var lighting = rendererIndirectDiffuse(normal) + vec3<f32>(0.08);\n var directionalLighting = vec3<f32>(0.0);\n let directionalCount = rendererDirectionalLightCount();\n\n for (var index: u32 = 0u; index < directionalCount; index = index + 1u) {\n let lightDirection = rendererDirectionalLightDirectionForLight(index);\n let shadow = rendererSampleDirectionalShadowForLightWithNormal(index, input.worldPosition, normal);\n directionalLighting += rendererDirectionalLightColorForLight(index) * max(dot(normal, lightDirection), 0.0) * shadow;\n }\n if (directionalCount > 0u) {\n lighting += directionalLighting / f32(directionalCount);\n }\n\n for (var index: u32 = 0u; index < rendererPointLightCount(); index = index + 1u) {\n let lightVector = rendererPointLightPositionForLight(index) - input.worldPosition;\n let distanceToLight = length(lightVector);\n let lightDirection = normalize(lightVector);\n let shadow = rendererSamplePointShadowForLightWithNormal(index, input.worldPosition, normal);\n lighting += rendererPointLightColorForLight(index) * max(dot(normal, lightDirection), 0.0) * distanceAttenuation(distanceToLight, rendererPointLightRangeForLight(index), rendererPointLightDecayForLight(index)) * shadow;\n }\n\n for (var index: u32 = 0u; index < rendererSpotLightCount(); index = index + 1u) {\n let lightVector = rendererSpotLightPositionForLight(index) - input.worldPosition;\n let distanceToLight = length(lightVector);\n let lightDirection = normalize(lightVector);\n let angleCos = dot(-lightDirection, rendererSpotLightDirectionForLight(index));\n let cone = smoothstep(rendererSpotLightConeCosForLight(index), rendererSpotLightPenumbraCosForLight(index), angleCos);\n let shadow = rendererSampleSpotShadowForLightWithNormal(index, input.worldPosition, normal);\n lighting += rendererSpotLightColorForLight(index) * max(dot(normal, lightDirection), 0.0) * cone * distanceAttenuation(distanceToLight, rendererSpotLightRangeForLight(index), rendererSpotLightDecayForLight(index)) * shadow;\n }\n\n return vec4<f32>(base * max(lighting, vec3<f32>(0.08)), input.color.a);\n}\n";/*
|
|
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{AudioLoader as e,BufferGeometry as t,Euler as r,Group as a,LoadingManager as s,Mesh as i,Object3D as n,Texture as o,TextureLoader as l,Vector3 as c}from"three";import{MTLLoader as h,OBJLoader as u,LUTCubeLoader as m}from"three-stdlib";import{cloneMesh as d,mergeCompatibleSkinnedMeshes as p}from"../utils/mesh.js";import{pathJoin as f}from"../utils/files.js";import{Subject as y,firstValueFrom as g}from"rxjs";import{BoxCollisionShape as w,CapsuleCollisionShape as x,CollisionShapeSource as M,CylinderCollisionShape as b,SphereCollisionShape as A}from"./collision/collision-shape.js";import{importCollisionShapes as S,isCollisionMesh as _}from"./collision/collision-shape-import.js";import*as v from"three";import{iterateMaterials as L}from"../utils/materials.js";import{applyMaterial as T,getConvertedFbxToGlbAssignedMaterialTextureFlipY as R}from"./materializer.js";import{BufferGeometryUtils as C,DRACOLoader as I,EXRLoader as P,FBXLoader as B,GLTFLoader as K,KTX2Loader as k,TGALoader as j,UltraHDRLoader as F}from"three/examples/jsm/Addons.js";import{disposeScene as D}from"../utils/three/cleanup.js";import{resolveAnimationRigBasisRotation as z,setAnimationClipRigId as W}from"../gameplay/animation/retarget.js";(()=>{const e=new Uint8Array([255,0,0,255]),t=new v.DataTexture(e,1,1,v.RGBAFormat);t.needsUpdate=!0})();const U=new I;U.setDecoderConfig({type:"js"}),U.setDecoderPath("/assets/draco/");const E=["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(U),this.fbxLoader=new B(this.loadingManager),this.objLoader=new u(this.loadingManager),this.textureLoader=new l(this.loadingManager),this.tgaLoader=new j(this.loadingManager),this.exrLoader=new P(this.loadingManager),this.cubeLoader=new m(this.loadingManager),this.hdrLoader=new F(this.loadingManager),this.ktx2Loader=new k(this.loadingManager),this._textureLoader=new v.ImageBitmapLoader(this.loadingManager),this.audioLoader=new e(this.loadingManager),this.initialisedKtx2=!1,this._retrievedMeshes=[],this._retrievedTextures=[],this.makeReady=new y,this.ready=g(this.makeReady),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.collisionShapeCache=new Map,this.optimizedMeshes=new Set}initKtx2(e){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.detectSupport(e),this.ktx2Loader.setWorkerLimit(6)}setDataDir(e){this.basePath=f(e,"asset-resources"),this.makeReady.next(!0)}getUri(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")?f(this.basePath,e)+(null!=r?`?windowId=${r}${null!=t?`&v=${t}`:""}`:""):new URL(f("..",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,this.textureCache.has(e.id)){const t=this.textureCache.get(e.id);if(null!=e.texture){const r=q(t,e);t.flipY!==r?(t.needsUpdate=!0,t.flipY=r):t.wrapS!==Y(e.texture?.wrapS)?(t.needsUpdate=!0,t.wrapS=Y(e.texture?.wrapS)):t.wrapT!==Y(e.texture?.wrapT)&&(t.wrapT=Y(e.texture?.wrapT),t.needsUpdate=!0)}const r=V(X(e));t.colorSpace!==r&&(t.colorSpace=r,t.needsUpdate=!0)}else try{await this._getTextureLoader(e.fileKey).loadAsync(this.getUri(e.fileKey)).then(t=>(t.texture3D?t=t.texture3D:(t.wrapS=Y(e.texture?.wrapS),t.wrapT=Y(e.texture?.wrapT),t.flipY=q(t,e),t.colorSpace=V(X(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 e=this._arrayTexturePromisesByFileKey.get(t);null==e&&(e=this.loadTextureArray(t).finally(()=>{this._arrayTexturePromisesByFileKey.delete(t)}),this._arrayTexturePromisesByFileKey.set(t,e)),a=await e}return this.applyTextureArraySettings(t,a,e),{texture:a,layerIndex:r}}async loadTextureArray(e){const t=this._getTextureLoader(e);let r=await t.loadAsync(this.getUri(e));return r instanceof o||(r=r.texture3D),this._arrayTexturesByFileKey.set(e,r),this._retrievedTextures.push(r),r}applyTextureArraySettings(e,t,r){const a=function(e){return{wrapS:Y(e.texture?.wrapS),wrapT:Y(e.texture?.wrapT)}}(r),s=this._arrayTextureSettingsByFileKey.get(e);if(null!=s&&(n=a,(i=s).wrapS!==n.wrapS||i.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 i,n;null==s&&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)}),$(e.fileKey)),null!=e.materialLib&&$(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(!E.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 s=this.cache.get(r),n=s.scene,o=s.animations;if(!1===t?.clone)return{scene:n,animations:o};const l=this.computeCollisionShapes(e,n);!0===t?.mergeGeomtries&&this.optimizeDrawGroups(n);const c=d(n);c.traverse(e=>{e instanceof i&&e.material instanceof Array&&(e.material=e.material.slice())});const h=new AssetMeshInstance;h.add(c),h.collisionShapes=l,h.animations=o,h.userData.assetId=e.id;const u=e.mesh?.animationRigId??e.anim?.rigId;if(h.userData.animationRigId=u,c.userData.animationRigId=u,null!=u&&null!=this.animationRigProvider){const e=await this.animationRigProvider(u);h.userData.animationRig=e,c.userData.animationRig=e,null!=e&&!1!==t?.applyRigBasis&&h.quaternion.copy(z(e))}const m=e.receiveShadow??!1,p=e.castShadow??!1;if(c.traverse(e=>{e.castShadow=p,e.receiveShadow=m}),!0===t?.rescale&&"number"==typeof e.mesh?.rescale&&h.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(h,t,r,void 0,{textureFlipY:R(e)});else console.warn(`applyMaterials was set to true for ${e.name} but no material resolver was provided.`)}return{scene:h,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 t=e.anim.clip,r=e.fileKey+"|"+t;if(this._animationCache.has(r))return this._animationCache.get(r);if(this._animationPromises.has(r))return this._animationPromises.get(r);const a=(async()=>{try{const a=await this.getMesh(e),s=a.animations.find(e=>e.name===t);return null!=s&&(W(s,e.anim?.rigId),this._animationCache.set(r,s)),s}catch(e){return this.onError(e),null}finally{setTimeout(()=>this._animationPromises.delete(r),1e3)}})();return this._animationPromises.set(r,a),a}computeCollisionShapes(e,t){if(!0!==e.collisionDetection)return[];const r=e.id+"|"+(e.mesh?.collisions?.shapeType??"")+"|"+(e.mesh?.rescale??1)+"|"+JSON.stringify(e.mesh?.colliders??[]);if(!this.collisionShapeCache.has(r)){const a=e.mesh?.colliders??[],s=a.length>0?createAuthoredCollisionShapes(a,e.mesh?.rescale):S(t,e);this.collisionShapeCache.set(r,s)}return this.collisionShapeCache.get(r)}async loadMesh(e){return await this.ready,await this.loadByAsset(e).then(r=>(r.scene.traverse(r=>{if(r instanceof i&&r.geometry instanceof t){const t=removeInvalidFaces(r.geometry);t>0&&console.warn(`Removed ${t} invalid face${1===t?"":"s"} from ${e.name}/${r.name||"unnamed mesh"}`)}r instanceof i&&r.material&&function(e){let t=e.material;if(null==t)return;Array.isArray(t)?e.material=t.map(Q):e.material=Q(t)}(r),_(r)?r.visible=!1:r instanceof i&&r.geometry instanceof t&&(r.geometry.hasAttribute("normal")||r.geometry.computeVertexNormals())}),this._retrievedMeshes.push(r.scene),r.scene=function(e,t){let r=!1;if(t.traverseVisible(e=>{H.test(e.name)&&(r=!0)}),!r)return t;const a=[],s=[t];for(;s.length>0;){const e=s.shift(),t=e.name.match(H);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}}s.push(...e.children)}for(const e of a)e.removeFromParent();return t}(0,r.scene),function(e){const t=Z;Z.length=0,e.traverse(e=>{(e instanceof v.Camera||e instanceof v.Light)&&t.push(e)}),t.forEach(e=>e.removeFromParent())}(r.scene),r.scene.userData.assetId=e.id,r))}optimizeDrawGroups(e){if(this.optimizedMeshes.has(e.uuid))return;this.optimizedMeshes.add(e.uuid),e.updateWorldMatrix(!0,!0);const r=[];e.traverse(e=>{e instanceof i&&!(e instanceof v.SkinnedMesh)&&e.geometry instanceof t&&Array.isArray(e.material)&&!_(e)&&r.push({mesh:e,geometry:e.geometry,materialArray:e.material,parent:e.parent})});for(const{mesh:t,geometry:a,materialArray:s,parent:n}of r){t.removeFromParent();for(let r=0;r<s.length;r++){const o=s[r],l=a.groups.filter(e=>e.materialIndex===r);if(0===l.length)continue;const c=this.extractGeometryGroups(a,l);c.groups.length>1&&C.mergeGroups(c);const h=new i(c,o);h.copy(t,!1),h.geometry=c,h.material=o,n?n.add(h):e.add(h)}a.dispose()}p(e,_);let a=!0,s=0,n=0;if(e.traverse(e=>{if(e instanceof i&&e.geometry instanceof t&&!_(e)){s++;const t=Object.keys(e.geometry.attributes).length;t!==n&&0!==n&&(a=!1),n=t}else(e instanceof v.SkinnedMesh||e instanceof v.Bone)&&(a=!1)}),s>1&&a){const r=new Map;e.traverse(a=>{if(a instanceof i&&a.geometry instanceof t&&!_(a)){if(Array.isArray(a.material))return;a.updateWorldMatrix(!0,!0);const t=a.material?.uuid||"default";r.has(t)||r.set(t,{material:a.material,geometries:[],objects:[]});const s=r.get(t),i=e.matrixWorld.clone().invert().multiply(a.matrixWorld);s.geometries.push(a.geometry.clone().applyMatrix4(i)),s.objects.push(a)}});for(const e of r.values())for(const t of e.objects)t.removeFromParent();for(const t of r.values()){let r,a=t.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 v.BufferAttribute(r,1)),e})),r=1===t.geometries.length?a[0]:O(a)?C.mergeGeometries(a,!0):null,null!=r)r.groups.length>1&&C.mergeGroups(r),e.add(new i(r,t.material));else for(const r of a)e.add(new i(r,t.material))}}e.traverse(e=>{if(e instanceof i&&e.geometry instanceof t&&!_(e)){const t=e.geometry;!Array.isArray(e.material)&&t.groups.length>1&&C.mergeGroups(t)}})}extractGeometryGroups(e,r){const a=e.index;if(!a){const a=new t;let s=0;for(const e of r)s+=e.count;if(0===s)return a;for(const t in e.attributes){const i=e.attributes[t],n=i.itemSize,o=i.array,l=new(0,o.constructor)(s*n);let c=0;for(const e of r){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 v.BufferAttribute(l,n,i.normalized);a.setAttribute(t,h)}return a.groups=[{start:0,count:s,materialIndex:0}],a}const s=[];for(const e of r)for(let t=0;t<e.count;t++){const r=a.getX(e.start+t);s.push(r)}if(0===s.length)return new t;const i=new Map,n=[];let o=0;for(const e of s)i.has(e)||(i.set(e,o),o++),n.push(i.get(e));if(0===n.length||0===i.size)return new t;const l=new t;for(const t in e.attributes){const r=e.attributes[t],a=r.itemSize,s=r.array,n=new(0,s.constructor)(i.size*a);for(const[e,t]of i)for(let r=0;r<a;r++)n[t*a+r]=s[e*a+r];const o=new v.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 v.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(e=>({scene:e.scene,animations:e.animations}));case"fbx":return this.fbxLoader.loadAsync(t).then(e=>({scene:e,animations:e.animations}));case"obj":if(null!=e.materialLib){const t=new h;t.materialOptions={normalizeRGB:!1};const r=await t.loadAsync(this.getUri(e.materialLib));this.objLoader.setMaterials(r)}return this.objLoader.loadAsync(t).then(e=>(N(e),e)).then(e=>({scene:e,animations:e.animations}))}}disposeAll(){this.cache.clear(),this._retrievedMeshes.forEach(e=>D(e)),this._retrievedTextures.forEach(e=>e.dispose()),this._retrievedMeshes.length=0,this._retrievedTextures.length=0}}const G=new Map;function $(e){G.set(e,(G.get(e)??0)+1)}function O(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 s=Object.keys(e.attributes).sort(),i=Object.keys(e.morphAttributes).sort();if(s.join("|")!==r.join("|"))return!1;if(i.join("|")!==a.join("|"))return!1;for(const a of r){const r=t.getAttribute(a),s=e.getAttribute(a);if(r.itemSize!==s.itemSize||r.normalized!==s.normalized||r.array.constructor!==s.array.constructor)return!1}for(const r of a){const a=t.morphAttributes[r],s=e.morphAttributes[r];if(a.length!==s.length)return!1;for(let e=0;e<a.length;e++)if(a[e].itemSize!==s[e].itemSize||a[e].normalized!==s[e].normalized||a[e].array.constructor!==s[e].array.constructor)return!1}return!0})}function N(e){if(e instanceof i)for(const t of L(e.material))t instanceof v.MeshPhongMaterial&&!t.color.isLinear&&(t.color.isLinear=!0);e.children?.forEach(N)}export class AssetMeshInstance extends n{}export function createAuthoredCollisionShapes(e,t=1){const a=null!=t&&0!==t?1/t:1;return e.map(e=>{let t;switch(e.type){case"box":t=new w((new c).fromArray(e.size).multiplyScalar(a));break;case"sphere":t=new A(e.radius*a);break;case"capsule":t=new x(e.length*a,e.radius*a);break;case"cylinder":t=new b(e.radius*a,e.radius*a,e.height*a,16,new r)}return t.source=M.custom,t.offset.fromArray(e.position??[0,0,0]).multiplyScalar(a),t.rotation.fromArray(e.rotation??[0,0,0,"XYZ"]),t})}export function getElectronArg(e){const t=`--${e}=`,r=window.process?.argv.find(e=>e.startsWith(t));return r?.substring(t.length)}function Y(e){switch(e){case"clamp":return v.ClampToEdgeWrapping;case"repeat":return v.RepeatWrapping;case"mirror":return v.MirroredRepeatWrapping}return v.RepeatWrapping}function q(e,t){const r=e;return!0!==r.isCompressedTexture&&!0!==r.isCompressedArrayTexture&&(t.texture?.flipY??!0)}function X(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(J.some(e=>t.includes(e)))return"linear";return"srgb"}(e)}function V(e){return"linear"===e?v.LinearSRGBColorSpace:v.SRGBColorSpace}const J=["_normal","_nrm","_nor","_norm","_roughness","_rough","_metalness","_metallic","_metal","_ao","_ambient","_occlusion","_mask","_height","_disp","_displacement","_bump","_opacity"];new v.Matrix4;const Z=[];const H=/_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,s=Math.floor(a/3),i=[],n=new Uint32Array(s+1),o=new c,l=new c,h=new c;let u=0;for(let e=0;e<s;e++){const a=3*e,s=r?.getX(a)??a,c=r?.getX(a+1)??a+1,m=r?.getX(a+2)??a+2;let d=Number.isInteger(s)&&Number.isInteger(c)&&Number.isInteger(m)&&s>=0&&c>=0&&m>=0&&s<t.count&&c<t.count&&m<t.count;if(d){o.fromBufferAttribute(t,s),l.fromBufferAttribute(t,c).sub(o),h.fromBufferAttribute(t,m).sub(o);const e=Math.max(l.lengthSq(),h.lengthSq(),l.distanceToSquared(h)),r=l.cross(h).lengthSq();d=Number.isFinite(e)&&Number.isFinite(r)&&e>0&&r>Number.EPSILON*e*e}d?i.push(s,c,m):u++,n[e+1]=i.length}if(0===u)return 0;if(e.setIndex(i),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(s,Math.floor((r.start+r.count)/3));if(a<=t)continue;const i=n[t],o=n[a]-i;o>0&&e.addGroup(i,o,r.materialIndex)}}const m=Math.max(0,Math.ceil(e.drawRange.start/3)),d=Number.isFinite(e.drawRange.count)?Math.min(s,Math.floor((e.drawRange.start+e.drawRange.count)/3)):s,p=Math.min(m,s),f=Math.min(s,Math.max(p,d)),y=n[p];return e.setDrawRange(y,n[f]-y),u}function Q(e){let t=e instanceof v.MeshPhysicalMaterial&&0===e.sheen&&0===e.anisotropy;if("MeshLambertMaterial"===e.type||"MeshPhongMaterial"===e.type||t){let t={};(e instanceof v.MeshLambertMaterial||e instanceof v.MeshPhongMaterial||e instanceof v.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 v.MeshStandardMaterial(t);return r.userData={...e.userData??{}},r.name=e.name,r}return e}/*
|
|
1
|
+
import{AudioLoader as e,BufferGeometry as t,Euler as r,Group as a,LoadingManager as s,Mesh as i,Object3D as n,Texture as o,TextureLoader as l,Vector3 as c}from"three";import{MTLLoader as h,OBJLoader as u,LUTCubeLoader as m}from"three-stdlib";import{cloneMesh as d,mergeCompatibleSkinnedMeshes as p}from"../utils/mesh.js";import{pathJoin as f}from"../utils/files.js";import{Subject as y,firstValueFrom as g}from"rxjs";import{BoxCollisionShape as w,CapsuleCollisionShape as x,CollisionShapeSource as M,CylinderCollisionShape as b,SphereCollisionShape as A}from"./collision/collision-shape.js";import{importCollisionShapes as S,isCollisionMesh as _}from"./collision/collision-shape-import.js";import*as v from"three";import{iterateMaterials as L}from"../utils/materials.js";import{applyMaterial as T,getConvertedFbxToGlbAssignedMaterialTextureFlipY as R}from"./materializer.js";import{BufferGeometryUtils as C,DRACOLoader as I,EXRLoader as P,FBXLoader as B,GLTFLoader as K,KTX2Loader as k,TGALoader as j,UltraHDRLoader as F}from"three/examples/jsm/Addons.js";import{disposeScene as D}from"../utils/three/cleanup.js";import{resolveAnimationRigBasisRotation as z,setAnimationClipRigId as W}from"../gameplay/animation/retarget.js";(()=>{const e=new Uint8Array([255,0,0,255]),t=new v.DataTexture(e,1,1,v.RGBAFormat);t.needsUpdate=!0})();const U=new I;U.setDecoderConfig({type:"js"}),U.setDecoderPath("/assets/draco/");const G=["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(U),this.fbxLoader=new B(this.loadingManager),this.objLoader=new u(this.loadingManager),this.textureLoader=new l(this.loadingManager),this.tgaLoader=new j(this.loadingManager),this.exrLoader=new P(this.loadingManager),this.cubeLoader=new m(this.loadingManager),this.hdrLoader=new F(this.loadingManager),this.ktx2Loader=new k(this.loadingManager),this._textureLoader=new v.ImageBitmapLoader(this.loadingManager),this.audioLoader=new e(this.loadingManager),this.initialisedKtx2=!1,this._retrievedMeshes=[],this._retrievedTextures=[],this.makeReady=new y,this.ready=g(this.makeReady),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.collisionShapeCache=new Map,this.optimizedMeshes=new Set}initKtx2(e){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.detectSupport(e),this.ktx2Loader.setWorkerLimit(6)}setDataDir(e){this.basePath=f(e,"asset-resources"),this.makeReady.next(!0)}getUri(e){const t=function(e){return E.get(e)}(e),r=getElectronArg("windowId");return null!=r||import.meta.url.includes(".vite")||import.meta.url.includes("hology/packages/core")?f(this.basePath,e)+(null!=r?`?windowId=${r}${null!=t?`&v=${t}`:""}`:""):new URL(f("..",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,this.textureCache.has(e.id)){const t=this.textureCache.get(e.id);if(null!=e.texture){const r=q(t,e);t.flipY!==r?(t.needsUpdate=!0,t.flipY=r):t.wrapS!==Y(e.texture?.wrapS)?(t.needsUpdate=!0,t.wrapS=Y(e.texture?.wrapS)):t.wrapT!==Y(e.texture?.wrapT)&&(t.wrapT=Y(e.texture?.wrapT),t.needsUpdate=!0)}const r=V(X(e));t.colorSpace!==r&&(t.colorSpace=r,t.needsUpdate=!0)}else try{await this._getTextureLoader(e.fileKey).loadAsync(this.getUri(e.fileKey)).then(t=>(t.texture3D?t=t.texture3D:(t.wrapS=Y(e.texture?.wrapS),t.wrapT=Y(e.texture?.wrapT),t.flipY=q(t,e),t.colorSpace=V(X(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 e=this._arrayTexturePromisesByFileKey.get(t);null==e&&(e=this.loadTextureArray(t).finally(()=>{this._arrayTexturePromisesByFileKey.delete(t)}),this._arrayTexturePromisesByFileKey.set(t,e)),a=await e}return this.applyTextureArraySettings(t,a,e),{texture:a,layerIndex:r}}async loadTextureArray(e){const t=this._getTextureLoader(e);let r=await t.loadAsync(this.getUri(e));return r instanceof o||(r=r.texture3D),this._arrayTexturesByFileKey.set(e,r),this._retrievedTextures.push(r),r}applyTextureArraySettings(e,t,r){const a=function(e){return{wrapS:Y(e.texture?.wrapS),wrapT:Y(e.texture?.wrapT)}}(r),s=this._arrayTextureSettingsByFileKey.get(e);if(null!=s&&(n=a,(i=s).wrapS!==n.wrapS||i.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 i,n;null==s&&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)}),$(e.fileKey)),null!=e.materialLib&&$(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(!G.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 s=this.cache.get(r),n=s.scene,o=s.animations;if(!1===t?.clone)return{scene:n,animations:o};const l=this.computeCollisionShapes(e,n);!0===t?.mergeGeomtries&&this.optimizeDrawGroups(n);const c=d(n);c.traverse(e=>{e instanceof i&&e.material instanceof Array&&(e.material=e.material.slice())});const h=new AssetMeshInstance;h.add(c),h.collisionShapes=l,h.animations=o,h.userData.assetId=e.id,h.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 s=e instanceof i?"Mesh":"Group",n=`${t}/${e.name?.trim()?.length>0?e.name:s}[${a}]`;e.userData.secondaryMotionMeshPath=n;for(let t=0;t<e.children.length;t++)r(e.children[t],n,t)}}(h);const u=e.mesh?.animationRigId??e.anim?.rigId;if(h.userData.animationRigId=u,c.userData.animationRigId=u,null!=u&&null!=this.animationRigProvider){const e=await this.animationRigProvider(u);h.userData.animationRig=e,c.userData.animationRig=e,null!=e&&!1!==t?.applyRigBasis&&h.quaternion.copy(z(e))}const m=e.receiveShadow??!1,p=e.castShadow??!1;if(c.traverse(e=>{e.castShadow=p,e.receiveShadow=m}),!0===t?.rescale&&"number"==typeof e.mesh?.rescale&&h.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(h,t,r,void 0,{textureFlipY:R(e)});else console.warn(`applyMaterials was set to true for ${e.name} but no material resolver was provided.`)}return{scene:h,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 t=e.anim.clip,r=e.fileKey+"|"+t;if(this._animationCache.has(r))return this._animationCache.get(r);if(this._animationPromises.has(r))return this._animationPromises.get(r);const a=(async()=>{try{const a=await this.getMesh(e),s=a.animations.find(e=>e.name===t);return null!=s&&(W(s,e.anim?.rigId),this._animationCache.set(r,s)),s}catch(e){return this.onError(e),null}finally{setTimeout(()=>this._animationPromises.delete(r),1e3)}})();return this._animationPromises.set(r,a),a}computeCollisionShapes(e,t){if(!0!==e.collisionDetection)return[];const r=e.id+"|"+(e.mesh?.collisions?.shapeType??"")+"|"+(e.mesh?.rescale??1)+"|"+JSON.stringify(e.mesh?.colliders??[]);if(!this.collisionShapeCache.has(r)){const a=e.mesh?.colliders??[],s=a.length>0?createAuthoredCollisionShapes(a,e.mesh?.rescale):S(t,e);this.collisionShapeCache.set(r,s)}return this.collisionShapeCache.get(r)}async loadMesh(e){return await this.ready,await this.loadByAsset(e).then(r=>(r.scene.traverse(r=>{if(r instanceof i&&r.geometry instanceof t){const t=removeInvalidFaces(r.geometry);t>0&&console.warn(`Removed ${t} invalid face${1===t?"":"s"} from ${e.name}/${r.name||"unnamed mesh"}`)}r instanceof i&&r.material&&function(e){let t=e.material;if(null==t)return;Array.isArray(t)?e.material=t.map(Q):e.material=Q(t)}(r),_(r)?r.visible=!1:r instanceof i&&r.geometry instanceof t&&(r.geometry.hasAttribute("normal")||r.geometry.computeVertexNormals())}),this._retrievedMeshes.push(r.scene),r.scene=function(e,t){let r=!1;if(t.traverseVisible(e=>{H.test(e.name)&&(r=!0)}),!r)return t;const a=[],s=[t];for(;s.length>0;){const e=s.shift(),t=e.name.match(H);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}}s.push(...e.children)}for(const e of a)e.removeFromParent();return t}(0,r.scene),function(e){const t=Z;Z.length=0,e.traverse(e=>{(e instanceof v.Camera||e instanceof v.Light)&&t.push(e)}),t.forEach(e=>e.removeFromParent())}(r.scene),r.scene.userData.assetId=e.id,r))}optimizeDrawGroups(e){if(this.optimizedMeshes.has(e.uuid))return;this.optimizedMeshes.add(e.uuid),e.updateWorldMatrix(!0,!0);const r=[];e.traverse(e=>{e instanceof i&&!(e instanceof v.SkinnedMesh)&&e.geometry instanceof t&&Array.isArray(e.material)&&!_(e)&&r.push({mesh:e,geometry:e.geometry,materialArray:e.material,parent:e.parent})});for(const{mesh:t,geometry:a,materialArray:s,parent:n}of r){t.removeFromParent();for(let r=0;r<s.length;r++){const o=s[r],l=a.groups.filter(e=>e.materialIndex===r);if(0===l.length)continue;const c=this.extractGeometryGroups(a,l);c.groups.length>1&&C.mergeGroups(c);const h=new i(c,o);h.copy(t,!1),h.geometry=c,h.material=o,n?n.add(h):e.add(h)}a.dispose()}p(e,_);let a=!0,s=0,n=0;if(e.traverse(e=>{if(e instanceof i&&e.geometry instanceof t&&!_(e)){s++;const t=Object.keys(e.geometry.attributes).length;t!==n&&0!==n&&(a=!1),n=t}else(e instanceof v.SkinnedMesh||e instanceof v.Bone)&&(a=!1)}),s>1&&a){const r=new Map;e.traverse(a=>{if(a instanceof i&&a.geometry instanceof t&&!_(a)){if(Array.isArray(a.material))return;a.updateWorldMatrix(!0,!0);const t=a.material?.uuid||"default";r.has(t)||r.set(t,{material:a.material,geometries:[],objects:[]});const s=r.get(t),i=e.matrixWorld.clone().invert().multiply(a.matrixWorld);s.geometries.push(a.geometry.clone().applyMatrix4(i)),s.objects.push(a)}});for(const e of r.values())for(const t of e.objects)t.removeFromParent();for(const t of r.values()){let r,a=t.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 v.BufferAttribute(r,1)),e})),r=1===t.geometries.length?a[0]:O(a)?C.mergeGeometries(a,!0):null,null!=r)r.groups.length>1&&C.mergeGroups(r),e.add(new i(r,t.material));else for(const r of a)e.add(new i(r,t.material))}}e.traverse(e=>{if(e instanceof i&&e.geometry instanceof t&&!_(e)){const t=e.geometry;!Array.isArray(e.material)&&t.groups.length>1&&C.mergeGroups(t)}})}extractGeometryGroups(e,r){const a=e.index;if(!a){const a=new t;let s=0;for(const e of r)s+=e.count;if(0===s)return a;for(const t in e.attributes){const i=e.attributes[t],n=i.itemSize,o=i.array,l=new(0,o.constructor)(s*n);let c=0;for(const e of r){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 v.BufferAttribute(l,n,i.normalized);a.setAttribute(t,h)}return a.groups=[{start:0,count:s,materialIndex:0}],a}const s=[];for(const e of r)for(let t=0;t<e.count;t++){const r=a.getX(e.start+t);s.push(r)}if(0===s.length)return new t;const i=new Map,n=[];let o=0;for(const e of s)i.has(e)||(i.set(e,o),o++),n.push(i.get(e));if(0===n.length||0===i.size)return new t;const l=new t;for(const t in e.attributes){const r=e.attributes[t],a=r.itemSize,s=r.array,n=new(0,s.constructor)(i.size*a);for(const[e,t]of i)for(let r=0;r<a;r++)n[t*a+r]=s[e*a+r];const o=new v.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 v.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(e=>({scene:e.scene,animations:e.animations}));case"fbx":return this.fbxLoader.loadAsync(t).then(e=>({scene:e,animations:e.animations}));case"obj":if(null!=e.materialLib){const t=new h;t.materialOptions={normalizeRGB:!1};const r=await t.loadAsync(this.getUri(e.materialLib));this.objLoader.setMaterials(r)}return this.objLoader.loadAsync(t).then(e=>(N(e),e)).then(e=>({scene:e,animations:e.animations}))}}disposeAll(){this.cache.clear(),this._retrievedMeshes.forEach(e=>D(e)),this._retrievedTextures.forEach(e=>e.dispose()),this._retrievedMeshes.length=0,this._retrievedTextures.length=0}}const E=new Map;function $(e){E.set(e,(E.get(e)??0)+1)}function O(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 s=Object.keys(e.attributes).sort(),i=Object.keys(e.morphAttributes).sort();if(s.join("|")!==r.join("|"))return!1;if(i.join("|")!==a.join("|"))return!1;for(const a of r){const r=t.getAttribute(a),s=e.getAttribute(a);if(r.itemSize!==s.itemSize||r.normalized!==s.normalized||r.array.constructor!==s.array.constructor)return!1}for(const r of a){const a=t.morphAttributes[r],s=e.morphAttributes[r];if(a.length!==s.length)return!1;for(let e=0;e<a.length;e++)if(a[e].itemSize!==s[e].itemSize||a[e].normalized!==s[e].normalized||a[e].array.constructor!==s[e].array.constructor)return!1}return!0})}function N(e){if(e instanceof i)for(const t of L(e.material))t instanceof v.MeshPhongMaterial&&!t.color.isLinear&&(t.color.isLinear=!0);e.children?.forEach(N)}export class AssetMeshInstance extends n{}export function createAuthoredCollisionShapes(e,t=1){const a=null!=t&&0!==t?1/t:1;return e.map(e=>{let t;switch(e.type){case"box":t=new w((new c).fromArray(e.size).multiplyScalar(a));break;case"sphere":t=new A(e.radius*a);break;case"capsule":t=new x(e.length*a,e.radius*a);break;case"cylinder":t=new b(e.radius*a,e.radius*a,e.height*a,16,new r)}return t.source=M.custom,t.offset.fromArray(e.position??[0,0,0]).multiplyScalar(a),t.rotation.fromArray(e.rotation??[0,0,0,"XYZ"]),t})}export function getElectronArg(e){const t=`--${e}=`,r=window.process?.argv.find(e=>e.startsWith(t));return r?.substring(t.length)}function Y(e){switch(e){case"clamp":return v.ClampToEdgeWrapping;case"repeat":return v.RepeatWrapping;case"mirror":return v.MirroredRepeatWrapping}return v.RepeatWrapping}function q(e,t){const r=e;return!0!==r.isCompressedTexture&&!0!==r.isCompressedArrayTexture&&(t.texture?.flipY??!0)}function X(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(J.some(e=>t.includes(e)))return"linear";return"srgb"}(e)}function V(e){return"linear"===e?v.LinearSRGBColorSpace:v.SRGBColorSpace}const J=["_normal","_nrm","_nor","_norm","_roughness","_rough","_metalness","_metallic","_metal","_ao","_ambient","_occlusion","_mask","_height","_disp","_displacement","_bump","_opacity"];new v.Matrix4;const Z=[];const H=/_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,s=Math.floor(a/3),i=[],n=new Uint32Array(s+1),o=new c,l=new c,h=new c;let u=0;for(let e=0;e<s;e++){const a=3*e,s=r?.getX(a)??a,c=r?.getX(a+1)??a+1,m=r?.getX(a+2)??a+2;let d=Number.isInteger(s)&&Number.isInteger(c)&&Number.isInteger(m)&&s>=0&&c>=0&&m>=0&&s<t.count&&c<t.count&&m<t.count;if(d){o.fromBufferAttribute(t,s),l.fromBufferAttribute(t,c).sub(o),h.fromBufferAttribute(t,m).sub(o);const e=Math.max(l.lengthSq(),h.lengthSq(),l.distanceToSquared(h)),r=l.cross(h).lengthSq();d=Number.isFinite(e)&&Number.isFinite(r)&&e>0&&r>Number.EPSILON*e*e}d?i.push(s,c,m):u++,n[e+1]=i.length}if(0===u)return 0;if(e.setIndex(i),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(s,Math.floor((r.start+r.count)/3));if(a<=t)continue;const i=n[t],o=n[a]-i;o>0&&e.addGroup(i,o,r.materialIndex)}}const m=Math.max(0,Math.ceil(e.drawRange.start/3)),d=Number.isFinite(e.drawRange.count)?Math.min(s,Math.floor((e.drawRange.start+e.drawRange.count)/3)):s,p=Math.min(m,s),f=Math.min(s,Math.max(p,d)),y=n[p];return e.setDrawRange(y,n[f]-y),u}function Q(e){let t=e instanceof v.MeshPhysicalMaterial&&0===e.sheen&&0===e.anisotropy;if("MeshLambertMaterial"===e.type||"MeshPhongMaterial"===e.type||t){let t={};(e instanceof v.MeshLambertMaterial||e instanceof v.MeshPhongMaterial||e instanceof v.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 v.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
|
*/
|
package/dist/scene/model.d.ts
CHANGED
|
@@ -149,6 +149,56 @@ export type ModelColliderData = {
|
|
|
149
149
|
radius: number;
|
|
150
150
|
height: number;
|
|
151
151
|
};
|
|
152
|
+
export type SecondaryMotionMode = 'strand' | 'cloth';
|
|
153
|
+
export type SecondaryMotionQuality = 'low' | 'medium' | 'high' | 'hero';
|
|
154
|
+
export interface SecondaryMotionRegion {
|
|
155
|
+
id: string;
|
|
156
|
+
name: string;
|
|
157
|
+
/** Stable path from the imported model root to the source mesh. */
|
|
158
|
+
meshPath: string;
|
|
159
|
+
mode: SecondaryMotionMode;
|
|
160
|
+
attachment: {
|
|
161
|
+
type: 'bone';
|
|
162
|
+
boneName: string;
|
|
163
|
+
} | {
|
|
164
|
+
type: 'world';
|
|
165
|
+
};
|
|
166
|
+
preset?: string;
|
|
167
|
+
enabled: boolean;
|
|
168
|
+
quality: SecondaryMotionQuality;
|
|
169
|
+
stiffness: number;
|
|
170
|
+
bendStiffness: number;
|
|
171
|
+
animationStiffness: number;
|
|
172
|
+
damping: number;
|
|
173
|
+
gravityScale: number;
|
|
174
|
+
drag: number;
|
|
175
|
+
windResponse: number;
|
|
176
|
+
thickness: number;
|
|
177
|
+
maxDistance?: number;
|
|
178
|
+
/** Sparse editor-authored influence mask. Generated binary data supersedes this at cook time. */
|
|
179
|
+
paintedVertices?: Array<[vertexIndex: number, influence: number]>;
|
|
180
|
+
}
|
|
181
|
+
export interface SecondaryMotionCollider {
|
|
182
|
+
id: string;
|
|
183
|
+
name: string;
|
|
184
|
+
boneName: string;
|
|
185
|
+
type: 'sphere' | 'capsule';
|
|
186
|
+
position: [number, number, number];
|
|
187
|
+
rotation?: [number, number, number, number];
|
|
188
|
+
radius: number;
|
|
189
|
+
length?: number;
|
|
190
|
+
}
|
|
191
|
+
/** Editable secondary-motion metadata. Generated arrays live in resourceKey. */
|
|
192
|
+
export interface SecondaryMotionAssetData {
|
|
193
|
+
version: 1;
|
|
194
|
+
resourceKey: string;
|
|
195
|
+
regions: SecondaryMotionRegion[];
|
|
196
|
+
colliders: SecondaryMotionCollider[];
|
|
197
|
+
groundPlane?: {
|
|
198
|
+
enabled: boolean;
|
|
199
|
+
height: number;
|
|
200
|
+
};
|
|
201
|
+
}
|
|
152
202
|
export type TextureSettings = {
|
|
153
203
|
flipY?: boolean;
|
|
154
204
|
wrapS?: WrapMode;
|
|
@@ -364,6 +414,7 @@ export type Asset = {
|
|
|
364
414
|
shapeType?: CollisionShapeType;
|
|
365
415
|
};
|
|
366
416
|
colliders?: ModelColliderData[];
|
|
417
|
+
secondaryMotion?: SecondaryMotionAssetData;
|
|
367
418
|
/** Animation rig this mesh is compatible with. */
|
|
368
419
|
animationRigId?: AssetId;
|
|
369
420
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{ObjectStorage as e,defaultSerializer as t}from"./storage/storage.js";export class LegacyRuntimeBackendService{constructor(s){this.sceneStorage=new e("scene"),this.sceneBlobStorage=new e("scene-blob",void 0,e=>{const s={...e};return Array.isArray(s.data)&&(s.data=[...s.data].sort((e,t)=>e.id.localeCompare(t.id))),t(s)}),this.assetStorage=new e("asset"),this.sceneStorage.setBasePath(s),this.sceneBlobStorage.setBasePath(s),this.assetStorage.setBasePath(s)}getAssets(){return this.assetStorage.getAll()}getAsset(e){return this.assetStorage.get(e)}getScenes(){return this.sceneStorage.getAll()}getScene(e){return this.sceneStorage.get(e)}async getSceneData(e){const t=await this.sceneBlobStorage.get(e);return null==t?null:"string"==typeof t.data?
|
|
1
|
+
import{ObjectStorage as e,defaultSerializer as t}from"./storage/storage.js";import{parseStorageJson as s}from"./storage/packed-json.js";export class LegacyRuntimeBackendService{constructor(s){this.sceneStorage=new e("scene"),this.sceneBlobStorage=new e("scene-blob",void 0,e=>{const s={...e};return Array.isArray(s.data)&&(s.data=[...s.data].sort((e,t)=>e.id.localeCompare(t.id))),t(s)}),this.assetStorage=new e("asset"),this.sceneStorage.setBasePath(s),this.sceneBlobStorage.setBasePath(s),this.assetStorage.setBasePath(s)}getAssets(){return this.assetStorage.getAll()}getAsset(e){return this.assetStorage.get(e)}getScenes(){return this.sceneStorage.getAll()}getScene(e){return this.sceneStorage.get(e)}async getSceneData(e){const t=await this.sceneBlobStorage.get(e);return null==t?null:"string"==typeof t.data?s(t.data):t.data}}/*
|
|
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
|
-
export class RuntimeBundledBackendService{constructor(){this.assets=new Map,this.scenes=new Map,this.sceneBlobs=new Map}getAssets(){return Promise.resolve(Array.from(this.assets.values()))}getAsset(e){return Promise.resolve(this.assets.get(e))}getScenes(){return Promise.resolve(Array.from(this.scenes.values()))}getScene(e){return Promise.resolve(this.scenes.get(e))}async getSceneData(e){const
|
|
1
|
+
import{decodeStorageValue as e,parseStorageJson as s}from"./storage/packed-json.js";export class RuntimeBundledBackendService{constructor(){this.assets=new Map,this.scenes=new Map,this.sceneBlobs=new Map}getAssets(){return Promise.resolve(Array.from(this.assets.values()))}getAsset(e){return Promise.resolve(this.assets.get(e))}getScenes(){return Promise.resolve(Array.from(this.scenes.values()))}getScene(e){return Promise.resolve(this.scenes.get(e))}async getSceneData(e){const t=this.sceneBlobs.get(e);return"string"==typeof t.data?s(t.data):t.data}static tryCreate(){}async preloadData(){const s=new URL("../hology_data.json",import.meta.url),t=await(await fetch(s)).json()??{};for(const s of t.asset??[]){const t=e(s);this.assets.set(t.id,t)}for(const e of t.scene??[])this.scenes.set(e.id,e),this.scenes.set(e.name,e);for(const s of t["scene-blob"]??[]){const t=e(s);this.sceneBlobs.set(t.id,t)}}}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Serializes storage data while compacting explicitly selected, machine-generated fields. */
|
|
2
|
+
export declare function serializeStorageJson(value: unknown): string;
|
|
3
|
+
/** Parses storage JSON and transparently restores packed fields. */
|
|
4
|
+
export declare function parseStorageJson<T = unknown>(text: string): T;
|
|
5
|
+
/** Restores packed fields in data which has already been parsed, such as a runtime bundle. */
|
|
6
|
+
export declare function decodeStorageValue<T>(value: T): T;
|
|
7
|
+
//# sourceMappingURL=packed-json.d.ts.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{gzipSync as r,gunzipSync as e,strFromU8 as t,strToU8 as n}from"fflate";const o="@hology/packed-json:1:gzip:base64:";export function serializeStorageJson(r){return JSON.stringify(a(r),null,2)}export function parseStorageJson(r){return JSON.parse(r,(r,e)=>f(e))}export function decodeStorageValue(r){const e=f(r);return e!==r?decodeStorageValue(e):Array.isArray(r)?r.map(r=>decodeStorageValue(r)):y(r)?Object.fromEntries(Object.entries(r).map(([r,e])=>[r,decodeStorageValue(e)])):r}function a(r){if(Array.isArray(r))return r.map(a);if(!y(r))return r;const e={};for(const[t,n]of Object.entries(r))"vertexMaterials"===t&&Array.isArray(n)?e[t]=l(n):"landscape"===t&&y(n)?e[t]=s(n):"paintedScatter"===t&&y(n)?e[t]=i(n):"secondaryMotion"===t&&y(n)?e[t]=c(n):e[t]=a(n);return e}function s(r){const e=u(r);return Array.isArray(r.heightMaps)&&(e.heightMaps=l(r.heightMaps)),Array.isArray(r.holes)&&(e.holes=l(r.holes)),e}function i(r){const e=u(r);return Array.isArray(r.cells)&&(e.cells=r.cells.map(r=>{if(!y(r))return a(r);const e=u(r);return Array.isArray(r.instances)&&(e.instances=l(r.instances)),e})),e}function c(r){const e=u(r);return Array.isArray(r.regions)&&(e.regions=r.regions.map(r=>{if(!y(r))return a(r);const e=u(r);return Array.isArray(r.paintedVertices)&&(e.paintedVertices=l(r.paintedVertices)),e})),e}function u(r){return Object.fromEntries(Object.entries(r).map(([r,e])=>[r,a(e)]))}function l(e){const t=JSON.stringify(e);return t.length<128?a(e):o+function(r){let e="";const t=32768;for(let n=0;n<r.length;n+=t)e+=String.fromCharCode(...r.subarray(n,n+t));return btoa(e)}(r(n(t),{level:9}))}function f(r){if("string"!=typeof r||!r.startsWith(o))return r;const n=r.slice(34);try{return JSON.parse(t(e(function(r){const e=atob(r),t=new Uint8Array(e.length);for(let r=0;r<e.length;r++)t[r]=e.charCodeAt(r);return t}(n))))}catch(r){throw Object.assign(new Error("Failed to decode packed Hology storage data"),{cause:r})}}function y(r){return null!=r&&"object"==typeof r&&!Array.isArray(r)}/*
|
|
2
|
+
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
|
+
* See the LICENSE.md file for details.
|
|
4
|
+
*/
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Observable } from 'rxjs';
|
|
2
|
+
export { decodeStorageValue, parseStorageJson, serializeStorageJson } from './packed-json.js';
|
|
2
3
|
export interface StorageEntity {
|
|
3
4
|
id: string;
|
|
4
5
|
name: string;
|
|
@@ -90,5 +91,4 @@ export type StorageWatchEvent<T> = {
|
|
|
90
91
|
filename: string;
|
|
91
92
|
};
|
|
92
93
|
export declare function defaultSerializer<T>(obj: Partial<T>): string;
|
|
93
|
-
export {};
|
|
94
94
|
//# sourceMappingURL=storage.d.ts.map
|