@hology/core 0.0.272 → 0.0.274
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/effects/sequence/sequence-player.js +1 -1
- package/dist/effects/vfx/gpu-sprite-runtime.js +1 -1
- package/dist/effects/vfx/gpu-vfx-world-service.js +1 -1
- package/dist/effects/vfx/rates.d.ts +2 -0
- package/dist/effects/vfx/rates.js +1 -1
- package/dist/effects/vfx/vfx-actor.d.ts +2 -1
- package/dist/effects/vfx/vfx-actor.js +1 -1
- package/dist/effects/vfx/vfx-asset.d.ts +10 -1
- package/dist/effects/vfx/vfx-backend-partition.d.ts +8 -0
- package/dist/effects/vfx/vfx-backend-partition.js +4 -0
- package/dist/effects/vfx/vfx-materializer.js +1 -1
- package/dist/effects/vfx/vfx-render-preparation.js +1 -1
- package/dist/effects/vfx/vfx-renderers.d.ts +27 -2
- package/dist/effects/vfx/vfx-renderers.js +1 -1
- package/dist/gameplay/actors/builtin/components/character/character-animation.d.ts +2 -0
- package/dist/gameplay/actors/builtin/components/character/character-animation.js +1 -1
- package/dist/gameplay/actors/builtin/components/character/character-movement.d.ts +19 -0
- package/dist/gameplay/actors/builtin/components/character/character-movement.js +1 -1
- package/dist/gameplay/actors/builtin/water-spline-volume-actor.d.ts +10 -0
- package/dist/gameplay/actors/builtin/water-spline-volume-actor.js +1 -1
- package/dist/gameplay/actors/builtin/water-volume-actor.d.ts +3 -1
- package/dist/gameplay/actors/builtin/water-volume-actor.js +1 -1
- package/dist/gameplay/actors/camera/first-person-camera-component.js +1 -1
- package/dist/gameplay/actors/camera/third-person-camera-component.d.ts +7 -0
- package/dist/gameplay/actors/camera/third-person-camera-component.js +1 -1
- package/dist/gameplay/ai/behavior-tree/move.d.ts +5 -0
- package/dist/gameplay/ai/behavior-tree/move.js +1 -1
- package/dist/gameplay/ai/behavior-tree/move.test.d.ts +2 -0
- package/dist/gameplay/ai/behavior-tree/move.test.js +4 -0
- package/dist/gameplay/animation/root-motion.js +1 -1
- package/dist/gameplay/input/input-service.d.ts +9 -1
- package/dist/gameplay/input/input-service.js +1 -1
- package/dist/gameplay/services/physics/physics-system.d.ts +2 -0
- package/dist/gameplay/services/physics/physics-system.js +1 -1
- package/dist/gameplay/services/render.d.ts +4 -1
- package/dist/gameplay/services/render.js +1 -1
- package/dist/gameplay/services/world.d.ts +5 -0
- package/dist/rendering/node-material-webgpu-resolver.js +1 -1
- package/dist/rendering/webgpu-experimental-backend.d.ts +4 -2
- package/dist/rendering/webgpu-experimental-backend.js +1 -1
- package/dist/rendering.d.ts +17 -2
- package/dist/rendering.js +1 -1
- package/dist/scene/asset-resource-loader.d.ts +4 -0
- package/dist/scene/asset-resource-loader.js +1 -1
- package/dist/scene/collision/collision-shape-import.js +1 -1
- package/dist/scene/collision/rapier-shape.js +1 -1
- package/dist/scene/landscape/landscape-geometry.d.ts +24 -0
- package/dist/scene/landscape/landscape-geometry.js +4 -0
- package/dist/scene/landscape/landscape-manager.d.ts +1 -0
- package/dist/scene/landscape/landscape-manager.js +1 -1
- package/dist/scene/materializer.d.ts +7 -1
- package/dist/scene/materializer.js +1 -1
- package/dist/scene/scene-data-service.d.ts +2 -1
- package/dist/scene/scene-data-service.js +1 -1
- package/dist/scene/sky.d.ts +2 -0
- package/dist/scene/sky.js +1 -1
- package/dist/shader/graph/compiler.js +1 -1
- package/dist/shader/graph/registry.js +1 -1
- package/dist/shader/parameter.js +1 -1
- package/dist/shader/sprite-shader.js +1 -1
- package/dist/shader-nodes/depth.d.ts +6 -4
- package/dist/shader-nodes/depth.js +1 -1
- package/dist/shader-nodes/index.d.ts +2 -1
- package/dist/shader-nodes/index.js +1 -1
- package/dist/shader-nodes/surface-reflection.d.ts +9 -0
- package/dist/shader-nodes/surface-reflection.js +4 -0
- package/dist/shader-nodes/water-volume.d.ts +6 -2
- package/dist/shader-nodes/water-volume.js +1 -1
- package/dist/test/asset-resource-loader-texture-array.test.js +1 -1
- package/dist/test/character-movement-solve.test.js +1 -1
- package/dist/test/collision-shape-import.test.js +1 -1
- package/dist/test/first-person-camera-component.test.js +1 -1
- package/dist/test/frame-stats.test.js +1 -1
- package/dist/test/gpu-sprite-compiler.test.js +1 -1
- package/dist/test/input.test.js +1 -1
- package/dist/test/landscape-geometry.test.d.ts +2 -0
- package/dist/test/landscape-geometry.test.js +4 -0
- package/dist/test/material-assignment.test.js +1 -1
- package/dist/test/materializer-dynamic-transform.test.js +1 -1
- package/dist/test/materializer-prefetch.test.js +1 -1
- package/dist/test/net-character-movement.test.js +1 -1
- package/dist/test/node-material-webgpu-resolver.test.js +1 -1
- package/dist/test/parameter-definition.test.js +1 -1
- package/dist/test/physics-hitstop.test.d.ts +2 -0
- package/dist/test/physics-hitstop.test.js +4 -0
- package/dist/test/physics-ray-object.test.js +1 -1
- package/dist/test/post-process-effect.test.js +1 -1
- package/dist/test/rapier-trimesh.test.d.ts +2 -0
- package/dist/test/rapier-trimesh.test.js +4 -0
- package/dist/test/sequence-animation-retiming.test.js +1 -1
- package/dist/test/sequence-vfx.test.js +1 -1
- package/dist/test/surface-reflection.test.d.ts +2 -0
- package/dist/test/surface-reflection.test.js +4 -0
- package/dist/test/vfx-backend-partition.test.d.ts +2 -0
- package/dist/test/vfx-backend-partition.test.js +4 -0
- package/dist/test/vfx-playback-transform.test.js +1 -1
- package/dist/test/vfx-render-preparation.test.js +1 -1
- package/dist/test/water-spline-volume-actor.test.js +1 -1
- package/dist/utils/mesh.d.ts +1 -1
- package/dist/utils/mesh.js +1 -1
- package/dist/utils/three/frame-stats.d.ts +8 -0
- package/dist/utils/three/frame-stats.js +1 -1
- package/package.json +2 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{describe as t,expect as e,it as i}from"vitest";import{partitionVfxBackends as s}from"../effects/vfx/vfx-backend-partition.js";const n=(t,e,i)=>({id:t,name:t,output:{type:e,space:"world"},children:i});t("VFX backend partition",()=>{i("keeps independent visual roots on GPU and whole light dependency trees on CPU",()=>{const t=n("visual","sprite"),i=n("light","pointLight"),o=n("parent","mesh",[n("child-light","pointLight")]),p={id:"effect",vfx:{emitters:[t,i,o]}},r=s(p);e(r.gpu.vfx.emitters).toEqual([t]),e(r.cpu.vfx.emitters).toEqual([i,o]),e(r.cpu.vfx.emitters[1]).toBe(o),e(p.vfx.emitters).toHaveLength(3),e(s(p).gpu).toBe(r.gpu)}),i("invalidates cached partitions on revision changes",()=>{const t={id:"effect",vfx:{emitters:[n("light","pointLight")]}};e(s(t,1).gpu.vfx.emitters).toHaveLength(0),t.vfx.emitters.push(n("visual","sprite")),e(s(t,2).gpu.vfx.emitters).toHaveLength(1)})});/*
|
|
2
|
+
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
|
+
* See the LICENSE.md file for details.
|
|
4
|
+
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Object3D as e,Vector3 as t}from"three";import{describe as
|
|
1
|
+
import{Object3D as e,Vector3 as t}from"three";import{describe as a,expect as n,it as o,vi as s}from"vitest";s.mock("../gameplay/actors/builtin/index.js",()=>({builtInActors:{},default:{}})),s.mock("../gameplay/actors/index.js",()=>({CharacterAnimationComponent:class{},CharacterMovementComponent:class{}})),s.mock("../rendering.js",()=>({RenderingView:class{},setRenderingPaused:s.fn()})),s.hoisted(()=>{const e=new Proxy({},{get:(e,t)=>e[t]??(e[t]=s.fn()),set:(e,t,a)=>(e[t]=a,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}),a("VFX playback transform",()=>{o("refreshes a reused GPU effect before resuming emission",async()=>{const{VfxActor:a}=await import("../effects/vfx/vfx-actor.js"),o=Object.create(a.prototype),r=new e;r.position.set(12,0,8),o.object=new e,o.object.position.set(0,0,1.75),r.add(o.object),o._worldPos=new t;const i=new t;o.gpuPrepared={},o.gpuHandle={active:!0,setPosition:s.fn(e=>i.copy(e)),setTransform:s.fn(),play:s.fn(()=>n(i.toArray()).toEqual([12,0,9.75])),restart:s.fn(()=>n(i.toArray()).toEqual([20,0,9.75]))},o.play(),n(o.gpuHandle.setTransform).toHaveBeenCalledWith(o.object.matrixWorld),n(o.gpuHandle.play).toHaveBeenCalledOnce(),r.position.x=20,o.restart(),n(o.gpuHandle.restart).toHaveBeenCalledOnce()})}),a("mixed VFX playback",()=>{async function a(){const{VfxActor:a}=await import("../effects/vfx/vfx-actor.js"),{Rate:n}=await import("@hology/nebula"),{Subject:o}=await import("rxjs"),{Euler:r,Quaternion:i}=await import("three"),l=Object.create(a.prototype),p={dead:!1,rate:new n(1,1),particles:[{age:1,life:10}],clearParticlesRecursive:s.fn(),removeAllParticles:s.fn()};return Object.assign(l,{object:new e,_worldPos:new t,_worldRot:new i,_worldScale:new t,_worldEul:new r,_timescale:.5,system:{emitters:[p],update:s.fn(),getCount:s.fn(()=>1)},gpuPrepared:{},gpuHandle:{active:!0,setPosition:s.fn(),setTransform:s.fn(),setTimescale:s.fn(),play:s.fn(),pause:s.fn(),stop:s.fn(),restart:s.fn(),kill:s.fn(),expire:s.fn()},onComplete:new o,paused:!1}),{actor:l,emitter:p}}o("updates CPU simulation with timescale and pauses/resumes both groups",async()=>{const{actor:e}=await a();e.onUpdate(.2),n(e.system.update).toHaveBeenCalledWith(.1),e.pause(),e.onUpdate(.2),n(e.system.update).toHaveBeenCalledTimes(1),n(e.gpuHandle.pause).toHaveBeenCalledOnce(),e.play(),n(e.gpuHandle.play).toHaveBeenCalledOnce(),n(e.paused).toBe(!1)}),o("restarts, stops, expires and kills CPU lights alongside GPU particles",async()=>{const{actor:e,emitter:t}=await a();e.restart(),n(t.clearParticlesRecursive).toHaveBeenCalledOnce(),n(e.gpuHandle.restart).toHaveBeenCalledOnce(),e.stop(),n(t.rate.nextTime).toBe(1/0),n(e.gpuHandle.stop).toHaveBeenCalledOnce(),e.applyClipEndBehavior("expire",.2),n(t.particles[0].life).toBe(1.2),n(e.gpuHandle.expire).toHaveBeenCalledWith(.2),e.applyClipEndBehavior("kill"),n(t.removeAllParticles).toHaveBeenCalledOnce(),n(e.gpuHandle.kill).toHaveBeenCalledOnce()}),o("waits for both groups before completing and does not replay a finished GPU tail on resume",async()=>{const{actor:e,emitter:t}=await a(),o=s.fn();e.onComplete.subscribe(o),e.gpuHandle.active=!1,e.updateCompletionState(),n(o).not.toHaveBeenCalled(),e.pause(),e.play(),n(e.gpuHandle.play).not.toHaveBeenCalled(),t.dead=!0,e.updateCompletionState(),e.updateCompletionState(),n(o).toHaveBeenCalledOnce()}),o("waits for delayed light emission, then completes after its last particle drains",async()=>{const{actor:e,emitter:t}=await a(),{OnceRate:o}=await import("../effects/vfx/rates.js");t.rate=new o(.5,1),t.particles=[],e.gpuHandle.active=!1;const r=s.fn();e.onComplete.subscribe(r),e.updateCompletionState(),n(r).not.toHaveBeenCalled(),n(t.rate.getValue(.6)).toBe(1),t.particles.push({age:0,life:1}),e.updateCompletionState(),n(r).not.toHaveBeenCalled(),t.particles.length=0,e.updateCompletionState(),n(r).toHaveBeenCalledOnce()}),o("can restart a completed GPU group while paused and spawn it on the next play",async()=>{const{actor:e}=await a(),t=e.gpuHandle;t.active=!1,t.release=s.fn(),e.paused=!0,e.inputRuntime={getDefinitions:()=>[]},e.gpuPrepared.spawn=s.fn(()=>(t.active=!0,t)),e.restart(),n(t.release).toHaveBeenCalledOnce(),n(e.gpuHandle).toBeNull(),e.play(),n(e.gpuPrepared.spawn).toHaveBeenCalledOnce(),n(e.gpuHandle.active).toBe(!0)})}),o("materializes only light roots on CPU when the visual roots prepare on GPU",async()=>{const{VfxActor:t}=await import("../effects/vfx/vfx-actor.js"),{VfxInputRuntime:a}=await import("../effects/vfx/vfx-input-runtime.js"),o=Object.create(t.prototype),r=(e,t)=>({id:e,name:e,output:t,rate:{type:"once",count:1,delay:0},position:{x:0,y:0,z:0},rotation:{x:0,y:0,z:0},initializers:[],behaviours:[]}),i=r("visual",{type:"sprite",space:"world"}),l={id:"mixed",type:"vfx",vfx:{emitters:[i,r("light",{type:"pointLight",space:"local",maxLights:1})]}};Object.assign(o,{object:new e,world:{scene:new e},inputOverrides:new Map,inputRuntime:new a,view:{vfxBackendPreference:"auto"},gpuWorld:{prepare:s.fn(async()=>({}))}}),await o.fromAsset(l),n(o.runtimeBackend).toBe("mixed"),n(o.gpuWorld.prepare.mock.calls[0][0].vfx.emitters).toEqual([i]),n(o.system.emitters).toHaveLength(1),n(o.renderBodies).toHaveLength(1),n(o.renderBodies[0].type).toBe("pointLight"),o.system.destroy(),o.disposeSystem()});/*
|
|
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{describe as e,expect as t,it as n,vi as o}from"vitest";import*as a from"three";import{NodeShaderMaterial as s}from"three-shader-graph";import{RenderingView as
|
|
1
|
+
import{describe as e,expect as t,it as n,vi as o}from"vitest";import*as a from"three";import{NodeShaderMaterial as s}from"three-shader-graph";import{RenderingView as i}from"../rendering.js";import{createVfxRenderPreparation as r,prepareVfxRendering as c}from"../effects/vfx/vfx-render-preparation.js";import{StretchedSprite as l}from"../effects/vfx/stretched-sprite.js";import{materializeVfx as d}from"../effects/vfx/vfx-materializer.js";o.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=("string"!=typeof t||!t.startsWith("is"))&&o.fn()),e[t]),set:(e,t,n)=>(e[t]=n,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}),e("VFX rendering preload",()=>{n("batches concurrent effects and keeps their probe resources alive until compilation finishes",async()=>{let e;const n=new Promise(t=>{e=t}),i=o.fn(e=>n),r={compileAsync:i,activeBackend:"webgpu"},l={getCamera:()=>new a.PerspectiveCamera},d=new s({}),p=new a.PlaneGeometry,m=[0,1].map(()=>c([new a.Mesh(p,d)],l,r));await new Promise(e=>setTimeout(e,5)),t(i).toHaveBeenCalledTimes(1);const f=i.mock.calls[0][0];t(f.children).toHaveLength(2);let u=0;f.traverse(e=>{e instanceof a.Mesh&&u++}),t(u).toBe(2);const v=f.children[0].children[0],h=o.spyOn(v.geometry,"dispose"),w=o.spyOn(p,"dispose"),y=o.spyOn(d,"dispose");t(h).not.toHaveBeenCalled(),e(),await Promise.all(m),t(h).toHaveBeenCalledOnce(),t(w).not.toHaveBeenCalled(),t(y).not.toHaveBeenCalled(),t(f.children).toHaveLength(0)}),n("rejects every effect in a failed batch and allows subsequent preparation",async()=>{const e=o.fn().mockRejectedValueOnce(new Error("compile failed")).mockResolvedValue(void 0),n=o.fn(),s={compileAsync:e,initTextures:n,activeBackend:"webgl"},i={getCamera:()=>new a.PerspectiveCamera},r=await Promise.allSettled([0,1].map(()=>c([],i,s)));t(r.map(e=>e.status)).toEqual(["rejected","rejected"]),t(e).toHaveBeenCalledTimes(1),t(n).not.toHaveBeenCalled(),await c([],i,s),t(e).toHaveBeenCalledTimes(2),t(n).toHaveBeenCalledOnce()}),n("includes delayed and nested collision outputs without emitting any particles",async()=>{const e=(e,t=[])=>({id:e,name:e,position:[0,0,0],rotation:[0,0,0],rate:{type:"once",count:0,delay:100},initializers:[],behaviours:[],children:t,spawnEvent:"collision",output:{type:"sprite",texture:e,space:"world",color:"#ffffff",materialSource:"default"}}),n={type:"vfx",id:"test",vfx:{emitters:[e("root",[e("child",[e("grandchild")])])]}},s={getCamera:()=>new a.PerspectiveCamera},i=await d(n,new a.Object3D,{getTexture:o.fn(async()=>new a.Texture)},s,{},{},{},{});t(i.renderBodies).toHaveLength(3),t(i.system.getCount()).toBe(0);const c=r(i.renderBodies,s);t(c.scene.children).toHaveLength(3),t(i.system.getCount()).toBe(0),c.dispose(),i.dispose(),i.system.destroy()}),n("prepares mesh, sprite and stretched output variants without disposing source resources",()=>{const e=new s({});e.transparent=!0;const n=new a.PlaneGeometry,i=new a.Mesh(n,e),c=new l(new a.PlaneGeometry,new s({})),d=new a.Sprite(new a.SpriteMaterial),p=o.spyOn(n,"dispose"),m=o.spyOn(e,"dispose"),f=r([i,c,d],{getCamera:()=>new a.PerspectiveCamera}),u=[];f.scene.traverse(e=>{(e instanceof a.Mesh||e instanceof a.Sprite)&&u.push(e)}),t(u).toHaveLength(3);const v=u.find(e=>e instanceof a.InstancedMesh);t(v.count).toBe(1),t(v.material).toBe(e),t(v.geometry.getAttribute("particleData")).toBeDefined(),t(v.geometry).not.toBe(n),f.dispose(),t(p).not.toHaveBeenCalled(),t(m).not.toHaveBeenCalled(),t(f.scene.children).toHaveLength(0)}),n("waits for every concurrent compilation and recovers after a failed request",async()=>{let e;const n=new Promise(t=>{e=t}),s=o.fn().mockImplementationOnce(()=>n).mockRejectedValueOnce(new Error("compile failed")).mockResolvedValue(void 0),r=Object.create(i.prototype);r.compileTail=Promise.resolve(),r.compileSceneAsync=s;const c=[new a.Scene,new a.Scene,new a.Scene],l=c.map(e=>r.compileAsync(e)),d=Promise.allSettled(l);await Promise.resolve(),await Promise.resolve(),t(s).toHaveBeenCalledTimes(1),e();const p=await d;t(s.mock.calls.map(e=>e[0])).toEqual(c),t(p.map(e=>e.status)).toEqual(["fulfilled","rejected","fulfilled"]),t(r.compileInProgress).toBe(!1)})}),e("VFX point lights",()=>{n("caps live lights, reuses slots, and maps alpha/scale without unlimited radii",async()=>{const{Particle:e}=await import("@hology/nebula"),{PointLightRenderer:n}=await import("../effects/vfx/vfx-renderers.js"),o=new a.Object3D,s=new n(o),i={type:"pointLight",output:{type:"pointLight",color:"#ffffff",intensity:8,radius:4,maxLights:1}},r=()=>{const t=new e;return t.body=i,t.scale=.5,t.useAlpha=!0,t.alpha=.25,t.position.set(1,2,3),t},c=r();s.onParticleCreated(c);const l=c.target;t(l.position.toArray()).toEqual([1,2,3]),t(l.intensity).toBe(2),t(l.distance).toBe(2),t(l.castShadow).toBe(!1);const d=r();s.onParticleCreated(d),t(d.target).toBeUndefined(),t(o.children).toHaveLength(1),s.onParticleDead(c),t(o.children).toHaveLength(0);const p=r();s.onParticleCreated(p),t(p.target).toBe(l),p.scale=0,s.onParticleUpdate(p),t(l.visible).toBe(!1),t(l.distance).toBeGreaterThan(0),s.dispose(),t(o.children).toHaveLength(0)})});/*
|
|
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{describe as e,expect as t,it as n,vi as o}from"vitest";import{Box3 as a,ShaderMaterial as i,Vector3 as s}from"three";import{NavigationArea as r}from"../gameplay/ai/navigation-areas.js";import{createWaterSplineSurfaceGeometry as l,WaterSplineVolume as c}from"../gameplay/actors/builtin/water-spline-volume-actor.js";import{
|
|
1
|
+
import{describe as e,expect as t,it as n,vi as o}from"vitest";import{Box3 as a,ShaderMaterial as i,Vector3 as s}from"three";import{NavigationArea as r}from"../gameplay/ai/navigation-areas.js";import{createWaterSplineSurfaceGeometry as l,WaterSplineVolume as c}from"../gameplay/actors/builtin/water-spline-volume-actor.js";import{WaterVolume as u}from"../gameplay/actors/builtin/water-volume-actor.js";import{packSplineData as g}from"../gameplay/actors/builtin/components/spline-component.js";function w(e,t){e.spline.setData(g([{position:new s,inTangent:new s,outTangent:new s(t/3,0,0)},{position:new s(t,0,0),inTangent:new s(-t/3,0,0),outTangent:new s}],!1),!1)}o.hoisted(()=>{if("undefined"!=typeof HTMLCanvasElement){const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=o.fn()),e[t]),set:(e,t,n)=>(e[t]=n,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}"undefined"==typeof AudioBuffer&&Object.defineProperty(globalThis,"AudioBuffer",{configurable:!0,value:class{}})}),e("WaterSplineVolume",()=>{n("creates water navigation prisms centered below the spline surface",()=>{const e=new c;e.width=4,e.depth=3,w(e,10);const n=e.getNavigationAreaModifiers();t(n).toHaveLength(20),t(n.every(e=>e.area===r.Water)).toBe(!0),t(n[0].vertices[0]).toEqual([0,0,2]),t(n[0].vertices[1]).toEqual([0,0,-2]),t(n[0].minHeight).toBe(-3),t(n[0].maxHeight).toBe(0)}),n("rebuilds the editor visualization when the spline changes",()=>{const e=new c;e.width=2,e.depth=1,w(e,2);const n=Object.values(e).find(e=>"WaterSplineVolumeEditorComponent"===e?.constructor?.name);n.onInit();const o=[...n.visual.children],i=(new a).setFromObject(n.visual);t(i.max.x).toBeCloseTo(2),w(e,6),e.spline.changed.next(e.spline),i.setFromObject(n.visual),t(i.max.x).toBeCloseTo(6),t(i.min.y).toBeCloseTo(-1),t(n.visual.children).toEqual(o),t(o.every(e=>!1===e.frustumCulled)).toBe(!0)}),n("clamps offset joins at sharp turns",()=>{const e=new c;e.width=10,e.depth=1,e.spline.setData(g([{position:new s(-1,0,0),inTangent:new s,outTangent:new s(1/3,0,0)},{position:new s(0,0,0),inTangent:new s(-1/3,0,0),outTangent:new s(0,0,1/3)},{position:new s(0,0,1),inTangent:new s(0,0,-1/3),outTangent:new s}],!1),!1);const n=Object.values(e).find(e=>"WaterSplineVolumeEditorComponent"===e?.constructor?.name);n.onInit();const o=(new a).setFromObject(n.visual);t(o.min.x).toBeGreaterThanOrEqual(-1-e.width*p/2),t(o.max.x).toBeLessThanOrEqual(e.width*p/2),t(o.min.z).toBeGreaterThanOrEqual(-e.width*p/2),t(o.max.z).toBeLessThanOrEqual(1+e.width*p/2);const i=l(e.spline,e.width,.1,1),r=i.getAttribute("position"),u=i.getIndex();for(let e=0;e<u.count;e+=3){const n=u.getX(e),o=u.getX(e+1),a=u.getX(e+2),i=(r.getX(o)-r.getX(n))*(r.getZ(a)-r.getZ(n))-(r.getZ(o)-r.getZ(n))*(r.getX(a)-r.getX(n));t(i).toBeLessThanOrEqual(1e-6)}}),n("creates tessellated surface data for water shaders",()=>{const e=new c;w(e,10);const n=l(e.spline,4,2,2),o=n.getAttribute("position").count;t(o).toBeGreaterThan(0),t(n.getAttribute("uv").count).toBe(o),t(n.getAttribute("waterFlowDirection").count).toBe(o),t(Math.max(...Array.from({length:o},(e,t)=>n.getAttribute("waterDistanceFromBank").getX(t)))).toBeGreaterThan(0),t(Math.max(...Array.from({length:o},(e,t)=>n.getAttribute("waterDistanceAlongSpline").getX(t)))).toBeCloseTo(10)}),n("classifies points inside the authored spline prism",()=>{const e=new c;e.width=4,e.depth=3,w(e,10),t(e.containsWaterPoint(new s(5,-1,0))).toBe(!0),t(e.containsWaterPoint(new s(5,1,0))).toBe(!1),t(e.containsWaterPoint(new s(5,-4,0))).toBe(!1),t(e.containsWaterPoint(new s(5,-1,3))).toBe(!1)}),n("applies underwater graph changes without recreating the actor",()=>{const e=new c,n=new i;t(e.underwater.applySceneParameters({postProcessMaterial:n},new Set(["postProcessMaterial"]))).toBe(!0),t(e.underwater.postProcessMaterial).toBe(n)}),n("samples transformed surfaces and invalidates cached spline geometry without change events",()=>{const e=new c;e.depth=3,w(e,10),e.object.position.y=5,e.object.scale.y=2,t(e.getWaterSurfaceHeight(new s(5,4,0))).toBeCloseTo(5);const n=e.waterSections;t(e.getWaterSurfaceHeight(new s(5,3,0))).toBeCloseTo(5),t(e.waterSections).toBe(n),e.object.position.y=6,t(e.getWaterSurfaceHeight(new s(5,4,0))).toBeCloseTo(6),w(e,2),t(e.getWaterSurfaceHeight(new s(5,4,0))).toBeNull(),e.width=1,t(e.getWaterSurfaceHeight(new s(1,4,1))).toBeNull(),e.depth=.1,t(e.getWaterSurfaceHeight(new s(1,4,0))).toBeNull()}),n("samples a sloping spline surface at the query position",()=>{const e=new c;e.spline.setData(g([{position:new s,inTangent:new s,outTangent:new s(10/3,2/3,0)},{position:new s(10,2,0),inTangent:new s(-10/3,-2/3,0),outTangent:new s}],!1),!1),t(e.getWaterSurfaceHeight(new s(5,0,0))).toBeCloseTo(1),t(e.getWaterSurfaceHeight(new s(5,2,0))).toBeNull()}),n("samples the scaled and tilted box top face",()=>{const e=new u;e.object.scale.set(10,4,10),e.object.position.y=3,t(e.getWaterSurfaceHeight(new s(0,2,0))).toBeCloseTo(5),t(e.getWaterSurfaceHeight(new s(0,0,0))).toBeNull(),t(e.getWaterSurfaceHeight(new s(6,2,0))).toBeNull(),e.object.rotation.z=Math.PI/6,t(e.getWaterSurfaceHeight(new s(0,3,0))).toBeCloseTo(3+2/Math.cos(Math.PI/6))})});const p=1.5;/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
package/dist/utils/mesh.d.ts
CHANGED
package/dist/utils/mesh.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as e from"three";import{clone as t}from"three/examples/jsm/utils/SkeletonUtils.js";import{mergeGeometries as
|
|
1
|
+
import*as e from"three";import{clone as t}from"three/examples/jsm/utils/SkeletonUtils.js";import{mergeGeometries as r,mergeGroups as n}from"three/examples/jsm/utils/BufferGeometryUtils.js";import{Matrix4 as s}from"three";export class NoRaycastMesh extends e.Mesh{raycast(...e){}}export function cloneMesh(e){const r=t(e),n=[],s=[];e.traverse(e=>n.push(e)),r.traverse(e=>s.push(e));const i=new Map,l=[];for(let e=0;e<n.length;e++){const t=n[e],r=s[e];if(!0!==t?.isSkinnedMesh||!0!==r?.isSkinnedMesh)continue;let a=i.get(t.skeleton);null==a&&(a=l.find(e=>o(t.skeleton,e.source))?.clone),null!=a?(i.set(t.skeleton,a),r.bind(a,r.bindMatrix)):(i.set(t.skeleton,r.skeleton),l.push({source:t.skeleton,clone:r.skeleton}))}return r}function o(e,t){if(e.bones.length!==t.bones.length||e.boneInverses.length!==t.boneInverses.length)return!1;for(let r=0;r<e.bones.length;r++){if(e.bones[r].name!==t.bones[r].name)return!1;const n=e.boneInverses[r].elements,s=t.boneInverses[r].elements;for(let e=0;e<16;e++)if(n[e]!==s[e])return!1}return!0}export function mergeCompatibleSkinnedMeshes(t,o=()=>!1){t.updateWorldMatrix(!0,!0);const a=t.matrixWorld.clone().invert(),m=[],u=new s;t.traverse(e=>{const t=e;if(!0!==t.isSkinnedMesh||Array.isArray(t.material)||null==t.material||o(t))return;if(!function(e){for(const t in e)if(Object.hasOwn(e,t))return!1;return!0}(t.geometry.morphAttributes))return;u.copy(a).multiply(t.matrixWorld);const r=m.find(e=>{return e.skeleton===t.skeleton&&i(e.bindMatrix,t.bindMatrix)&&i(e.relativeMatrix,u)&&(r=e.material,n=t.material,r===n||r.name.length>0&&r.name===n.name)&&function(e,t){return e.visible===t.visible&&e.renderOrder===t.renderOrder&&e.frustumCulled===t.frustumCulled&&e.layers.mask===t.layers.mask}(e.meshes[0],t);var r,n});null==r?m.push({material:t.material,skeleton:t.skeleton,bindMatrix:t.bindMatrix.clone(),relativeMatrix:u,meshes:[t],geometries:[t.geometry]}):(r.meshes.push(t),r.geometries.push(t.geometry))});for(const s of m){if(s.meshes.length<2||!l(s.geometries))continue;const o=r(s.geometries,!1);if(null==o)continue;o.groups.length>1&&n(o);const i=new e.SkinnedMesh(o,s.material);i.copy(s.meshes[0],!1),i.geometry=o,i.material=s.material,s.relativeMatrix.decompose(i.position,i.quaternion,i.scale),i.matrix.copy(s.relativeMatrix),i.bind(s.skeleton,s.bindMatrix);for(const e of s.meshes)e.removeFromParent(),e.geometry.dispose();t.add(i)}}function i(e,t,r=1e-6){const n=e.elements,s=t.elements;for(let e=0;e<16;e++)if(Math.abs(n[e]-s[e])>r)return!1;return!0}function l(e){const t=e[0],r=Object.keys(t.attributes).sort();return e.slice(1).every(e=>null==e.index==(null==t.index)&&Object.keys(e.attributes).sort().join("|")===r.join("|")&&r.every(r=>{const n=t.getAttribute(r),s=e.getAttribute(r);return n.itemSize===s.itemSize&&n.normalized===s.normalized&&n.array.constructor===s.array.constructor}))}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import Stats from 'three/examples/jsm/libs/stats.module.js';
|
|
2
|
+
export type RenderingStatsStyle = 'simple' | 'detailed';
|
|
2
3
|
/** Stats.js panels with frame measurement independent of canvas refreshes. */
|
|
3
4
|
export declare class FrameStats {
|
|
4
5
|
readonly dom: HTMLDivElement;
|
|
6
|
+
private readonly panels;
|
|
5
7
|
private readonly fpsPanel;
|
|
6
8
|
private readonly msPanel;
|
|
7
9
|
private readonly memoryPanel;
|
|
@@ -12,8 +14,14 @@ export declare class FrameStats {
|
|
|
12
14
|
private frames;
|
|
13
15
|
private measuredFrames;
|
|
14
16
|
private measuredMs;
|
|
17
|
+
private readonly simpleDom;
|
|
18
|
+
private _style;
|
|
19
|
+
private simpleTime;
|
|
20
|
+
private simpleFrames;
|
|
15
21
|
constructor();
|
|
16
22
|
addPanel<T extends Stats.Panel>(panel: T): T;
|
|
23
|
+
get style(): RenderingStatsStyle;
|
|
24
|
+
set style(value: RenderingStatsStyle);
|
|
17
25
|
showPanel(index: number): void;
|
|
18
26
|
reset(): void;
|
|
19
27
|
begin(): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import e from"three/examples/jsm/libs/stats.module.js";export class FrameStats{constructor(){this.dom=document.createElement("div"),this.fpsPanel=this.addPanel(new e.Panel("FPS","#0ff","#002")),this.msPanel=this.addPanel(new e.Panel("MS avg","#0f0","#020")),this.memoryPanel="memory"in performance?this.addPanel(new e.Panel("MB","#f08","#201")):null,this.mode=0,this.beginTime=0,this.fpsTime=0,this.panelTime=0,this.frames=0,this.measuredFrames=0,this.measuredMs=0,this.dom.style.cssText="position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000",this.dom.addEventListener("click",e=>{e.preventDefault(),this.showPanel((this.mode+1)%this.
|
|
1
|
+
import e from"three/examples/jsm/libs/stats.module.js";export class FrameStats{constructor(){this.dom=document.createElement("div"),this.panels=[],this.fpsPanel=this.addPanel(new e.Panel("FPS","#0ff","#002")),this.msPanel=this.addPanel(new e.Panel("MS avg","#0f0","#020")),this.memoryPanel="memory"in performance?this.addPanel(new e.Panel("MB","#f08","#201")):null,this.mode=0,this.beginTime=0,this.fpsTime=0,this.panelTime=0,this.frames=0,this.measuredFrames=0,this.measuredMs=0,this.simpleDom=document.createElement("div"),this._style="detailed",this.simpleTime=null,this.simpleFrames=0,this.dom.style.cssText="position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000",this.dom.addEventListener("click",e=>{"simple"!==this._style&&(e.preventDefault(),this.showPanel((this.mode+1)%this.panels.length))}),this.simpleDom.style.cssText="display:none;white-space:nowrap",this.dom.appendChild(this.simpleDom),this.showPanel(0),this.reset()}addPanel(e){return this.panels.push(e),this.dom.appendChild(e.dom),"simple"===this._style&&(e.dom.style.display="none"),e}get style(){return this._style}set style(e){this._style=e,this.dom.style.cssText="simple"===e?"position:absolute;top:12px;left:12px;z-index:10000;pointer-events:none;user-select:none;padding:8px 12px;border-radius:6px;background:rgba(12,16,22,.75);color:#f5f7fa;font:500 13px/1.4 system-ui,sans-serif;font-variant-numeric:tabular-nums":"position:absolute;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000";for(const s of Array.from(this.dom.children))s.style.display="simple"===e?"none":"inline-block";this.simpleDom.style.display="simple"===e?"block":"none",this.reset()}showPanel(e){const s=this.panels;for(let i=0;i<s.length;i++)s[i].dom.style.display="detailed"===this._style&&i===e?"block":"none";this.mode=e}reset(){this.beginTime=this.fpsTime=this.panelTime=performance.now(),this.frames=this.measuredFrames=this.measuredMs=0,this.simpleTime=null,this.simpleFrames=0,this.simpleDom.textContent="— FPS · — ms"}begin(){"simple"!==this._style&&(this.beginTime=performance.now())}end(){const e=performance.now();if("simple"===this._style){if(null==this.simpleTime)return void(this.simpleTime=e);this.simpleFrames++;const s=e-this.simpleTime;if(s>=250){const i=s/this.simpleFrames;this.simpleDom.textContent=`${Math.round(1e3/i)} FPS · ${i.toFixed(1)} ms`,this.simpleFrames=0,this.simpleTime=e}return}if(this.frames++,this.measuredFrames++,this.measuredMs+=e-this.beginTime,e-this.panelTime>=250&&(this.msPanel.update(this.measuredMs/this.measuredFrames,200),this.measuredFrames=this.measuredMs=0,this.panelTime=e),e-this.fpsTime>=1e3&&(this.fpsPanel.update(1e3*this.frames/(e-this.fpsTime),100),this.frames=0,this.fpsTime=e,this.memoryPanel)){const e=performance.memory;this.memoryPanel.update(e.usedJSHeapSize/1048576,e.jsHeapSizeLimit/1048576)}}}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hology/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.274",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -231,7 +231,7 @@
|
|
|
231
231
|
"recast-navigation": "0.39.0",
|
|
232
232
|
"rxjs": "7.8.1",
|
|
233
233
|
"three-mesh-bvh": "^0.7.5",
|
|
234
|
-
"three-shader-graph": "0.2.
|
|
234
|
+
"three-shader-graph": "0.2.74",
|
|
235
235
|
"three-stdlib": "2.34.0",
|
|
236
236
|
"ts-key-enum": "^2.0.12",
|
|
237
237
|
"typedi": "^0.10.0"
|