@hology/core 0.0.270 → 0.0.272
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-compiler.js +1 -1
- package/dist/effects/vfx/gpu-sprite-runtime.js +1 -1
- package/dist/effects/vfx/gpu-vfx-world-service.d.ts +2 -0
- package/dist/effects/vfx/gpu-vfx-world-service.js +1 -1
- package/dist/effects/vfx/vfx-actor.js +1 -1
- package/dist/effects/vfx/vfx-materializer.js +1 -1
- package/dist/effects/vfx/vfx-render-preparation.d.ts +3 -0
- package/dist/effects/vfx/vfx-render-preparation.js +1 -1
- package/dist/gameplay/actors/builtin/components/spline-component.d.ts +7 -39
- package/dist/gameplay/actors/builtin/components/spline-component.js +1 -1
- package/dist/gameplay/actors/builtin/navmesh-actor.d.ts +16 -2
- package/dist/gameplay/actors/builtin/navmesh-actor.js +1 -1
- package/dist/gameplay/actors/builtin/water-spline-volume-actor.d.ts +2 -2
- package/dist/gameplay/actors/builtin/water-spline-volume-actor.js +1 -1
- package/dist/gameplay/ai/build-tile.js +1 -1
- package/dist/gameplay/ai/dynamic-tiled-navmesh.js +1 -1
- package/dist/gameplay/ai/dynamic-tiled-navmesh.worker.js +1 -1
- package/dist/gameplay/ai/navigation.d.ts +2 -0
- package/dist/gameplay/ai/navigation.js +1 -1
- package/dist/gameplay/initiate.js +1 -1
- package/dist/gameplay/services/physics/physics-system.d.ts +1 -1
- package/dist/gameplay/services/physics/physics-system.js +1 -1
- package/dist/gameplay/services/render.js +1 -1
- package/dist/navigation/bake.d.ts +68 -0
- package/dist/navigation/bake.js +4 -0
- package/dist/navigation/cache.d.ts +11 -0
- package/dist/navigation/cache.js +4 -0
- package/dist/navigation/spline-data.d.ts +64 -0
- package/dist/navigation/spline-data.js +4 -0
- package/dist/navigation/water-spline-geometry.d.ts +23 -0
- package/dist/navigation/water-spline-geometry.js +4 -0
- package/dist/scene/asset-resource-loader.d.ts +7 -3
- package/dist/scene/asset-resource-loader.js +1 -1
- package/dist/scene/bootstrap.js +1 -1
- package/dist/scene/collision/authored-colliders.d.ts +4 -0
- package/dist/scene/collision/authored-colliders.js +4 -0
- package/dist/scene/collision/rapier-shape.d.ts +7 -0
- package/dist/scene/collision/rapier-shape.js +4 -0
- package/dist/scene/materializer.d.ts +4 -1
- package/dist/scene/materializer.js +1 -1
- package/dist/scene/model.d.ts +4 -0
- package/dist/scene/native-texture.d.ts +13 -0
- package/dist/scene/native-texture.js +4 -0
- package/dist/scene/objects/shapes.js +1 -1
- package/dist/scene/runtime-backend-service.d.ts +1 -0
- package/dist/scene/runtime-bundled-backend-service.d.ts +1 -0
- package/dist/scene/runtime-bundled-backend-service.js +1 -1
- package/dist/shader-nodes/particle-parameter.d.ts +4 -0
- package/dist/shader-nodes/particle-parameter.js +4 -0
- package/dist/test/asset-resource-loader-texture-array.test.js +1 -1
- package/dist/test/authored-collision-rescale.test.js +1 -1
- package/dist/test/gpu-sprite-compiler.test.js +1 -1
- package/dist/test/gpu-sprite-pipeline-cache.test.js +1 -1
- package/dist/test/gpu-vfx-preload.test.d.ts +2 -0
- package/dist/test/gpu-vfx-preload.test.js +4 -0
- package/dist/test/materializer-prefetch.test.js +1 -1
- package/dist/test/native-texture.test.d.ts +2 -0
- package/dist/test/native-texture.test.js +4 -0
- package/dist/test/navigation-bake-backend.test.d.ts +2 -0
- package/dist/test/navigation-bake-backend.test.js +4 -0
- package/dist/test/navigation-bake.test.d.ts +2 -0
- package/dist/test/navigation-bake.test.js +4 -0
- package/dist/test/sequence-vfx.test.js +1 -1
- package/dist/test/shader-graph.test.js +1 -1
- package/dist/test/vfx-render-preparation.test.js +1 -1
- package/package.json +6 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -71,6 +71,8 @@ export declare class GpuVfxWorldService {
|
|
|
71
71
|
private currentEpoch;
|
|
72
72
|
private nextGeneration;
|
|
73
73
|
private rebuildTail;
|
|
74
|
+
private pendingRebuild;
|
|
75
|
+
private readonly pendingEntryBuilds;
|
|
74
76
|
private unregisterRenderGraph;
|
|
75
77
|
private estimatedWorldBytes;
|
|
76
78
|
private profilingEnabled;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{__decorate as e,__metadata as t}from"tslib";import*as s from"three";import{Service as r}from"typedi";import{inject as i}from"../../gameplay/inject.js";import{AssetLoader as n}from"../../gameplay/services/asset-loader.js";import{ViewController as a}from"../../gameplay/services/render.js";import{AssetResourceLoader as o}from"../../scene/asset-resource-loader.js";import{AssetsProvider as l}from"../../scene/assets-provider.js";import{ShaderProvider as h}from"../../gameplay/services/shader-provider.js";import{ensureWebGpuRuntimeBindings as u}from"../../rendering/webgpu-runtime-bridge.js";import{createGpuSpriteShaderGraphResolver as c}from"./vfx-materializer.js";import{GpuSpriteRenderClassCache as d,hasGpuUnsupportedVfxInputBindings as p}from"./gpu-sprite-compiler.js";import{addGpuSpriteRenderGraphPasses as m}from"./gpu-sprite-render-graph.js";import{GpuSpriteWorldRuntime as g}from"./gpu-sprite-world-runtime.js";import{buildGpuSpriteTemplate as f,GpuSpriteTemplateCache as y}from"./gpu-sprite-template.js";import{gpuVfxCapacityClass as v}from"./gpu-vfx-capacity.js";export class GpuVfxEffectHandle{constructor(e,t){this.epoch=e,this.instance=t,this.released=!1}setPosition(e){return!this.released&&this.epoch.runtime.setPosition(this.instance,e)}setTransform(e){return!this.released&&this.epoch.runtime.setTransform(this.instance,e)}pause(){return!this.released&&this.epoch.runtime.pause(this.instance)}play(){return!this.released&&this.epoch.runtime.play(this.instance)}restart(){return!this.released&&this.epoch.runtime.restart(this.instance)}stop(){return!this.released&&this.epoch.runtime.stop(this.instance)}expire(e){return!this.released&&this.epoch.runtime.stop(this.instance,Math.max(0,e))}kill(){return!this.released&&this.epoch.runtime.stop(this.instance,0)}setTimescale(e){return!this.released&&this.epoch.runtime.setTimescale(this.instance,e)}setInput(e,t){return!this.released&&this.epoch.runtime.setInput(this.instance,e,t)}release(){return!this.released&&(this.released=!0,this.epoch.runtime.destroy(this.instance))}get active(){return!this.released&&this.epoch.runtime.has(this.instance)}}export class PreparedGpuVfxEffect{constructor(e,t,s){this.entry=e,this.spawnEntry=t,this.releaseEntry=s,this.released=!1}spawn(e){return this.released?null:this.spawnEntry(this.entry,e)}release(){this.released||(this.released=!0,this.releaseEntry(this.entry))}}let w=class{constructor(){this.view=i(a),this.assetLoader=i(n),this.assetsProvider=i(l),this.assetResources=i(o),this.shaderProvider=i(h),this.templateCache=new y,this.renderClassCache=new d,this.entriesByAssetId=new Map,this.entries=new Set,this.epochs=new Set,this.reportedCpuFallbacks=new Set,this.cpuFallbackReasons=new Map,this.currentEpoch=null,this.nextGeneration=1,this.rebuildTail=Promise.resolve(!0),this.unregisterRenderGraph=null,this.estimatedWorldBytes=0,this.profilingEnabled=!1,this.view.onLateUpdate().subscribe(e=>this.update(e))}setProfilingEnabled(e){this.profilingEnabled=e}getStats(){const e=this.currentEpoch;return null==e?null:{particles:e.runtime.runtime.getStats(),scheduler:e.runtime.scheduler.getStats()}}beginPipelineWarmup(){const e=[],t=new s.Vector3;for(const s of this.epochs){for(const r of s.templateIndices.values()){const i=s.runtime.spawn(r,t);null!=i&&e.push({epoch:s,instance:i})}s.runtime.flushActivePages()}return()=>{for(const{epoch:t,instance:s}of e)t.runtime.destroy(s);for(const e of this.epochs)e.runtime.flushActivePages()}}async prepare(e,t,s){if(this.cpuFallbackReasons.delete(this.fallbackKey(e,t)),"webgpu"!==this.view.requestedBackend)return this.reportCpuFallback(e,t,[`GPU VFX requires the WebGPU renderer; the requested renderer is "${this.view.requestedBackend}".`]),null;if(await u(),p(e))return this.reportCpuFallback(e,t,["one or more authored VFX input bindings are not supported on GPU"]),null;let r=null==s||0===s.size?this.entriesByAssetId.get(e.id):void 0;if(null==r||r.asset!==e||r.revision!==t){const i=r;r={asset:e,revision:t,pending:(async()=>(null!=i&&await this.retireEntry(i.pending),this.createEntry(e,t,s)))()},null!=s&&0!==s.size||this.entriesByAssetId.set(e.id,r)}const i=await r.pending;if(null==i)return this.reportCpuFallback(e,t,["GPU template preparation rejected this asset without a diagnostic"]),null;i.references+=1;const n=v(i.references),a=this.currentEpoch?.reservations.get(i)??0,o=i.reservedInstances;return n>i.reservedInstances&&(i.reservedInstances=n),a<n&&!await this.queueRebuild()?(i.references-=1,i.reservedInstances=o,this.reportCpuFallback(e,t,["the shared GPU VFX world could not grow within its memory budget or WebGPU device limits"]),null):new PreparedGpuVfxEffect(i,(e,t)=>this.spawn(e,t),e=>this.releaseEntry(e))}getFallbackReasons(e,t){return this.cpuFallbackReasons.get(this.fallbackKey(e,t))??[]}async createEntry(e,t,s){const r={assets:this.assets,renderClassCache:this.renderClassCache,resolveCustomMaterial:this.resolveCustomMaterial,revision:t,meshInputs:s},i=null==s||0===s.size?await this.templateCache.resolve(e,r):await f(e,r);if(null==i.template){const s=i.compatibility.issues.filter(e=>"warning"!==e.severity).map(e=>`${e.emitter}: ${e.message}`);return this.reportCpuFallback(e,t,s.length>0?s:i.reasons),null}const n={asset:e,revision:t,references:0,reservedInstances:1,retired:!1,runtimeMeshInput:null!=s&&s.size>0,template:i.template};return this.entries.add(n),n}spawn(e,t){let s=this.currentEpoch,r=s?.templateIndices.get(e);if(null==r&&e.retired)for(const t of this.epochs){const i=t.templateIndices.get(e);t.disposalQueued||null==i||(s=t,r=i)}if(null==s||null==r)return null;const i=s.runtime.spawn(r,t);return null==i?null:new GpuVfxEffectHandle(s,i)}releaseEntry(e){e.references=Math.max(0,e.references-1),!e.runtimeMeshInput||e.references>0||e.retired||(e.retired=!0,this.entries.delete(e),this.queueRebuild())}async retireEntry(e){const t=await e;null!=t&&(t.retired=!0,this.entries.delete(t))}queueRebuild(){const e=this.rebuildTail.then(()=>this.rebuildWorld()).catch(e=>(console.error("[Hology GPU VFX] Failed to rebuild the shared world generation.",e),!1));return this.rebuildTail=e,e}async rebuildWorld(){const e=[...this.entries].filter(e=>!e.retired);if(0===e.length)return null!=this.currentEpoch&&(this.currentEpoch.retired=!0,this.currentEpoch=null,this.disposeRetiredEpochs()),!0;if(null!=this.currentEpoch&&this.currentEpoch.templateIndices.size===e.length&&e.every(e=>(this.currentEpoch.reservations.get(e)??0)>=e.reservedInstances))return!0;const t=[...this.epochs].reduce((e,t)=>t===this.currentEpoch&&0===t.runtime.scheduler.getStats().activeInstances?e:e+t.estimatedBytes,0),s=Math.max(0,this.view.webgpuVfxWorldMemoryBudgetBytes-t),r=await g.create({templates:e.map(e=>({asset:e.asset,revision:e.revision,maximumInstances:e.reservedInstances,preparedTemplate:e.template})),assets:this.assets,templateCache:this.templateCache,renderClassCache:this.renderClassCache,resolveCustomMaterial:this.resolveCustomMaterial,particlePageSize:256,aliveCountReadback:this.profilingEnabled,device:this.view.experimentalWebGpuDevice??void 0,maximumStorageBytes:s});if(null==r)return!1;const i=r.runtime.getStats().storageBytes;if(i>this.view.webgpuVfxWorldMemoryBudgetBytes||t+i>this.view.webgpuVfxWorldMemoryBudgetBytes)return r.dispose(),console.warn(`[Hology GPU VFX] Refusing shared world growth: retained storage would become ${b(t+i)} MiB, exceeding the ${b(this.view.webgpuVfxWorldMemoryBudgetBytes)} MiB budget.`),!1;const n={generation:this.nextGeneration++,runtime:r,templateIndices:new Map(e.map((e,t)=>[e,t])),reservations:new Map(e.map(e=>[e,e.reservedInstances])),estimatedBytes:i,retired:!1,disposalQueued:!1};return null!=this.currentEpoch&&(this.currentEpoch.retired=!0),this.currentEpoch=n,this.epochs.add(n),this.estimatedWorldBytes+=i,this.ensureRenderGraph(),console.info(`[Hology GPU VFX] Shared world generation ${n.generation}: ${e.length} template(s), ${e.reduce((e,t)=>e+t.reservedInstances,0)} reserved instance(s), ${r.runtime.emitters.length} emitter slots, ${r.runtime.getStats().capacity.toLocaleString()} particle slots, ${b(i)} MiB estimated storage.`),this.disposeRetiredEpochs(),!0}ensureRenderGraph(){if(null!=this.unregisterRenderGraph)return;const e=(e,t)=>m(e,this,t,"WorldGpuVfx");e.getSceneTextureReads=()=>this.getSceneTextureReads(),this.unregisterRenderGraph=this.view.registerExperimentalWebGpuRenderGraphExtension(e)}update(e){for(const t of this.epochs)t.runtime.update(e);this.disposeRetiredEpochs()}disposeRetiredEpochs(){for(const e of this.epochs){if(!e.retired||e.disposalQueued||0!==e.runtime.scheduler.getStats().activeInstances||[...e.templateIndices.keys()].some(e=>e.retired&&e.references>0))continue;e.disposalQueued=!0,this.epochs.delete(e),this.estimatedWorldBytes=Math.max(0,this.estimatedWorldBytes-e.estimatedBytes);const t=()=>{e.runtime.dispose(),console.info(`[Hology GPU VFX] Retired shared world generation ${e.generation}, releasing ${b(e.estimatedBytes)} MiB estimated storage.`)},s=this.view.experimentalWebGpuDevice;null==s?t():s.queue.onSubmittedWorkDone().then(t,t)}}reportCpuFallback(e,t,s){const r=this.fallbackKey(e,t);this.cpuFallbackReasons.set(r,[...s]),this.reportedCpuFallbacks.has(r)||(this.reportedCpuFallbacks.add(r),console.warn(`[Hology GPU VFX] Falling back to CPU for "${e.name}".`,s))}fallbackKey(e,t){return`${e.id}:${t??""}`}executeCompute(e){for(const t of this.epochs)t.runtime.runtime.executeCompute(e)}executeRender(e,t){for(const s of this.epochs)s.runtime.runtime.executeRender(e,t)}getSceneTextureReads(){const e=new Set;for(const t of this.epochs)if(t.runtime.runtime.hasActiveParticles)for(const s of t.runtime.runtime.getSceneTextureReads())e.add(s);return[...e]}getComputeTextureReads(){const e=new Set;for(const t of this.epochs)if(t.runtime.runtime.hasActiveParticles)for(const s of t.runtime.runtime.getComputeTextureReads())e.add(s);return[...e]}get assets(){return{getTexture:e=>this.assetLoader.getTextureByAssetId(e),getMesh:async e=>(await this.assetResources.getMesh(await this.assetsProvider.getAsset(e),{applyMaterials:!0,rescale:!0})).scene}}get resolveCustomMaterial(){return c(this.assetsProvider,this.assetResources,this.shaderProvider,{getAsset:e=>this.assetLoader.getAsset(e),getTexture:e=>this.assetLoader.getTextureByAssetId(e),getMaterial:e=>this.assetLoader.getMaterialByAssetId(e),getMesh:e=>this.assetLoader.getModelByAssetId(e).then(e=>e.scene)})}};w=e([r(),t("design:paramtypes",[])],w);export{w as GpuVfxWorldService};function b(e){return(e/1048576).toFixed(1)}/*
|
|
1
|
+
import{__decorate as e,__metadata as t}from"tslib";import*as s from"three";import{Service as r}from"typedi";import{inject as i}from"../../gameplay/inject.js";import{AssetLoader as n}from"../../gameplay/services/asset-loader.js";import{ViewController as a}from"../../gameplay/services/render.js";import{AssetResourceLoader as o}from"../../scene/asset-resource-loader.js";import{AssetsProvider as l}from"../../scene/assets-provider.js";import{ShaderProvider as h}from"../../gameplay/services/shader-provider.js";import{ensureWebGpuRuntimeBindings as u}from"../../rendering/webgpu-runtime-bridge.js";import{createGpuSpriteShaderGraphResolver as c}from"./vfx-materializer.js";import{GpuSpriteRenderClassCache as d,hasGpuUnsupportedVfxInputBindings as p}from"./gpu-sprite-compiler.js";import{addGpuSpriteRenderGraphPasses as m}from"./gpu-sprite-render-graph.js";import{GpuSpriteWorldRuntime as g}from"./gpu-sprite-world-runtime.js";import{buildGpuSpriteTemplate as f,GpuSpriteTemplateCache as y}from"./gpu-sprite-template.js";import{gpuVfxCapacityClass as v}from"./gpu-vfx-capacity.js";export class GpuVfxEffectHandle{constructor(e,t){this.epoch=e,this.instance=t,this.released=!1}setPosition(e){return!this.released&&this.epoch.runtime.setPosition(this.instance,e)}setTransform(e){return!this.released&&this.epoch.runtime.setTransform(this.instance,e)}pause(){return!this.released&&this.epoch.runtime.pause(this.instance)}play(){return!this.released&&this.epoch.runtime.play(this.instance)}restart(){return!this.released&&this.epoch.runtime.restart(this.instance)}stop(){return!this.released&&this.epoch.runtime.stop(this.instance)}expire(e){return!this.released&&this.epoch.runtime.stop(this.instance,Math.max(0,e))}kill(){return!this.released&&this.epoch.runtime.stop(this.instance,0)}setTimescale(e){return!this.released&&this.epoch.runtime.setTimescale(this.instance,e)}setInput(e,t){return!this.released&&this.epoch.runtime.setInput(this.instance,e,t)}release(){return!this.released&&(this.released=!0,this.epoch.runtime.destroy(this.instance))}get active(){return!this.released&&this.epoch.runtime.has(this.instance)}}export class PreparedGpuVfxEffect{constructor(e,t,s){this.entry=e,this.spawnEntry=t,this.releaseEntry=s,this.released=!1}spawn(e){return this.released?null:this.spawnEntry(this.entry,e)}release(){this.released||(this.released=!0,this.releaseEntry(this.entry))}}let w=class{constructor(){this.view=i(a),this.assetLoader=i(n),this.assetsProvider=i(l),this.assetResources=i(o),this.shaderProvider=i(h),this.templateCache=new y,this.renderClassCache=new d,this.entriesByAssetId=new Map,this.entries=new Set,this.epochs=new Set,this.reportedCpuFallbacks=new Set,this.cpuFallbackReasons=new Map,this.currentEpoch=null,this.nextGeneration=1,this.rebuildTail=Promise.resolve(!0),this.pendingRebuild=null,this.pendingEntryBuilds=new Set,this.unregisterRenderGraph=null,this.estimatedWorldBytes=0,this.profilingEnabled=!1,this.view.onLateUpdate().subscribe(e=>this.update(e))}setProfilingEnabled(e){this.profilingEnabled=e}getStats(){const e=this.currentEpoch;return null==e?null:{particles:e.runtime.runtime.getStats(),scheduler:e.runtime.scheduler.getStats()}}beginPipelineWarmup(){const e=[],t=new s.Vector3;for(const s of this.epochs){for(const r of s.templateIndices.values()){const i=s.runtime.spawn(r,t);null!=i&&e.push({epoch:s,instance:i})}s.runtime.flushActivePages()}return()=>{for(const{epoch:t,instance:s}of e)t.runtime.destroy(s);for(const e of this.epochs)e.runtime.flushActivePages()}}async prepare(e,t,s){if(this.cpuFallbackReasons.delete(this.fallbackKey(e,t)),"webgpu"!==this.view.requestedBackend)return this.reportCpuFallback(e,t,[`GPU VFX requires the WebGPU renderer; the requested renderer is "${this.view.requestedBackend}".`]),null;if(await u(),p(e))return this.reportCpuFallback(e,t,["one or more authored VFX input bindings are not supported on GPU"]),null;let r=null==s||0===s.size?this.entriesByAssetId.get(e.id):void 0;if(null==r||r.asset!==e||r.revision!==t){const i=r;r={asset:e,revision:t,pending:(async()=>(null!=i&&await this.retireEntry(i.pending),this.createEntry(e,t,s)))()};const n=r.pending;this.pendingEntryBuilds.add(n),n.then(()=>this.pendingEntryBuilds.delete(n),()=>this.pendingEntryBuilds.delete(n)),null!=s&&0!==s.size||this.entriesByAssetId.set(e.id,r)}const i=await r.pending;if(null==i)return this.reportCpuFallback(e,t,["GPU template preparation rejected this asset without a diagnostic"]),null;i.references+=1;const n=v(i.references),a=this.currentEpoch?.reservations.get(i)??0,o=i.reservedInstances;return n>i.reservedInstances&&(i.reservedInstances=n),a<n&&!await this.queueRebuild()?(i.references-=1,i.reservedInstances=o,this.reportCpuFallback(e,t,["the shared GPU VFX world could not grow within its memory budget or WebGPU device limits"]),null):new PreparedGpuVfxEffect(i,(e,t)=>this.spawn(e,t),e=>this.releaseEntry(e))}getFallbackReasons(e,t){return this.cpuFallbackReasons.get(this.fallbackKey(e,t))??[]}async createEntry(e,t,s){const r={assets:this.assets,renderClassCache:this.renderClassCache,resolveCustomMaterial:this.resolveCustomMaterial,revision:t,meshInputs:s},i=null==s||0===s.size?await this.templateCache.resolve(e,r):await f(e,r);if(null==i.template){const s=i.compatibility.issues.filter(e=>"warning"!==e.severity).map(e=>`${e.emitter}: ${e.message}`);return this.reportCpuFallback(e,t,s.length>0?s:i.reasons),null}const n={asset:e,revision:t,references:0,reservedInstances:1,retired:!1,runtimeMeshInput:null!=s&&s.size>0,template:i.template};return this.entries.add(n),n}spawn(e,t){let s=this.currentEpoch,r=s?.templateIndices.get(e);if(null==r&&e.retired)for(const t of this.epochs){const i=t.templateIndices.get(e);t.disposalQueued||null==i||(s=t,r=i)}if(null==s||null==r)return null;const i=s.runtime.spawn(r,t);return null==i?null:new GpuVfxEffectHandle(s,i)}releaseEntry(e){e.references=Math.max(0,e.references-1),!e.runtimeMeshInput||e.references>0||e.retired||(e.retired=!0,this.entries.delete(e),this.queueRebuild())}async retireEntry(e){const t=await e;null!=t&&(t.retired=!0,this.entries.delete(t))}queueRebuild(){if(null!=this.pendingRebuild)return this.pendingRebuild;const e=this.rebuildTail.then(async()=>{for(;this.pendingEntryBuilds.size>0;)await Promise.allSettled([...this.pendingEntryBuilds]);return this.pendingRebuild=null,this.rebuildWorld()}).catch(t=>(this.pendingRebuild===e&&(this.pendingRebuild=null),console.error("[Hology GPU VFX] Failed to rebuild the shared world generation.",t),!1));return this.pendingRebuild=e,this.rebuildTail=e,e}async rebuildWorld(){const e=[...this.entries].filter(e=>!e.retired);if(0===e.length)return null!=this.currentEpoch&&(this.currentEpoch.retired=!0,this.currentEpoch=null,this.disposeRetiredEpochs()),!0;if(null!=this.currentEpoch&&this.currentEpoch.templateIndices.size===e.length&&e.every(e=>(this.currentEpoch.reservations.get(e)??0)>=e.reservedInstances))return!0;const t=[...this.epochs].reduce((e,t)=>t===this.currentEpoch&&0===t.runtime.scheduler.getStats().activeInstances?e:e+t.estimatedBytes,0),s=Math.max(0,this.view.webgpuVfxWorldMemoryBudgetBytes-t),r=await g.create({templates:e.map(e=>({asset:e.asset,revision:e.revision,maximumInstances:e.reservedInstances,preparedTemplate:e.template})),assets:this.assets,templateCache:this.templateCache,renderClassCache:this.renderClassCache,resolveCustomMaterial:this.resolveCustomMaterial,particlePageSize:256,aliveCountReadback:this.profilingEnabled,device:this.view.experimentalWebGpuDevice??void 0,maximumStorageBytes:s});if(null==r)return!1;const i=r.runtime.getStats().storageBytes;if(i>this.view.webgpuVfxWorldMemoryBudgetBytes||t+i>this.view.webgpuVfxWorldMemoryBudgetBytes)return r.dispose(),console.warn(`[Hology GPU VFX] Refusing shared world growth: retained storage would become ${b(t+i)} MiB, exceeding the ${b(this.view.webgpuVfxWorldMemoryBudgetBytes)} MiB budget.`),!1;const n={generation:this.nextGeneration++,runtime:r,templateIndices:new Map(e.map((e,t)=>[e,t])),reservations:new Map(e.map(e=>[e,e.reservedInstances])),estimatedBytes:i,retired:!1,disposalQueued:!1};return null!=this.currentEpoch&&(this.currentEpoch.retired=!0),this.currentEpoch=n,this.epochs.add(n),this.estimatedWorldBytes+=i,this.ensureRenderGraph(),console.info(`[Hology GPU VFX] Shared world generation ${n.generation}: ${e.length} template(s), ${e.reduce((e,t)=>e+t.reservedInstances,0)} reserved instance(s), ${r.runtime.emitters.length} emitter slots, ${r.runtime.getStats().capacity.toLocaleString()} particle slots, ${b(i)} MiB estimated storage.`),this.disposeRetiredEpochs(),!0}ensureRenderGraph(){if(null!=this.unregisterRenderGraph)return;const e=(e,t)=>m(e,this,t,"WorldGpuVfx");e.getSceneTextureReads=()=>this.getSceneTextureReads(),this.unregisterRenderGraph=this.view.registerExperimentalWebGpuRenderGraphExtension(e)}update(e){for(const t of this.epochs)t.runtime.update(e);this.disposeRetiredEpochs()}disposeRetiredEpochs(){for(const e of this.epochs){if(!e.retired||e.disposalQueued||0!==e.runtime.scheduler.getStats().activeInstances||[...e.templateIndices.keys()].some(e=>e.retired&&e.references>0))continue;e.disposalQueued=!0,this.epochs.delete(e),this.estimatedWorldBytes=Math.max(0,this.estimatedWorldBytes-e.estimatedBytes);const t=()=>{e.runtime.dispose(),console.info(`[Hology GPU VFX] Retired shared world generation ${e.generation}, releasing ${b(e.estimatedBytes)} MiB estimated storage.`)},s=this.view.experimentalWebGpuDevice;null==s?t():s.queue.onSubmittedWorkDone().then(t,t)}}reportCpuFallback(e,t,s){const r=this.fallbackKey(e,t);this.cpuFallbackReasons.set(r,[...s]),this.reportedCpuFallbacks.has(r)||(this.reportedCpuFallbacks.add(r),console.warn(`[Hology GPU VFX] Falling back to CPU for "${e.name}".`,s))}fallbackKey(e,t){return`${e.id}:${t??""}`}executeCompute(e){for(const t of this.epochs)t.runtime.runtime.executeCompute(e)}executeRender(e,t){for(const s of this.epochs)s.runtime.runtime.executeRender(e,t)}getSceneTextureReads(){const e=new Set;for(const t of this.epochs)if(t.runtime.runtime.hasActiveParticles)for(const s of t.runtime.runtime.getSceneTextureReads())e.add(s);return[...e]}getComputeTextureReads(){const e=new Set;for(const t of this.epochs)if(t.runtime.runtime.hasActiveParticles)for(const s of t.runtime.runtime.getComputeTextureReads())e.add(s);return[...e]}get assets(){return{getTexture:e=>this.assetLoader.getTextureByAssetId(e),getMesh:async e=>(await this.assetResources.getMesh(await this.assetsProvider.getAsset(e),{applyMaterials:!0,rescale:!0})).scene}}get resolveCustomMaterial(){return c(this.assetsProvider,this.assetResources,this.shaderProvider,{getAsset:e=>this.assetLoader.getAsset(e),getTexture:e=>this.assetLoader.getTextureByAssetId(e),getMaterial:e=>this.assetLoader.getMaterialByAssetId(e),getMesh:e=>this.assetLoader.getModelByAssetId(e).then(e=>e.scene)})}};w=e([r(),t("design:paramtypes",[])],w);export{w as GpuVfxWorldService};function b(e){return(e/1048576).toFixed(1)}/*
|
|
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{__decorate as t,__metadata as e}from"tslib";import*as s from"three";import{Rate as i}from"@hology/nebula";import{Actor as r,BaseActor as n}from"../../gameplay/actors/actor.js";import{inject as o}from"../../gameplay/inject.js";import{AssetLoader as a}from"../../gameplay/services/asset-loader.js";import{ViewController as l}from"../../gameplay/services/render.js";import{World as p}from"../../gameplay/services/world.js";import{DelayRate as h}from"./rates.js";import{materializeVfx as u}from"./vfx-materializer.js";import{PhysicsSystem as d}from"../../gameplay/services/physics/physics-system.js";import{ShaderProvider as m}from"../../gameplay/services/shader-provider.js";import{AssetResourceLoader as c}from"../../scene/asset-resource-loader.js";import{AssetsProvider as g}from"../../scene/assets-provider.js";import{VfxInputRuntime as f}from"./vfx-input-runtime.js";import{Subject as y}from"rxjs";import{EventGraphCallable as v}from"../../gameplay/event-graph/decorators.js";import{EVENT_GRAPH_VOID_TYPE as w}from"../../gameplay/event-graph/types.js";import{VFX_ACTOR_TYPE_ID as b}from"../../gameplay/event-graph/asset-actor-types.js";import{GpuVfxWorldService as P}from"./gpu-vfx-world-service.js";import{compileGpuSpriteEmitters as S,requiresCpuVfxBackend as j}from"./gpu-sprite-compiler.js";import{withSeededRandom as x}from"./seeded-random.js";import{RenderingView as E}from"../../rendering.js";import{createVfxRenderPreparation as R}from"./vfx-render-preparation.js";let C=class extends n{constructor(){super(...arguments),this.onComplete=new y,this.collisionEvent={emitterId:"",emitterName:"",position:new s.Vector3,normal:new s.Vector3},this._timescale=1,this.paused=!0,this.runtimeBackend="cpu",this.backendFallbackReasons=[],this.profilingEnabled=!1,this.simulationCpuMs=0,this.system=null,this.disposeSystem=null,this.renderBodies=[],this.renderingView=o(E),this.assetLoader=o(a),this.assetManagerService=o(c),this.assetService=o(g),this.world=o(p),this.view=o(l),this.gpuWorld=o(P),this.particleSystemContainer=null,this.gpuPrepared=null,this.gpuHandle=null,this.physics=o(d),this.shaderProvider=o(m),this.inputRuntime=new f,this.inputOverrides=new Map,this.finishRequested=!1,this.completionEmitted=!1,this.inputsMaterialized=!1,this._worldPos=new s.Vector3,this._worldRot=new s.Quaternion,this._worldEul=new s.Euler,this._worldScale=new s.Vector3,this.previewStepSeconds=1/60,this.maxPreviewSteps=240,this.max=0}get timescale(){return this._timescale}set timescale(t){this._timescale=Math.max(0,t),this.gpuHandle?.setTimescale(this._timescale)}async fromAsset(t,e,i,r){if("vfx"!==t.type)throw"Asset must be a VFX asset but is "+t.type;null!=this.system&&(this.system.destroy(),this.system.emitters.forEach(t=>t.reset())),this.gpuHandle?.release(),this.gpuPrepared?.release(),this.gpuHandle=null,this.gpuPrepared=null,this.runtimeBackend="cpu",this.renderBodies=[],this.renderPreparation=void 0,this.backendFallbackReasons=[],this.simulationCpuMs=0,this.sourceAsset=t,this.finishRequested=!1,this.completionEmitted=!1,this.inputsMaterialized=!1,this.disposeSystem&&this.disposeSystem(),null==this.object.parent&&this.world.scene.add(this.object),this.inputRuntime.setDefinitions(this.sourceAsset.vfx.inputs??[]);for(const t of this.inputRuntime.getDefinitions()){if("mesh"!==t.type||"string"!=typeof t.defaultValue||0===t.defaultValue.length)continue;const e=await this.assetService.getAsset(t.defaultValue);if(null==e)continue;const s=await this.assetManagerService.getMesh(e,{applyMaterials:!0,rescale:!0});this.inputRuntime.setInput(t.id,s.scene)}for(const[t,e]of this.inputOverrides)this.inputRuntime.setInput(t,e);null!=e&&this.setInputs(e);const n=i??this.view.vfxBackendPreference,o=j(this.sourceAsset);if("cpu"===n&&(this.backendFallbackReasons=["GPU VFX was not requested; the selected VFX backend preference is CPU."]),"cpu"!==n&&!o){const t=new Map;for(const e of this.inputRuntime.getDefinitions()){if("mesh"!==e.type)continue;const s=this.inputRuntime.getMesh(e.id);null!=s&&t.set(e.id,s)}if(this.gpuPrepared=await this.gpuWorld.prepare(this.sourceAsset,r,t),null!=this.gpuPrepared)return this.runtimeBackend="gpu",this.system=null,this.disposeSystem=null,this.particleSystemContainer=null,this.object.visible=!1,this.object.matrixAutoUpdate=!0,this.object.matrixWorldAutoUpdate=!0,void(this.inputsMaterialized=!0);if(this.backendFallbackReasons=this.gpuWorld.getFallbackReasons(this.sourceAsset,r),"gpu"===n){const t=S(this.sourceAsset,[new s.Vector3]).compatibility;this.backendFallbackReasons=t.issues.filter(t=>"warning"!==t.severity).map(t=>`${t.emitter}: ${t.message}`),0===this.backendFallbackReasons.length&&(this.backendFallbackReasons=["GPU template or material preparation failed; inspect earlier shader diagnostics."])}}const{system:a,dispose:l,container:p,renderBodies:h}=await u(this.sourceAsset,this.object,{getAsset:t=>this.assetLoader.getAsset(t),getMaterial:t=>this.assetLoader.getMaterialByAssetId(t),getTexture:t=>this.assetLoader.getTextureByAssetId(t),getMesh:async t=>(await this.assetManagerService.getMesh(await this.assetService.getAsset(t),{applyMaterials:!0,rescale:!0})).scene},this.view,this.physics,this.shaderProvider,this.assetService,this.assetManagerService,this.inputRuntime,(t,e,s,i,r)=>{if(null==this.onCollision)return;const n=this.collisionEvent;n.emitterId=t,n.emitterName=e,n.position.copy(s),n.normal.copy(i),r&&(n.position.applyMatrix4(this.object.matrixWorld),n.normal.transformDirection(this.object.matrixWorld)),this.onCollision(n)});this.system=a,this.renderBodies=h,this.disposeSystem=l,this.particleSystemContainer=p,this.inputsMaterialized=!0,this.object.visible=!1,this.object.matrixAutoUpdate=!1,this.object.matrixWorldAutoUpdate=!1,this.particleSystemContainer.matrixAutoUpdate=!1,this.particleSystemContainer.matrixWorldAutoUpdate=!1}prepareRendering(){return this.renderPreparation??(this.renderPreparation=this.prepareRenderResources())}async prepareRenderResources(){if(null!=this.gpuPrepared||0===this.renderBodies.length)return;const t=R(this.renderBodies,this.view);try{await this.renderingView.compileAsync(t.scene),"webgpu"!==this.renderingView.activeBackend&&this.renderingView.initTextures(t.scene)}finally{t.dispose()}}setInput(t,e){const i=this.inputRuntime.getDefinition(t);if(this.inputsMaterialized&&"mesh"===i?.type)return void console.warn(`VFX mesh input "${i.name}" is creation-time only; recreate the VFX to replace it.`);this.inputOverrides.set(t,function(t){if(t instanceof s.Vector3||t instanceof s.Color)return t.clone();if(Array.isArray(t))return[...t];return t}(e)),this.inputRuntime.setInput(t,e);const r=this.inputRuntime.getDefinition(t);null!=r&&this.gpuHandle?.setInput(r.id,this.inputRuntime.getValue(r.id))}setInputs(t){for(const[e,s]of Object.entries(t))this.setInput(e,s)}play(){if(this.prepareForPlayback(),this.paused=!1,null!=this.gpuPrepared){if(this.object.getWorldPosition(this._worldPos),null!=this.gpuHandle&&this.gpuHandle.active)this.gpuHandle.setPosition(this._worldPos),this.gpuHandle.setTransform(this.object.matrixWorld),this.gpuHandle.play();else{this.gpuHandle=this.gpuPrepared.spawn(this._worldPos),this.gpuHandle?.setTransform(this.object.matrixWorld),this.gpuHandle?.setTimescale(this._timescale);for(const t of this.inputRuntime.getDefinitions())this.gpuHandle?.setInput(t.id,this.inputRuntime.getValue(t.id))}return}this.system.emitters.every(t=>t.dead)&&this.restart()}pause(){this.paused=!0,this.gpuHandle?.pause()}previewAt(t,e,s,i,r=0){null!=this.system&&(this.prepareForPlayback(),x(r,()=>{this.restart(),this.paused=!0;const r=Math.max(0,t),n=Math.min(r,Math.max(0,e)),o=r-n,a=Number(n>0)+Number(o>0),l=Math.max(a,Math.min(this.maxPreviewSteps,Math.max(1,Math.ceil(r/this.previewStepSeconds)))),p=n<=0?0:o<=0?l:Math.min(l-1,Math.max(1,Math.round(l*n/r)));this.simulatePreview(n,p),o>0&&(this.applyClipEndBehavior(s,i),this.simulatePreview(o,l-p))}))}clearPreview(){this.restart(),this.pause(),this.object.visible=!1,null!=this.particleSystemContainer&&(this.particleSystemContainer.visible=!1)}stop(){this.finishRequested=!0,null==this.gpuPrepared?null!=this.system?.emitters&&this.system.emitters.forEach(t=>{this.stopEmitterEmission(t)}):this.gpuHandle?.stop()}applyClipEndBehavior(t,e){const s=t??"finish";if("finish"!==s){if(null!=this.gpuPrepared)return this.finishRequested=!0,void("kill"===s?this.gpuHandle?.kill():this.gpuHandle?.expire(Math.max(0,e??0)));if(null!=this.system&&null!=this.system.emitters)for(const t of this.system.emitters)this.applyEmitterClipEndBehavior(t,s,e)}else this.stop()}restart(){if(null==this.gpuPrepared){if(null!=this.system&&null!=this.system.emitters){this.finishRequested=!1,this.completionEmitted=!1;for(const t of this.system.emitters)this.restartEmitter(t)}}else if(this.finishRequested=!1,this.completionEmitted=!1,null!=this.gpuHandle){if(!this.gpuHandle.active&&!this.paused)return void this.play();this.object.getWorldPosition(this._worldPos),this.gpuHandle.setPosition(this._worldPos),this.gpuHandle.setTransform(this.object.matrixWorld),this.gpuHandle.restart()}}restartEmitter(t){const e=t.rate;e instanceof h?e.restart():e instanceof i&&(e.nextTime=0),null==t.clearParticlesRecursive?this.removeEmitterParticlesRecursive(t):t.clearParticlesRecursive()}removeEmitterParticlesRecursive(t){const e=t.childEmitters??[];for(const t of e)this.removeEmitterParticlesRecursive(t);t.removeAllParticles?.()}onUpdate(t){if(this.paused)null!=this.gpuHandle&&(this.object.getWorldPosition(this._worldPos),this.gpuHandle.setPosition(this._worldPos),this.gpuHandle.setTransform(this.object.matrixWorld));else{if(null!=this.gpuPrepared)return this.object.getWorldPosition(this._worldPos),this.gpuHandle?.setPosition(this._worldPos),this.gpuHandle?.setTransform(this.object.matrixWorld),void this.updateCompletionState();this.updateWorldSpaceEmitterTransforms(),this.updateSystem(t*this.timescale),this.updateCompletionState()}}prepareForPlayback(){null==this.gpuPrepared?(null!=this.particleSystemContainer&&null==this.particleSystemContainer.parent&&this.world.scene.add(this.particleSystemContainer),this.object.matrixAutoUpdate=!0,this.object.matrixWorldAutoUpdate=!0,null!=this.particleSystemContainer&&(this.particleSystemContainer.matrixAutoUpdate=!0,this.particleSystemContainer.matrixWorldAutoUpdate=!0),this.object.visible=!0,null!=this.particleSystemContainer&&(this.particleSystemContainer.visible=!0)):this.object.visible=!0}simulatePreview(t,e){if(t<=0)return;const s=t/e;for(let t=0;t<e;t++)this.updateWorldSpaceEmitterTransforms(),this.updateSystem(s)}updateWorldSpaceEmitterTransforms(){this.object.getWorldPosition(this._worldPos),this.object.getWorldQuaternion(this._worldRot),this.object.getWorldScale(this._worldScale),this._worldEul.setFromQuaternion(this._worldRot),this.system?.emitters.forEach(t=>{if("world"===t._space){t.setPosition(this._worldPos),t.setRotation(this._worldEul);(t._worldScale??(t._worldScale=new s.Vector3)).copy(this._worldScale)}})}getParticleCount(){return null!=this.gpuPrepared?0:this.system?.getCount()??0}getProfilingStats(){let t=0,e=0;return this.particleSystemContainer?.traverse(i=>{const r=i,n=r.geometry;if(null==n||!1===r.visible)return;const o=r instanceof s.InstancedMesh?r.count:1,a=n.index?.count??n.getAttribute("position")?.count??0;o<=0||a<=0||(t++,e+=Math.floor(a/3)*o)}),{particles:this.system?.getCount()??0,drawCalls:t,triangles:e,cpuTimeMs:this.simulationCpuMs}}onEndPlay(){this.stop(),null!=this.disposeSystem&&this.disposeSystem(),this.gpuHandle?.release(),this.gpuPrepared?.release(),this.gpuHandle=null,this.gpuPrepared=null,this.renderBodies=[],this.renderPreparation=void 0,this.onComplete.complete()}updateCompletionState(){if(null!=this.gpuPrepared){if(this.completionEmitted||!1!==this.gpuHandle?.active)return;return this.completionEmitted=!0,void this.onComplete.next()}if(this.completionEmitted||null==this.system||!this.onComplete.observed)return;const t=this.system.emitters.every(t=>t.dead),e=this.finishRequested&&0===this.system.getCount();(t||e)&&(this.completionEmitted=!0,this.onComplete.next())}updateSystem(t){if(null==this.system)return;if(!this.profilingEnabled)return void this.system.update(t);const e=performance.now();this.system.update(t),this.simulationCpuMs=performance.now()-e}applyEmitterClipEndBehavior(t,e,s,i=!1){this.stopEmitterEmission(t,i);const r=t.particles??[];if("kill"===e)t.removeAllParticles?.();else{const t=Math.max(0,s??0);for(const e of r)e.life=Math.min(e.life,e.age+t)}const n=t.childEmitters??[];for(const t of n)this.applyEmitterClipEndBehavior(t,e,s,!0)}stopEmitterEmission(t,e=!1){const s=t.rate;s instanceof i&&(s.nextTime=1/0),e&&t.stopEmit?.()}};t([v({parameters:[],returns:w}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],C.prototype,"play",null),t([v({parameters:[],returns:w}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],C.prototype,"pause",null),t([v({parameters:[],returns:w}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],C.prototype,"stop",null),t([v({parameters:[],returns:w}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],C.prototype,"restart",null),C=t([r({typeId:b})],C);export{C as VfxActor};/*
|
|
1
|
+
import{__decorate as t,__metadata as e}from"tslib";import*as s from"three";import{Rate as i}from"@hology/nebula";import{Actor as r,BaseActor as n}from"../../gameplay/actors/actor.js";import{inject as o}from"../../gameplay/inject.js";import{AssetLoader as a}from"../../gameplay/services/asset-loader.js";import{ViewController as l}from"../../gameplay/services/render.js";import{World as p}from"../../gameplay/services/world.js";import{DelayRate as h}from"./rates.js";import{materializeVfx as u}from"./vfx-materializer.js";import{PhysicsSystem as m}from"../../gameplay/services/physics/physics-system.js";import{ShaderProvider as d}from"../../gameplay/services/shader-provider.js";import{AssetResourceLoader as c}from"../../scene/asset-resource-loader.js";import{AssetsProvider as g}from"../../scene/assets-provider.js";import{VfxInputRuntime as f}from"./vfx-input-runtime.js";import{Subject as y}from"rxjs";import{EventGraphCallable as v}from"../../gameplay/event-graph/decorators.js";import{EVENT_GRAPH_VOID_TYPE as w}from"../../gameplay/event-graph/types.js";import{VFX_ACTOR_TYPE_ID as P}from"../../gameplay/event-graph/asset-actor-types.js";import{GpuVfxWorldService as b}from"./gpu-vfx-world-service.js";import{compileGpuSpriteEmitters as S,requiresCpuVfxBackend as j}from"./gpu-sprite-compiler.js";import{withSeededRandom as x}from"./seeded-random.js";import{RenderingView as E}from"../../rendering.js";import{prepareVfxRendering as R}from"./vfx-render-preparation.js";let C=class extends n{constructor(){super(...arguments),this.onComplete=new y,this.collisionEvent={emitterId:"",emitterName:"",position:new s.Vector3,normal:new s.Vector3},this._timescale=1,this.paused=!0,this.runtimeBackend="cpu",this.backendFallbackReasons=[],this.profilingEnabled=!1,this.simulationCpuMs=0,this.system=null,this.disposeSystem=null,this.renderBodies=[],this.renderingView=o(E),this.assetLoader=o(a),this.assetManagerService=o(c),this.assetService=o(g),this.world=o(p),this.view=o(l),this.gpuWorld=o(b),this.particleSystemContainer=null,this.gpuPrepared=null,this.gpuHandle=null,this.physics=o(m),this.shaderProvider=o(d),this.inputRuntime=new f,this.inputOverrides=new Map,this.finishRequested=!1,this.completionEmitted=!1,this.inputsMaterialized=!1,this._worldPos=new s.Vector3,this._worldRot=new s.Quaternion,this._worldEul=new s.Euler,this._worldScale=new s.Vector3,this.previewStepSeconds=1/60,this.maxPreviewSteps=240,this.max=0}get timescale(){return this._timescale}set timescale(t){this._timescale=Math.max(0,t),this.gpuHandle?.setTimescale(this._timescale)}async fromAsset(t,e,i,r){if("vfx"!==t.type)throw"Asset must be a VFX asset but is "+t.type;null!=this.system&&(this.system.destroy(),this.system.emitters.forEach(t=>t.reset())),this.gpuHandle?.release(),this.gpuPrepared?.release(),this.gpuHandle=null,this.gpuPrepared=null,this.runtimeBackend="cpu",this.renderBodies=[],this.renderPreparation=void 0,this.backendFallbackReasons=[],this.simulationCpuMs=0,this.sourceAsset=t,this.finishRequested=!1,this.completionEmitted=!1,this.inputsMaterialized=!1,this.disposeSystem&&this.disposeSystem(),null==this.object.parent&&this.world.scene.add(this.object),this.inputRuntime.setDefinitions(this.sourceAsset.vfx.inputs??[]);for(const t of this.inputRuntime.getDefinitions()){if("mesh"!==t.type||"string"!=typeof t.defaultValue||0===t.defaultValue.length)continue;const e=await this.assetService.getAsset(t.defaultValue);if(null==e)continue;const s=await this.assetManagerService.getMesh(e,{applyMaterials:!0,rescale:!0});this.inputRuntime.setInput(t.id,s.scene)}for(const[t,e]of this.inputOverrides)this.inputRuntime.setInput(t,e);null!=e&&this.setInputs(e);const n=i??this.view.vfxBackendPreference,o=j(this.sourceAsset);if("cpu"===n&&(this.backendFallbackReasons=["GPU VFX was not requested; the selected VFX backend preference is CPU."]),"cpu"!==n&&!o){const t=new Map;for(const e of this.inputRuntime.getDefinitions()){if("mesh"!==e.type)continue;const s=this.inputRuntime.getMesh(e.id);null!=s&&t.set(e.id,s)}if(this.gpuPrepared=await this.gpuWorld.prepare(this.sourceAsset,r,t),null!=this.gpuPrepared)return this.runtimeBackend="gpu",this.system=null,this.disposeSystem=null,this.particleSystemContainer=null,this.object.visible=!1,this.object.matrixAutoUpdate=!0,this.object.matrixWorldAutoUpdate=!0,void(this.inputsMaterialized=!0);if(this.backendFallbackReasons=this.gpuWorld.getFallbackReasons(this.sourceAsset,r),"gpu"===n){const t=S(this.sourceAsset,[new s.Vector3]).compatibility;this.backendFallbackReasons=t.issues.filter(t=>"warning"!==t.severity).map(t=>`${t.emitter}: ${t.message}`),0===this.backendFallbackReasons.length&&(this.backendFallbackReasons=["GPU template or material preparation failed; inspect earlier shader diagnostics."])}}const{system:a,dispose:l,container:p,renderBodies:h}=await u(this.sourceAsset,this.object,{getAsset:t=>this.assetLoader.getAsset(t),getMaterial:t=>this.assetLoader.getMaterialByAssetId(t),getTexture:t=>this.assetLoader.getTextureByAssetId(t),getMesh:async t=>(await this.assetManagerService.getMesh(await this.assetService.getAsset(t),{applyMaterials:!0,rescale:!0})).scene},this.view,this.physics,this.shaderProvider,this.assetService,this.assetManagerService,this.inputRuntime,(t,e,s,i,r)=>{if(null==this.onCollision)return;const n=this.collisionEvent;n.emitterId=t,n.emitterName=e,n.position.copy(s),n.normal.copy(i),r&&(n.position.applyMatrix4(this.object.matrixWorld),n.normal.transformDirection(this.object.matrixWorld)),this.onCollision(n)});this.system=a,this.renderBodies=h,this.disposeSystem=l,this.particleSystemContainer=p,this.inputsMaterialized=!0,this.object.visible=!1,this.object.matrixAutoUpdate=!1,this.object.matrixWorldAutoUpdate=!1,this.particleSystemContainer.matrixAutoUpdate=!1,this.particleSystemContainer.matrixWorldAutoUpdate=!1}prepareRendering(){return this.renderPreparation??(this.renderPreparation=this.prepareRenderResources())}async prepareRenderResources(){null==this.gpuPrepared&&0!==this.renderBodies.length&&await R(this.renderBodies,this.view,this.renderingView)}setInput(t,e){const i=this.inputRuntime.getDefinition(t);if(this.inputsMaterialized&&"mesh"===i?.type)return void console.warn(`VFX mesh input "${i.name}" is creation-time only; recreate the VFX to replace it.`);this.inputOverrides.set(t,function(t){if(t instanceof s.Vector3||t instanceof s.Color)return t.clone();if(Array.isArray(t))return[...t];return t}(e)),this.inputRuntime.setInput(t,e);const r=this.inputRuntime.getDefinition(t);null!=r&&this.gpuHandle?.setInput(r.id,this.inputRuntime.getValue(r.id))}setInputs(t){for(const[e,s]of Object.entries(t))this.setInput(e,s)}play(){if(this.prepareForPlayback(),this.paused=!1,null!=this.gpuPrepared){if(this.object.getWorldPosition(this._worldPos),null!=this.gpuHandle&&this.gpuHandle.active)this.gpuHandle.setPosition(this._worldPos),this.gpuHandle.setTransform(this.object.matrixWorld),this.gpuHandle.play();else{this.gpuHandle=this.gpuPrepared.spawn(this._worldPos),this.gpuHandle?.setTransform(this.object.matrixWorld),this.gpuHandle?.setTimescale(this._timescale);for(const t of this.inputRuntime.getDefinitions())this.gpuHandle?.setInput(t.id,this.inputRuntime.getValue(t.id))}return}this.system.emitters.every(t=>t.dead)&&this.restart()}pause(){this.paused=!0,this.gpuHandle?.pause()}previewAt(t,e,s,i,r=0){null!=this.system&&(this.prepareForPlayback(),x(r,()=>{this.restart(),this.paused=!0;const r=Math.max(0,t),n=Math.min(r,Math.max(0,e)),o=r-n,a=Number(n>0)+Number(o>0),l=Math.max(a,Math.min(this.maxPreviewSteps,Math.max(1,Math.ceil(r/this.previewStepSeconds)))),p=n<=0?0:o<=0?l:Math.min(l-1,Math.max(1,Math.round(l*n/r)));this.simulatePreview(n,p),o>0&&(this.applyClipEndBehavior(s,i),this.simulatePreview(o,l-p))}))}clearPreview(){this.restart(),this.pause(),this.object.visible=!1,null!=this.particleSystemContainer&&(this.particleSystemContainer.visible=!1)}stop(){this.finishRequested=!0,null==this.gpuPrepared?null!=this.system?.emitters&&this.system.emitters.forEach(t=>{this.stopEmitterEmission(t)}):this.gpuHandle?.stop()}applyClipEndBehavior(t,e){const s=t??"finish";if("finish"!==s){if(null!=this.gpuPrepared)return this.finishRequested=!0,void("kill"===s?this.gpuHandle?.kill():this.gpuHandle?.expire(Math.max(0,e??0)));if(null!=this.system&&null!=this.system.emitters)for(const t of this.system.emitters)this.applyEmitterClipEndBehavior(t,s,e)}else this.stop()}restart(){if(null==this.gpuPrepared){if(null!=this.system&&null!=this.system.emitters){this.finishRequested=!1,this.completionEmitted=!1;for(const t of this.system.emitters)this.restartEmitter(t)}}else if(this.finishRequested=!1,this.completionEmitted=!1,null!=this.gpuHandle){if(!this.gpuHandle.active&&!this.paused)return void this.play();this.object.getWorldPosition(this._worldPos),this.gpuHandle.setPosition(this._worldPos),this.gpuHandle.setTransform(this.object.matrixWorld),this.gpuHandle.restart()}}restartEmitter(t){const e=t.rate;e instanceof h?e.restart():e instanceof i&&(e.nextTime=0),null==t.clearParticlesRecursive?this.removeEmitterParticlesRecursive(t):t.clearParticlesRecursive()}removeEmitterParticlesRecursive(t){const e=t.childEmitters??[];for(const t of e)this.removeEmitterParticlesRecursive(t);t.removeAllParticles?.()}onUpdate(t){if(this.paused)null!=this.gpuHandle&&(this.object.getWorldPosition(this._worldPos),this.gpuHandle.setPosition(this._worldPos),this.gpuHandle.setTransform(this.object.matrixWorld));else{if(null!=this.gpuPrepared)return this.object.getWorldPosition(this._worldPos),this.gpuHandle?.setPosition(this._worldPos),this.gpuHandle?.setTransform(this.object.matrixWorld),void this.updateCompletionState();this.updateWorldSpaceEmitterTransforms(),this.updateSystem(t*this.timescale),this.updateCompletionState()}}prepareForPlayback(){null==this.gpuPrepared?(null!=this.particleSystemContainer&&null==this.particleSystemContainer.parent&&this.world.scene.add(this.particleSystemContainer),this.object.matrixAutoUpdate=!0,this.object.matrixWorldAutoUpdate=!0,null!=this.particleSystemContainer&&(this.particleSystemContainer.matrixAutoUpdate=!0,this.particleSystemContainer.matrixWorldAutoUpdate=!0),this.object.visible=!0,null!=this.particleSystemContainer&&(this.particleSystemContainer.visible=!0)):this.object.visible=!0}simulatePreview(t,e){if(t<=0)return;const s=t/e;for(let t=0;t<e;t++)this.updateWorldSpaceEmitterTransforms(),this.updateSystem(s)}updateWorldSpaceEmitterTransforms(){this.object.getWorldPosition(this._worldPos),this.object.getWorldQuaternion(this._worldRot),this.object.getWorldScale(this._worldScale),this._worldEul.setFromQuaternion(this._worldRot),this.system?.emitters.forEach(t=>{if("world"===t._space){t.setPosition(this._worldPos),t.setRotation(this._worldEul);(t._worldScale??(t._worldScale=new s.Vector3)).copy(this._worldScale)}})}getParticleCount(){return null!=this.gpuPrepared?0:this.system?.getCount()??0}getProfilingStats(){let t=0,e=0;return this.particleSystemContainer?.traverse(i=>{const r=i,n=r.geometry;if(null==n||!1===r.visible)return;const o=r instanceof s.InstancedMesh?r.count:1,a=n.index?.count??n.getAttribute("position")?.count??0;o<=0||a<=0||(t++,e+=Math.floor(a/3)*o)}),{particles:this.system?.getCount()??0,drawCalls:t,triangles:e,cpuTimeMs:this.simulationCpuMs}}onEndPlay(){this.stop(),null!=this.disposeSystem&&this.disposeSystem(),this.gpuHandle?.release(),this.gpuPrepared?.release(),this.gpuHandle=null,this.gpuPrepared=null,this.renderBodies=[],this.renderPreparation=void 0,this.onComplete.complete()}updateCompletionState(){if(null!=this.gpuPrepared){if(this.completionEmitted||!1!==this.gpuHandle?.active)return;return this.completionEmitted=!0,void this.onComplete.next()}if(this.completionEmitted||null==this.system||!this.onComplete.observed)return;const t=this.system.emitters.every(t=>t.dead),e=this.finishRequested&&0===this.system.getCount();(t||e)&&(this.completionEmitted=!0,this.onComplete.next())}updateSystem(t){if(null==this.system)return;if(!this.profilingEnabled)return void this.system.update(t);const e=performance.now();this.system.update(t),this.simulationCpuMs=performance.now()-e}applyEmitterClipEndBehavior(t,e,s,i=!1){this.stopEmitterEmission(t,i);const r=t.particles??[];if("kill"===e)t.removeAllParticles?.();else{const t=Math.max(0,s??0);for(const e of r)e.life=Math.min(e.life,e.age+t)}const n=t.childEmitters??[];for(const t of n)this.applyEmitterClipEndBehavior(t,e,s,!0)}stopEmitterEmission(t,e=!1){const s=t.rate;s instanceof i&&(s.nextTime=1/0),e&&t.stopEmit?.()}};t([v({parameters:[],returns:w}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],C.prototype,"play",null),t([v({parameters:[],returns:w}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],C.prototype,"pause",null),t([v({parameters:[],returns:w}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],C.prototype,"stop",null),t([v({parameters:[],returns:w}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],C.prototype,"restart",null),C=t([r({typeId:P})],C);export{C as VfxActor};/*
|
|
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 e,{Behaviour as t,Body as a,Emitter as r,Rate as s}from"@hology/nebula";import*as i from"three";import{Object3D as n}from"three";import{AttributeVec3Node as l,AttributeVec4Node as o,NodeShaderMaterial as c,RgbNode as p,UniformFloatNode as u,UniformVec3Node as d,Vec3ExpressionNode as h,attributeVec3 as m,attributeVec4 as f,attributes as y,clamp as w,cross as g,float as b,lambertMaterial as M,length as v,log as E,normalize as A,pow as x,rgb as S,rgba as I,smoothstep as P,standardMaterial as T,textureSampler2d as C,uniforms as R,varying as B,varyingAttributes as G,varyingVec3 as j,varyingVec4 as k,vec2 as D,vec3 as V,vec4 as F}from"three-shader-graph";import{prepareClassParameters as L,prepareShaderGraphParameters as O,prepareShapeParameters as W}from"../../scene/materializer.js";import{SerializedParamType as U}from"../../scene/model.js";import{ShapeLibrary as N}from"../../scene/objects/shapes.js";import{fragmentLinearEyeDepth as Q,linearEyeDepth as H}from"../../shader-nodes/depth.js";import{particleEnergyUniformName as q,particleTimeUniformName as z,particleUniforms as J,particleVelcoityUniformName as X}from"../../shader-nodes/particle.js";import{sampleFlipbook as _}from"../../shader-nodes/texture-sequence.js";import{DefaultInitializer as K,WorldSpaceEmitterTransformInitializer as Y}from"./initializsers.js";import{DelayRate as Z,OnceRate as $}from"./rates.js";import{StretchedSprite as ee}from"./stretched-sprite.js";import{ThreeBlendingMode as te}from"./vfx-asset.js";import{VfxBehaviourLibrary as ae,VfxInitializserLibrary as re}from"./vfx-defs.js";import{MultiRenderer as se}from"./vfx-renderers.js";import{WorldCollisionBehaviour as ie}from"./vfx-collision-behaviour.js";import{getSpritePosition as ne,SpriteNodeShader as le,SpriteNodeShaderMaterial as oe}from"../../shader/sprite-shader.js";import{DecalUnlitShader as ce}from"../../shader/builtin/decal-unlit-shader.js";import{DecalStandardShader as pe}from"../../shader/builtin/decal-standard-shader.js";import{createBoundBehaviour as ue,createBoundInitializer as de}from"./vfx-binding-runtime.js";import{buildShaderGraphMaterial as he}from"../../shader/graph/index.js";import{markMaterialUniformValuesNeedUpdate as me,setMaterialUniformValuesAutoUpdate as fe}from"../../rendering/node-material-webgpu-resolver.js";import{applyMeshWorldTransformToGeometry as ye}from"./vfx-mesh-transform.js";import{createGpuMeshAssetMaterial as we}from"./vfx-gpu-material.js";export async function materializeVfx(t,a,r,s,l,o,c,p,u,d){let h=a;for(;null!=h.parent;)h=h.parent;const m=new n;m.name="particle system local",a.add(m);const f=new n;f.name="particle system world";const y=new se(f,m,i,s),w=new e,g=[];return(await Promise.all(t.vfx.emitters.slice().sort((e,t)=>(t.output.renderOrder??0)-(e.output.renderOrder??0)).map(async e=>{const t=await be(e,r,c,p,w,l,o,u,a,d,g);return t.setParentRecursive(w),t.emit()}))).forEach(e=>w.addEmitter(e)),w.addRenderer(y).emit({onEnd:()=>{}}),{renderBodies:g,container:f,system:w,dispose:()=>{f.removeFromParent(),m.removeFromParent(),y.dispose()}}}function ge(e,t){let a;const r=function(e,t){const a=Math.max(0,e.rate.delay??0),r=e.rate.delayBinding;if("vfxInput"!==r?.type||null==t)return a;const s=t.getDefinition(r.inputId);return"number"!==s?.type?a:Math.max(0,t.getNumber(r.inputId,a))}(e,t);switch(e.rate.type){case"continuous":a=new Z(r,e.rate.count,e.rate.time,e.rate.duration);break;case"once":a=new $(r,e.rate.count);break;default:console.warn(`Failed to configure rate for emitter: ${JSON.stringify(e)}`),a=new s(0,1/0)}return a}async function be(e,t,r,s,p,x,S,T,D,V,L){const O=ge(e,T);let q;switch(e.output.type){case"decal":q=new a(await async function(e,t,a,r,s){if("shaderGraph"===ve(e)){const t=await createShaderGraphMaterial(e,"decal",a,r,s);if(null!=t){const a=new i.Mesh(new i.BoxGeometry(1,1,1),t);return Ae(a,e),a}}const n=!1!==e.unlit?new ce:new pe;n.color=new i.Color(e.color),n instanceof ce?n.intensity=e.intensity??1:n.emissiveIntensity=e.intensity??1;if(e.colorMap){const a=await t.getTexture(e.colorMap);n.colorMap=a}if(e.alphaMap){const a=await t.getTexture(e.alphaMap);n.alphaMap=a}const l=n.build();l.blending=te[e.blendingMode]??i.NormalBlending,l.transparent=!0,l.side=i.BackSide,l.depthTest=!1,null!=l&&!0===e.bloom&&(l.userData.hasBloom=!0);l.defines.IS_PARTICLE="";const o=new i.BoxGeometry(1,1,1),c=new i.Mesh(o,l);return Ae(c,e),c}(e.output,t,r,s,S));break;case"sprite":q=new a(await async function(e,t,a,r,s){let n=null;const l=ve(e);"shaderGraph"===l&&(n=await createShaderGraphMaterial(e,"sprite",a,r,s));null==n&&"shader"===l&&null!=e.shader&&(n=await createShaderMaterial(e,t,s));null==n&&(n=await async function(e,t){const a=null!=e.texture?await t.getTexture(e.texture):Me,r=C(a);let s=r.sample(G.uv);e.flipbook?.enabled&&(s=_(r,y.uv,e.flipbook.columns,e.flipbook.rows,J.time,e.flipbook.fps,e.flipbook.mode));const n=new u("rotation",0),l=new d("color").rgb,o=j(new h("instanceColor")).rgb,c=H.subtract(Q).divide(H);let p=J.opacity;switch(e.opacityChannel??"red"){case"none":break;case"red":p=p.multiply(s.r);break;case"alpha":p=p.multiply(s.a)}if("number"==typeof e.softness&&e.softness>0){const t=w(c,0,1e3);p=p.multiply(P(0,.2*e.softness,t))}const m=ne(n,void 0,void 0,!0),f=I(l.multiply(o).multiply(s.rgb).multiplyScalar(e.intensity??1),p);var g=new oe({color:f,emissive:f.rgb.multiplyScalar(p),transparent:!0,position:m,alphaTest:1e-4,uniforms:{color:{value:new i.Color(e.color)}}});g.alphaHash=!0,null!=g&&!0===e.bloom&&(g.userData.hasBloom=!0);return g.blending=te[e.blendingMode]??i.NormalBlending,g}(e,t));n.depthWrite=!1,Ee(n,e);const o=new i.Mesh(new i.PlaneGeometry(1,1),n);return o.name="sprite",Ae(o,e),o}(e.output,t,r,s,S));break;case"stretchedSprite":q=new a(await async function(e,t){"shaderGraph"===ve(e)&&console.warn("Shader graph materials are not supported for stretched sprite VFX outputs yet. Falling back to the default stretched sprite material.");const a=null!=e.texture?await t.getTexture(e.texture):Me,r=C(a).sample(G.uv),s=k(new o("particleColor")),n=s.rgb.multiply(r.rgb);let l=s.w;switch(e.opacityChannel??"red"){case"none":break;case"red":l=l.multiply(r.r);break;case"alpha":l=l.multiply(r.a)}if("number"==typeof e.softness&&e.softness>0){const t=H.subtract(Q).divide(E(H)),a=w(t,0,1e3);l=l.multiply(P(0,.2*e.softness,a))}const c=m("offset"),p=f("velocity"),u=m("size"),d=p.w,h=u.x.add(u.y).multiply(.5),M=R.modelViewMatrix.multiplyVec(F(c,1)),x=R.normalMatrix.multiplyVec(p.xyz),S=A(g(M.xyz,x)).multiplyScalar(y.position.y.multiply(h)),T=x.multiplyScalar(y.position.x.add(.5).multiply(b(1).add(d.divide(v(x)))).multiply(h)),B=R.projectionMatrix.multiplyVec(F(M.xyz.add(S).subtract(T),M.w));var j=new oe({color:I(n.multiplyScalar(e.intensity??1),l),alphaTest:.1,transparent:!0,position:B,uniforms:{color:{value:new i.Color(e.color)}}});null!=j&&!0===e.bloom&&(j.userData.hasBloom=!0);j.blending=te[e.blendingMode]??i.NormalBlending;const D=new ee(new i.PlaneGeometry(1,1),j);return D.scaleFactor=e.scale,Ae(D,e),D}(e.output,t));break;case"shape":q=new a(await async function(e,t,a,r,s){if(null==e.shape)return console.log("Shape is null"),new n;const o=N[e.shape];if(null==o)return console.error(`No shape with type ${e.shape}`),new n;const p=W(e.params??{}),u=o.geometry(p);let d=null;const m=ve(e);"shaderGraph"===m&&(d=await createShaderGraphMaterial(e,"surface",a,r,s));null==d&&"shader"===m&&null!=e.shader&&(d=await createShaderMaterial(e,t,s));null==d&&(d="material"===m&&null!=e.material?xe(await t.getMaterial(e.material)):function(){const e=B(new h("instanceColor")).rgb,t=B(new l("particleData")).x;return new c({color:M({color:e}).rgb.rgba(t),opacity:t,transparent:!0})}());Ee(d,e);const f=new i.Mesh(u,d);return Ae(f,e),f}(e.output,t,r,s,S));break;case"mesh":q=new a(await async function(e,t,a,r,s){if(null==e.assetId)return console.warn("Can't use mesh as particle without asset id"),new n;const l=await t.getMesh(e.assetId),o=await t.getAsset(e.assetId),c=ve(e);if("shaderGraph"===c||"shader"===c&&null!=e.shader||"material"===c&&null!=e.material){let n;"shaderGraph"===c?n=await createShaderGraphMaterial(e,"surface",a,r,s):"shader"===c&&null!=e.shader?n=await createShaderMaterial(e,t,s):"material"===c&&null!=e.material&&(n=xe(await t.getMaterial(e.material))),n?.transparent&&(n.depthWrite=!1),null!=n&&Ee(n,e),l.traverse(e=>{e instanceof i.Mesh&&null!=n&&(e.material=n)})}else{const e=[];if(null!=o.materialAssignments)for(const a of o.materialAssignments)null!=a.materialId&&l.traverse(r=>{r instanceof i.Mesh&&r.material instanceof i.Material&&r.material.color instanceof i.Color&&(r.material.name!=a.name&&null!=a.name||"#"+r.material.color.getHexString()!==a.color||e.push(t.getMaterial(a.materialId).then(e=>r.material=e)))});await Promise.all(e)}const p=[];if(l.traverse(e=>{e instanceof i.Mesh&&p.push(e)}),Ae(l,e),1===p.length){const t=p[0];return!0!==e.useLegacyTransform&&function(e){e.geometry=e.geometry.clone(),ye(e,e.geometry),e.position.set(0,0,0),e.quaternion.identity(),e.scale.set(1,1,1),e.updateMatrix(),e.matrixWorld.identity()}(t),t.removeFromParent(),t}return l}(e.output,t,r,s,S));break;case"trail":{const n=e.output,l="shaderGraph"===ve(e.output)?await createShaderGraphMaterial(e.output,"trail",r,s,S,{trailBillboard:e.output.billboard??!1}):null;q=new a({type:"trail",taper:e.output.taper,headGeometry:null,material:l instanceof i.ShaderMaterial?l:void 0,dragTexture:!1,texture:null!=e.output.texture?await t.getTexture(e.output.texture).then(e=>{if("repeat"!==n.uvMode)return e;const t=e.clone();return t.wrapS=i.RepeatWrapping,t.needsUpdate=!0,t}):null,opacityChannel:e.output.opacityChannel,color:e.output.color,colorEnd:e.output.colorEnd,intensity:e.output.intensity??1,intensityEnd:e.output.intensityEnd??1,length:e.output.length,opacityStart:e.output.opacityStart,opacityEnd:e.output.opacityEnd,bloom:e.output.bloom,scrollSpeed:e.output.scrollSpeed,uvMode:e.output.uvMode,unitsPerRepeat:e.output.unitsPerRepeat,width:e.output.width,billboard:e.output.billboard??!1,renderOrder:e.output.renderOrder});break}default:console.error("Failed to create particly system body: "+JSON.stringify(e))}const z=new Ie(D);null!=q&&L?.push(q.body.getValue()),z.parent=p,z.setRate(O),z._space=e.output.space;const X=await Promise.all(e.initializers.filter(e=>!1!==e.enabled).filter(e=>null!=re[e.type]).map(async e=>{const t=re[e.type],a=await W(e.params??{},r,s);return de(t,e.params??{},a,T)}));"world"===e.output.space&&X.push(new Y("shape"===e.output.type||"mesh"===e.output.type)),X.push(q,new K),z.addInitializers(X);const Z=await Promise.all(e.behaviours.filter(e=>!1!==e.enabled).filter(e=>null!=ae[e.type]).map(async e=>{const t=ae[e.type];for(const[a,r]of Object.entries(e.params))t.parameters&&null!=t.parameters[a]&&"curve"===t.parameters[a].type&&r.type!==U.Curve&&(r.type=U.Curve);const a=await W(e.params??{},r,s);return ue(t,e.params??{},a,T)}));Z.sort((e,t)=>Number(t instanceof ie)-Number(e instanceof ie)),Z.push(new Se);for(const t of Z)t instanceof ie&&(t.physics=x,t.emitEvents&&null!=V&&(t.onCollision=(t,a)=>V(e.id,e.name,t,a,"local"===e.output.space)));z.addBehaviours(Z);for(const a of e.children??[]){const e=await be(a,t,r,s,p,x,S,T,D,V,L),i=new EmitterPool(()=>{const t=e.clone();return t.onExpired=()=>{const e=z.childEmitters.findIndex(e=>e.id===t.id);-1!=e&&z.childEmitters.splice(e,1),null!=t.parentParticle&&(l.delete(t.parentParticle.id),t.parentParticle=null),i.release(t)},t}),n=z.eventDispatcher,l=new Map;z.bindEmitterEvent=!0,n.addEventListener("PARTICLE_DEAD",e=>{const t=l.get(e.id);null!=t&&(z.detachChildEmitterFromParticle(t,e),l.delete(e.id))});let o="PARTICLE_CREATED";if("spawnEvent"in a)switch(a.spawnEvent){case"collision":o="PARTICLE_COLLISION";break;case"start":o="PARTICLE_CREATED"}n.addEventListener(o,e=>{const t=i.get();t.age=0,t.totalEmitTimes=-1,t.particles.length=0,t.currentEmitTime=0,t.cID=0,t.eventDispatcher.removeAllEventListeners(),z.childEmitters.push(t),l.set(e.id,t),t.parentParticle=e,t.system=z.system,t.emit()})}return z}const Me=(new i.TextureLoader).load("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJkSURBVHjaxJeJbusgEEW94S1L//83X18M2MSuLd2pbqc4wZGqRLrKBsyZhQHny7Jk73xVL8xpVhWrcmiB5lX+6GJ5YgQ2owbAm8oIwH1VgKZUmGcRqKGGPgtEQQAzGR8hQ59fAmhJHSAagigJ4E7GPWRXOYC6owAd1JM6wDQPADyMWUqZRMqmAojHp1Vn6EQQEgUNMJLnUjMyJsM49wygBkAPw9dVFwXRkncCIIW3GRgoTQUZn6HxCMAFEFd8TwEQ78X4rHbILoAUmeT+RFG4UhQ6MiIAE4W/UsYFjuVjAIa2nIY4q1R0GFtQWG3E84lqw2GO2QOoCKBVu0BAPgDSU0eUDjjQenNkV/AW/pWChhpMTelo1a64AOKM30vk18GzTHXCNtI/Knz3DFBgsUqBGIjTInXRY1yA9xkVoqW5tVq3pDR9A0hfF5BSARmVnh7RMDCaIdcNgbPBkgzn1Bu+SfIEFSpSBmkxyrMicb0fAEuCZrWnN89veA/4XcakrPcjBWzkTuLjlbfTQPOlBhz+HwkqqPXmPQDdrQItxE1moGof1S74j/8txk8EHhTQrAE8qlwfqS5yukm1x/rAJ9Jiaa6nyATqD78aUVBhFo8b1V4DdTXdCW+IxA1zB4JhiOhZMEWO1HqnvdoHZ4FAMIhV9REF8FiUm0jsYPEJx/Fm/N8OhH90HI9YRHesWbXXZwAShU8qThe7H8YAuJmw5yOd989uRINKRTJAhoF8jbqrHKfeCYdIISZfSq26bk/K+yO3YvfKrVgiwQBHnwt8ynPB25+M8hceTt/ybPhnryJ78+tLgAEAuCFyiQgQB30AAAAASUVORK5CYII=");export async function createShaderMaterial(e,t,a){const r=a.get(e.shader);if(null==r)return console.error("No shader exists with name "+e.shader),new i.Material;const s=new r.type;"sprite"===e.type&&s instanceof le&&(s.instanced=!0);const n=await L(e.shaderParams??{},r.type,t,{getTexture:e=>t.getTexture(e.id),getMaterial:e=>t.getMaterial(e.id),getMesh:e=>t.getMesh(e.id)},void 0,void 0,void 0,void 0,void 0,s);return Object.assign(s,n),s.build()}export async function createShaderGraphMaterial(e,t,a,r,s,i={}){const n=e.shaderGraph;if("asset"!==n?.source||null==n.assetId)return console.warn(`Missing shader graph asset reference for ${e.type} VFX output`),null;const l=await a.getAsset(n.assetId),o=l?.shaderGraph;if(null==o)return console.warn(`Missing shader graph asset "${n.assetId}" for ${e.type} VFX output`),null;if(o.target!==t)return console.warn(`Shader graph "${l.name}" targets "${o.target}" but ${e.type} VFX output expects "${t}"`),null;try{const t=await O(e.shaderGraphParams??{},o,a,r,void 0,s.shaders),l=he(o,{parameters:t,trailBillboard:i.trailBillboard});return l.userData.customShaderName=`shaderGraph:${n.assetId}`,Ee(l,e),l}catch(e){return console.log("Shader graph VFX runtime error: "+e,e),null}}export function createGpuSpriteShaderGraphResolver(e,t,a,r){return async s=>{const n=ve(s);let l=null;if("shaderGraph"===n)l=await createShaderGraphMaterial(s,"sprite"===s.type?"sprite":"decal"===s.type?"decal":"trail"===s.type?"trail":"surface",e,t,a,"trail"===s.type?{trailBillboard:s.billboard??!1}:void 0);else if("shader"===n&&"decal"!==s.type&&null!=r)l=await createShaderMaterial(s,r,a);else if("material"!==n||"mesh"!==s.type&&"shape"!==s.type||null==s.material||null==r){if("default"===n&&"mesh"===s.type&&null!=s.assetId&&null!=r){const e=await r.getAsset(s.assetId),t=e.materialAssignments?.find(e=>null!=e.materialId);null!=t&&(l=await we(t.materialId,r))}else if("default"===n&&"decal"===s.type&&null!=r){const e=!1!==s.unlit?new ce:new pe;e.color=new i.Color(s.color),e instanceof ce?e.intensity=s.intensity??1:e.emissiveIntensity=s.intensity??1,s.colorMap&&(e.colorMap=await r.getTexture(s.colorMap)),s.alphaMap&&(e.alphaMap=await r.getTexture(s.alphaMap)),l=e.build(),l.transparent=!0}}else l="mesh"===s.type?await we(s.material,r):xe(await r.getMaterial(s.material));return null==l?null:("mesh"!==s.type&&"shape"!==s.type||!l.transparent||(l.depthWrite=!1),l instanceof c?l:(console.warn(`GPU ${s.type} materials must compile to NodeShaderMaterial; using CPU fallback`),null))}}function ve(e){return null!=e.materialSource?("material"!==e.materialSource||"mesh"!==e.type&&"shape"!==e.type||null!=e.material)&&("shaderGraph"!==e.materialSource||"asset"===e.shaderGraph?.source&&null!=e.shaderGraph.assetId)&&("shader"!==e.materialSource||"shader"in e&&null!=e.shader)?e.materialSource:"default":"asset"===e.shaderGraph?.source?"shaderGraph":"shader"in e&&null!=e.shader?"shader":"mesh"!==e.type&&"shape"!==e.type||null==e.material?"default":"material"}function Ee(e,t){null!=e&&(fe(e,!1),"bloom"in t&&!0===t.bloom&&(e.userData.hasBloom=!0),"sprite"===t.type?(e.blending=te[t.blendingMode]??i.NormalBlending,!0===t.lockY&&e instanceof i.ShaderMaterial&&(e.defines??(e.defines={}),e.defines.LOCK_Y_AXIS="")):"decal"===t.type?(e.blending=te[t.blendingMode]??i.NormalBlending,e.transparent=!0,e.side=i.BackSide,e.depthTest=!1,e instanceof i.ShaderMaterial&&(e.defines??(e.defines={}),e.defines.IS_PARTICLE="")):"shape"!==t.type&&"mesh"!==t.type||e instanceof i.ShaderMaterial&&(e.defines??(e.defines={}),e.defines.IS_PARTICLE=""))}function Ae(e,t){const a=t.renderOrder;null!=a&&e.traverse(e=>e.renderOrder=a)}function xe(e){const t=B(new h("instanceColor")).rgb;let a,r=B(new l("particleData")).x;if(e instanceof i.MeshStandardMaterial||e instanceof i.MeshLambertMaterial||e instanceof i.MeshBasicMaterial){let s=t.multiply(S(e.color));null!=e.map&&(s=s.multiply(C(e.map).sample(G.uv).rgb)),null!=e.alphaMap&&(r=r.multiply(C(e.alphaMap).sample(G.uv).r)),e instanceof i.MeshStandardMaterial?a=new c({color:T({color:s,emissive:S(e.emissive),emissiveIntensity:b(e.emissiveIntensity),roughness:e.roughness,metalness:e.metalness}).rgb.rgba(r),emissive:S(e.emissive).multiplyScalar(e.emissiveIntensity),transparent:e.transparent,opacity:r,alphaTest:e.alphaTest}):e instanceof i.MeshLambertMaterial?a=new c({color:M({color:s}).rgb.rgba(r),emissive:S(e.emissive).multiplyScalar(e.emissiveIntensity),transparent:e.transparent,opacity:r,alphaTest:e.alphaTest}):e instanceof i.MeshBasicMaterial&&(a=new c({color:s.rgb.rgba(r),emissive:s.rgb,transparent:e.transparent,opacity:r,alphaTest:e.alphaTest}))}else e instanceof c&&(a=e.clone(),a.defines.IS_PARTICLE="");return null!=a&&(!0===e.userData?.hasBloom&&null!=a&&(a.userData.hasBloom=!0),a.side=e.side,a.transparent=e.transparent),a??e}class Se extends t{initialize(e){e.body instanceof i.Object3D&&e.body.traverse(e=>{if(e instanceof i.Mesh){const t=e.material;t instanceof c&&(null!=t.uniforms[q]||null!=t.uniforms[z]||t.uniforms[X])}})}mutate(e,t,a){this.energize(e,t),e.target instanceof n&&e.target.traverse(t=>{if(t instanceof i.Mesh){const a=t.material;if(a instanceof c){let t=!1;null!=a.uniforms[q]&&(a.uniforms[q].value=this.energy,t=!0),null!=a.uniforms[z]&&(a.uniforms[z].value=e.age,t=!0),null!=a.uniforms[X]&&(a.uniforms[X].value=e.velocity,t=!0),t&&me(a)}}})}}class Ie extends r{constructor(e){super(),this.transformRoot=e,this.childEmitters=[],this.bindEmitterEvent=!1,this.onExpired=()=>{}}clearParticlesRecursive(){let e=this.childEmitters.length;for(;e--;){const t=this.childEmitters[e];t.stopEmit(),t.clearParticlesRecursive(),t.onExpired()}this.childEmitters.length=0;let t=this.particles.length;for(;t--;){const e=this.particles[t];this.system?.dispatch("PARTICLE_DEAD",e),this.bindEmitterEvent&&this.dispatch("PARTICLE_DEAD",e),null!=this.system?this.system.pool.expire(e.reset()):e.reset()}this.particles.length=0}detachChildEmitterFromParticle(e,t){this.copyPositionToChildSpace(e,t.position),e.parentParticle=null,e.stopEmit()}update(e){if(!this.isEmitting&&0===this.particles.length&&0===this.childEmitters.length)return;this.age+=e,(this.dead||this.age>=this.life)&&this.destroy(),this.isEmitting&&this.generate(e),this.integrate(e);let t=this.particles.length;for(;t--;){const e=this.particles[t];e.dead&&(this.system&&this.system.dispatch("PARTICLE_DEAD",e),this.bindEmitterEvent&&this.dispatch("PARTICLE_DEAD",e),this.system.pool.expire(e.reset()),this.particles.splice(t,1))}this.updateEmitterBehaviours(e),this.updateChildren(e),this.isEmitting||0!==this.particles.length||0!==this.childEmitters.length||this.onExpired()}updateChildren(e){for(const t of this.childEmitters)null!=t.parentParticle&&this.copyPositionToChildSpace(t,t.parentParticle.position),t.update(e)}copyPositionToChildSpace(e,t){const a=this._space??"world",r=e._space??"world";a!==r&&null!=this.transformRoot?(Pe.copy(t),"local"===a&&"world"===r?Pe.applyMatrix4(this.transformRoot.matrixWorld):"world"===a&&"local"===r&&(Te.copy(this.transformRoot.matrixWorld).invert(),Pe.applyMatrix4(Te)),e.position.copy(Pe)):e.position.copy(t)}clone(){const e=new Ie(this.transformRoot);return e.setRate(this.rate.clone()),e.behaviours=this.behaviours,e.initializers=this.initializers,e._space=this._space,e.body=this.body,e.parent=this.parent,e.system=this.system,e}setParentRecursive(e){this.system=e,this.childEmitters.forEach(t=>t.setParentRecursive(e))}}const Pe=new i.Vector3,Te=new i.Matrix4;export class EmitterPool{constructor(e){this.creator=e,this.instances=[]}get(){0==this.instances.length&&this.instances.push(this.creator());return this.instances.pop()}release(e){this.instances.push(e)}dispose(){this.instances.length=0}}/*
|
|
1
|
+
import e,{Behaviour as t,Body as a,Emitter as r,Rate as s}from"@hology/nebula";import*as i from"three";import{Object3D as n}from"three";import{AttributeVec3Node as o,AttributeVec4Node as l,NodeShaderMaterial as p,RgbNode as c,UniformFloatNode as u,UniformVec3Node as d,Vec3ExpressionNode as h,attributeVec3 as m,attributeVec4 as f,attributes as y,clamp as w,cross as g,float as b,lambertMaterial as v,length as M,log as E,normalize as A,pow as x,rgb as S,rgba as I,smoothstep as P,standardMaterial as T,textureSampler2d as C,uniforms as R,varying as G,varyingAttributes as B,varyingVec3 as k,varyingVec4 as j,vec2 as D,vec3 as F,vec4 as V}from"three-shader-graph";import{prepareClassParameters as L,prepareShaderGraphParameters as O,prepareShapeParameters as W}from"../../scene/materializer.js";import{SerializedParamType as U}from"../../scene/model.js";import{ShapeLibrary as N}from"../../scene/objects/shapes.js";import{fragmentLinearEyeDepth as Q,linearEyeDepth as z}from"../../shader-nodes/depth.js";import{particleEnergyUniformName as H,particleTimeUniformName as q,particleUniforms as J,particleVelcoityUniformName as X}from"../../shader-nodes/particle.js";import{sampleFlipbook as _}from"../../shader-nodes/texture-sequence.js";import{DefaultInitializer as K,WorldSpaceEmitterTransformInitializer as Y}from"./initializsers.js";import{DelayRate as Z,OnceRate as $}from"./rates.js";import{StretchedSprite as ee}from"./stretched-sprite.js";import{ThreeBlendingMode as te}from"./vfx-asset.js";import{VfxBehaviourLibrary as ae,VfxInitializserLibrary as re}from"./vfx-defs.js";import{MultiRenderer as se}from"./vfx-renderers.js";import{WorldCollisionBehaviour as ie}from"./vfx-collision-behaviour.js";import{getSpritePosition as ne,SpriteNodeShader as oe,SpriteNodeShaderMaterial as le}from"../../shader/sprite-shader.js";import{DecalUnlitShader as pe}from"../../shader/builtin/decal-unlit-shader.js";import{DecalStandardShader as ce}from"../../shader/builtin/decal-standard-shader.js";import{createBoundBehaviour as ue,createBoundInitializer as de}from"./vfx-binding-runtime.js";import{buildShaderGraphMaterial as he}from"../../shader/graph/index.js";import{markMaterialUniformValuesNeedUpdate as me,setMaterialUniformValuesAutoUpdate as fe}from"../../rendering/node-material-webgpu-resolver.js";import{applyMeshWorldTransformToGeometry as ye}from"./vfx-mesh-transform.js";import{createGpuMeshAssetMaterial as we}from"./vfx-gpu-material.js";export async function materializeVfx(t,a,r,s,o,l,p,c,u,d){let h=a;for(;null!=h.parent;)h=h.parent;const m=new n;m.name="particle system local",a.add(m);const f=new n;f.name="particle system world";const y=new se(f,m,i,s),w=new e,g=[];return(await Promise.all(t.vfx.emitters.slice().sort((e,t)=>(t.output.renderOrder??0)-(e.output.renderOrder??0)).map(async e=>{const t=await be(e,r,p,c,w,o,l,u,a,d,g);return t.setParentRecursive(w),t.emit()}))).forEach(e=>w.addEmitter(e)),w.addRenderer(y).emit({onEnd:()=>{}}),{renderBodies:g,container:f,system:w,dispose:()=>{f.removeFromParent(),m.removeFromParent(),y.dispose()}}}function ge(e,t){let a;const r=function(e,t){const a=Math.max(0,e.rate.delay??0),r=e.rate.delayBinding;if("vfxInput"!==r?.type||null==t)return a;const s=t.getDefinition(r.inputId);return"number"!==s?.type?a:Math.max(0,t.getNumber(r.inputId,a))}(e,t);switch(e.rate.type){case"continuous":a=new Z(r,e.rate.count,e.rate.time,e.rate.duration);break;case"once":a=new $(r,e.rate.count);break;default:console.warn(`Failed to configure rate for emitter: ${JSON.stringify(e)}`),a=new s(0,1/0)}return a}async function be(e,t,r,s,c,x,S,T,D,F,L){const O=ge(e,T);let H;switch(e.output.type){case"decal":H=new a(await async function(e,t,a,r,s){if("shaderGraph"===Me(e)){const t=await createShaderGraphMaterial(e,"decal",a,r,s);if(null!=t){const a=new i.Mesh(new i.BoxGeometry(1,1,1),t);return Ae(a,e),a}}const n=!1!==e.unlit?new pe:new ce;n.color=new i.Color(e.color),n instanceof pe?n.intensity=e.intensity??1:n.emissiveIntensity=e.intensity??1;if(e.colorMap){const a=await t.getTexture(e.colorMap);n.colorMap=a}if(e.alphaMap){const a=await t.getTexture(e.alphaMap);n.alphaMap=a}const o=n.build();o.blending=te[e.blendingMode]??i.NormalBlending,o.transparent=!0,o.side=i.BackSide,o.depthTest=!1,null!=o&&!0===e.bloom&&(o.userData.hasBloom=!0);o.defines.IS_PARTICLE="";const l=new i.BoxGeometry(1,1,1),p=new i.Mesh(l,o);return Ae(p,e),p}(e.output,t,r,s,S));break;case"sprite":H=new a(await async function(e,t,a,r,s){let n=null;const o=Me(e);"shaderGraph"===o&&(n=await createShaderGraphMaterial(e,"sprite",a,r,s));null==n&&"shader"===o&&null!=e.shader&&(n=await createShaderMaterial(e,t,s));null==n&&(n=await async function(e,t){const a=null!=e.texture?await t.getTexture(e.texture):ve,r=C(a);let s=r.sample(B.uv);e.flipbook?.enabled&&(s=_(r,y.uv,new u("vfxFlipbookColumns",e.flipbook.columns,void 0,!1),new u("vfxFlipbookRows",e.flipbook.rows,void 0,!1),J.time,new u("vfxFlipbookFps",e.flipbook.fps,void 0,!1),e.flipbook.mode));const n=new u("rotation",0),o=new d("color").rgb,l=k(new h("instanceColor")).rgb,p=z.subtract(Q).divide(z);let c=J.opacity;switch(e.opacityChannel??"red"){case"none":break;case"red":c=c.multiply(s.r);break;case"alpha":c=c.multiply(s.a)}if("number"==typeof e.softness&&e.softness>0){const t=w(p,0,1e3);c=c.multiply(P(b(0),new u("vfxSoftness",.2*e.softness,void 0,!1),t))}const m=ne(n,void 0,void 0,!0),f=I(o.multiply(l).multiply(s.rgb).multiplyScalar(new u("vfxIntensity",e.intensity??1,void 0,!1)),c);var g=new le({color:f,emissive:f.rgb.multiplyScalar(c),transparent:!0,position:m,alphaTest:1e-4,uniforms:{color:{value:new i.Color(e.color)}}});g.alphaHash=!0,null!=g&&!0===e.bloom&&(g.userData.hasBloom=!0);return g.blending=te[e.blendingMode]??i.NormalBlending,g}(e,t));n.depthWrite=!1,Ee(n,e);const l=new i.Mesh(new i.PlaneGeometry(1,1),n);return l.name="sprite",Ae(l,e),l}(e.output,t,r,s,S));break;case"stretchedSprite":H=new a(await async function(e,t){"shaderGraph"===Me(e)&&console.warn("Shader graph materials are not supported for stretched sprite VFX outputs yet. Falling back to the default stretched sprite material.");const a=null!=e.texture?await t.getTexture(e.texture):ve,r=C(a).sample(B.uv),s=j(new l("particleColor")),n=s.rgb.multiply(r.rgb);let o=s.w;switch(e.opacityChannel??"red"){case"none":break;case"red":o=o.multiply(r.r);break;case"alpha":o=o.multiply(r.a)}if("number"==typeof e.softness&&e.softness>0){const t=z.subtract(Q).divide(E(z)),a=w(t,0,1e3);o=o.multiply(P(b(0),new u("vfxSoftness",.2*e.softness,void 0,!1),a))}const p=m("offset"),c=f("velocity"),d=m("size"),h=c.w,v=d.x.add(d.y).multiply(.5),x=R.modelViewMatrix.multiplyVec(V(p,1)),S=R.normalMatrix.multiplyVec(c.xyz),T=A(g(x.xyz,S)).multiplyScalar(y.position.y.multiply(v)),G=S.multiplyScalar(y.position.x.add(.5).multiply(b(1).add(h.divide(M(S)))).multiply(v)),k=R.projectionMatrix.multiplyVec(V(x.xyz.add(T).subtract(G),x.w));var D=new le({color:I(n.multiplyScalar(new u("vfxIntensity",e.intensity??1,void 0,!1)),o),alphaTest:.1,transparent:!0,position:k,uniforms:{color:{value:new i.Color(e.color)}}});null!=D&&!0===e.bloom&&(D.userData.hasBloom=!0);D.blending=te[e.blendingMode]??i.NormalBlending;const F=new ee(new i.PlaneGeometry(1,1),D);return F.scaleFactor=e.scale,Ae(F,e),F}(e.output,t));break;case"shape":H=new a(await async function(e,t,a,r,s){if(null==e.shape)return console.log("Shape is null"),new n;const l=N[e.shape];if(null==l)return console.error(`No shape with type ${e.shape}`),new n;const c=W(e.params??{}),u=l.geometry(c);let d=null;const m=Me(e);"shaderGraph"===m&&(d=await createShaderGraphMaterial(e,"surface",a,r,s));null==d&&"shader"===m&&null!=e.shader&&(d=await createShaderMaterial(e,t,s));null==d&&(d="material"===m&&null!=e.material?xe(await t.getMaterial(e.material)):function(){const e=G(new h("instanceColor")).rgb,t=G(new o("particleData")).x;return new p({color:v({color:e}).rgb.rgba(t),opacity:t,transparent:!0})}());Ee(d,e);const f=new i.Mesh(u,d);return Ae(f,e),f}(e.output,t,r,s,S));break;case"mesh":H=new a(await async function(e,t,a,r,s){if(null==e.assetId)return console.warn("Can't use mesh as particle without asset id"),new n;const o=await t.getMesh(e.assetId),l=await t.getAsset(e.assetId),p=Me(e);if("shaderGraph"===p||"shader"===p&&null!=e.shader||"material"===p&&null!=e.material){let n;"shaderGraph"===p?n=await createShaderGraphMaterial(e,"surface",a,r,s):"shader"===p&&null!=e.shader?n=await createShaderMaterial(e,t,s):"material"===p&&null!=e.material&&(n=xe(await t.getMaterial(e.material))),n?.transparent&&(n.depthWrite=!1),null!=n&&Ee(n,e),o.traverse(e=>{e instanceof i.Mesh&&null!=n&&(e.material=n)})}else{const e=[];if(null!=l.materialAssignments)for(const a of l.materialAssignments)null!=a.materialId&&o.traverse(r=>{r instanceof i.Mesh&&r.material instanceof i.Material&&r.material.color instanceof i.Color&&(r.material.name!=a.name&&null!=a.name||"#"+r.material.color.getHexString()!==a.color||e.push(t.getMaterial(a.materialId).then(e=>r.material=e)))});await Promise.all(e)}const c=[];if(o.traverse(e=>{e instanceof i.Mesh&&c.push(e)}),Ae(o,e),1===c.length){const t=c[0];return!0!==e.useLegacyTransform&&function(e){e.geometry=e.geometry.clone(),ye(e,e.geometry),e.position.set(0,0,0),e.quaternion.identity(),e.scale.set(1,1,1),e.updateMatrix(),e.matrixWorld.identity()}(t),t.removeFromParent(),t}return o}(e.output,t,r,s,S));break;case"trail":{const n=e.output,o="shaderGraph"===Me(e.output)?await createShaderGraphMaterial(e.output,"trail",r,s,S,{trailBillboard:e.output.billboard??!1}):null;H=new a({type:"trail",taper:e.output.taper,headGeometry:null,material:o instanceof i.ShaderMaterial?o:void 0,dragTexture:!1,texture:null!=e.output.texture?await t.getTexture(e.output.texture).then(e=>{if("repeat"!==n.uvMode)return e;const t=e.clone();return t.wrapS=i.RepeatWrapping,t.needsUpdate=!0,t}):null,opacityChannel:e.output.opacityChannel,color:e.output.color,colorEnd:e.output.colorEnd,intensity:e.output.intensity??1,intensityEnd:e.output.intensityEnd??1,length:e.output.length,opacityStart:e.output.opacityStart,opacityEnd:e.output.opacityEnd,bloom:e.output.bloom,scrollSpeed:e.output.scrollSpeed,uvMode:e.output.uvMode,unitsPerRepeat:e.output.unitsPerRepeat,width:e.output.width,billboard:e.output.billboard??!1,renderOrder:e.output.renderOrder});break}default:console.error("Failed to create particly system body: "+JSON.stringify(e))}const q=new Ie(D);null!=H&&L?.push(H.body.getValue()),q.parent=c,q.setRate(O),q._space=e.output.space;const X=await Promise.all(e.initializers.filter(e=>!1!==e.enabled).filter(e=>null!=re[e.type]).map(async e=>{const t=re[e.type],a=await W(e.params??{},r,s);return de(t,e.params??{},a,T)}));"world"===e.output.space&&X.push(new Y("shape"===e.output.type||"mesh"===e.output.type)),X.push(H,new K),q.addInitializers(X);const Z=await Promise.all(e.behaviours.filter(e=>!1!==e.enabled).filter(e=>null!=ae[e.type]).map(async e=>{const t=ae[e.type];for(const[a,r]of Object.entries(e.params))t.parameters&&null!=t.parameters[a]&&"curve"===t.parameters[a].type&&r.type!==U.Curve&&(r.type=U.Curve);const a=await W(e.params??{},r,s);return ue(t,e.params??{},a,T)}));Z.sort((e,t)=>Number(t instanceof ie)-Number(e instanceof ie)),Z.push(new Se);for(const t of Z)t instanceof ie&&(t.physics=x,t.emitEvents&&null!=F&&(t.onCollision=(t,a)=>F(e.id,e.name,t,a,"local"===e.output.space)));q.addBehaviours(Z);for(const a of e.children??[]){const e=await be(a,t,r,s,c,x,S,T,D,F,L),i=new EmitterPool(()=>{const t=e.clone();return t.onExpired=()=>{const e=q.childEmitters.findIndex(e=>e.id===t.id);-1!=e&&q.childEmitters.splice(e,1),null!=t.parentParticle&&(o.delete(t.parentParticle.id),t.parentParticle=null),i.release(t)},t}),n=q.eventDispatcher,o=new Map;q.bindEmitterEvent=!0,n.addEventListener("PARTICLE_DEAD",e=>{const t=o.get(e.id);null!=t&&(q.detachChildEmitterFromParticle(t,e),o.delete(e.id))});let l="PARTICLE_CREATED";if("spawnEvent"in a)switch(a.spawnEvent){case"collision":l="PARTICLE_COLLISION";break;case"start":l="PARTICLE_CREATED"}n.addEventListener(l,e=>{const t=i.get();t.age=0,t.totalEmitTimes=-1,t.particles.length=0,t.currentEmitTime=0,t.cID=0,t.eventDispatcher.removeAllEventListeners(),q.childEmitters.push(t),o.set(e.id,t),t.parentParticle=e,t.system=q.system,t.emit()})}return q}const ve=(new i.TextureLoader).load("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJkSURBVHjaxJeJbusgEEW94S1L//83X18M2MSuLd2pbqc4wZGqRLrKBsyZhQHny7Jk73xVL8xpVhWrcmiB5lX+6GJ5YgQ2owbAm8oIwH1VgKZUmGcRqKGGPgtEQQAzGR8hQ59fAmhJHSAagigJ4E7GPWRXOYC6owAd1JM6wDQPADyMWUqZRMqmAojHp1Vn6EQQEgUNMJLnUjMyJsM49wygBkAPw9dVFwXRkncCIIW3GRgoTQUZn6HxCMAFEFd8TwEQ78X4rHbILoAUmeT+RFG4UhQ6MiIAE4W/UsYFjuVjAIa2nIY4q1R0GFtQWG3E84lqw2GO2QOoCKBVu0BAPgDSU0eUDjjQenNkV/AW/pWChhpMTelo1a64AOKM30vk18GzTHXCNtI/Knz3DFBgsUqBGIjTInXRY1yA9xkVoqW5tVq3pDR9A0hfF5BSARmVnh7RMDCaIdcNgbPBkgzn1Bu+SfIEFSpSBmkxyrMicb0fAEuCZrWnN89veA/4XcakrPcjBWzkTuLjlbfTQPOlBhz+HwkqqPXmPQDdrQItxE1moGof1S74j/8txk8EHhTQrAE8qlwfqS5yukm1x/rAJ9Jiaa6nyATqD78aUVBhFo8b1V4DdTXdCW+IxA1zB4JhiOhZMEWO1HqnvdoHZ4FAMIhV9REF8FiUm0jsYPEJx/Fm/N8OhH90HI9YRHesWbXXZwAShU8qThe7H8YAuJmw5yOd989uRINKRTJAhoF8jbqrHKfeCYdIISZfSq26bk/K+yO3YvfKrVgiwQBHnwt8ynPB25+M8hceTt/ybPhnryJ78+tLgAEAuCFyiQgQB30AAAAASUVORK5CYII=");export async function createShaderMaterial(e,t,a){const r=a.get(e.shader);if(null==r)return console.error("No shader exists with name "+e.shader),new i.Material;const s=new r.type;"sprite"===e.type&&s instanceof oe&&(s.instanced=!0);const n=await L(e.shaderParams??{},r.type,t,{getTexture:e=>t.getTexture(e.id),getMaterial:e=>t.getMaterial(e.id),getMesh:e=>t.getMesh(e.id)},void 0,void 0,void 0,void 0,void 0,s);Object.assign(s,n);const o=s.build();return o.userData.vfxShader={key:"shader:"+e.shader,name:e.shader},o}export async function createShaderGraphMaterial(e,t,a,r,s,i={}){const n=e.shaderGraph;if("asset"!==n?.source||null==n.assetId)return console.warn(`Missing shader graph asset reference for ${e.type} VFX output`),null;const o=await a.getAsset(n.assetId),l=o?.shaderGraph;if(null==l)return console.warn(`Missing shader graph asset "${n.assetId}" for ${e.type} VFX output`),null;if(l.target!==t)return console.warn(`Shader graph "${o.name}" targets "${l.target}" but ${e.type} VFX output expects "${t}"`),null;const p="sprite"===t||"decal"===t||"surface"===t&&"unlit"===l.shadingModel;try{const t=await O(e.shaderGraphParams??{},l,a,r,void 0,s.shaders,p),c=he(l,{parameters:t,trailBillboard:i.trailBillboard});return c.userData.customShaderName=`shaderGraph:${n.assetId}`,c.userData.vfxShader={key:"shaderGraph:"+n.assetId,name:o.name,target:l.target,shadingModel:l.shadingModel,animationLayout:p?"shared":"specialized",parameters:Object.fromEntries((l.parameters??[]).map(t=>{const a=e.shaderGraphParams?.[t.name],r=!1!==a?.override&&null!=a?.value?a.value:t.defaultValue,s=null!=r&&"object"==typeof r&&"a"in r&&"b"in r&&null!=r.b;return[t.name,s?{mode:"animated",easing:"easing"in r?r.easing:void 0}:{mode:"static"}]}))},Ee(c,e),c}catch(e){return console.log("Shader graph VFX runtime error: "+e,e),null}}export function createGpuSpriteShaderGraphResolver(e,t,a,r){return async s=>{const n=Me(s);let o=null;if("shaderGraph"===n)o=await createShaderGraphMaterial(s,"sprite"===s.type?"sprite":"decal"===s.type?"decal":"trail"===s.type?"trail":"surface",e,t,a,"trail"===s.type?{trailBillboard:s.billboard??!1}:void 0);else if("shader"===n&&"decal"!==s.type&&null!=r)o=await createShaderMaterial(s,r,a);else if("material"!==n||"mesh"!==s.type&&"shape"!==s.type||null==s.material||null==r){if("default"===n&&"mesh"===s.type&&null!=s.assetId&&null!=r){const e=await r.getAsset(s.assetId),t=e.materialAssignments?.find(e=>null!=e.materialId);null!=t&&(o=await we(t.materialId,r))}else if("default"===n&&"decal"===s.type&&null!=r){const e=!1!==s.unlit?new pe:new ce;e.color=new i.Color(s.color),e instanceof pe?e.intensity=s.intensity??1:e.emissiveIntensity=s.intensity??1,s.colorMap&&(e.colorMap=await r.getTexture(s.colorMap)),s.alphaMap&&(e.alphaMap=await r.getTexture(s.alphaMap)),o=e.build(),o.transparent=!0}}else o="mesh"===s.type?await we(s.material,r):xe(await r.getMaterial(s.material));return null==o?null:("mesh"!==s.type&&"shape"!==s.type||!o.transparent||(o.depthWrite=!1),o instanceof p?o:(console.warn(`GPU ${s.type} materials must compile to NodeShaderMaterial; using CPU fallback`),null))}}function Me(e){return null!=e.materialSource?("material"!==e.materialSource||"mesh"!==e.type&&"shape"!==e.type||null!=e.material)&&("shaderGraph"!==e.materialSource||"asset"===e.shaderGraph?.source&&null!=e.shaderGraph.assetId)&&("shader"!==e.materialSource||"shader"in e&&null!=e.shader)?e.materialSource:"default":"asset"===e.shaderGraph?.source?"shaderGraph":"shader"in e&&null!=e.shader?"shader":"mesh"!==e.type&&"shape"!==e.type||null==e.material?"default":"material"}function Ee(e,t){null!=e&&(fe(e,!1),"bloom"in t&&!0===t.bloom&&(e.userData.hasBloom=!0),"sprite"===t.type?(e.blending=te[t.blendingMode]??i.NormalBlending,!0===t.lockY&&e instanceof i.ShaderMaterial&&(e.defines??(e.defines={}),e.defines.LOCK_Y_AXIS="")):"decal"===t.type?(e.blending=te[t.blendingMode]??i.NormalBlending,e.transparent=!0,e.side=i.BackSide,e.depthTest=!1,e instanceof i.ShaderMaterial&&(e.defines??(e.defines={}),e.defines.IS_PARTICLE="")):"shape"!==t.type&&"mesh"!==t.type||e instanceof i.ShaderMaterial&&(e.defines??(e.defines={}),e.defines.IS_PARTICLE=""))}function Ae(e,t){const a=t.renderOrder;null!=a&&e.traverse(e=>e.renderOrder=a)}function xe(e){const t=G(new h("instanceColor")).rgb;let a,r=G(new o("particleData")).x;if(e instanceof i.MeshStandardMaterial||e instanceof i.MeshLambertMaterial||e instanceof i.MeshBasicMaterial){let s=t.multiply(S(e.color));null!=e.map&&(s=s.multiply(C(e.map).sample(B.uv).rgb)),null!=e.alphaMap&&(r=r.multiply(C(e.alphaMap).sample(B.uv).r)),e instanceof i.MeshStandardMaterial?a=new p({color:T({color:s,emissive:S(e.emissive),emissiveIntensity:b(e.emissiveIntensity),roughness:e.roughness,metalness:e.metalness}).rgb.rgba(r),emissive:S(e.emissive).multiplyScalar(e.emissiveIntensity),transparent:e.transparent,opacity:r,alphaTest:e.alphaTest}):e instanceof i.MeshLambertMaterial?a=new p({color:v({color:s}).rgb.rgba(r),emissive:S(e.emissive).multiplyScalar(e.emissiveIntensity),transparent:e.transparent,opacity:r,alphaTest:e.alphaTest}):e instanceof i.MeshBasicMaterial&&(a=new p({color:s.rgb.rgba(r),emissive:s.rgb,transparent:e.transparent,opacity:r,alphaTest:e.alphaTest}))}else e instanceof p&&(a=e.clone(),a.defines.IS_PARTICLE="");return null!=a&&(!0===e.userData?.hasBloom&&null!=a&&(a.userData.hasBloom=!0),a.side=e.side,a.transparent=e.transparent),a??e}class Se extends t{initialize(e){e.body instanceof i.Object3D&&e.body.traverse(e=>{if(e instanceof i.Mesh){const t=e.material;t instanceof p&&(null!=t.uniforms[H]||null!=t.uniforms[q]||t.uniforms[X])}})}mutate(e,t,a){this.energize(e,t),e.target instanceof n&&e.target.traverse(t=>{if(t instanceof i.Mesh){const a=t.material;if(a instanceof p){let t=!1;null!=a.uniforms[H]&&(a.uniforms[H].value=this.energy,t=!0),null!=a.uniforms[q]&&(a.uniforms[q].value=e.age,t=!0),null!=a.uniforms[X]&&(a.uniforms[X].value=e.velocity,t=!0),t&&me(a)}}})}}class Ie extends r{constructor(e){super(),this.transformRoot=e,this.childEmitters=[],this.bindEmitterEvent=!1,this.onExpired=()=>{}}clearParticlesRecursive(){let e=this.childEmitters.length;for(;e--;){const t=this.childEmitters[e];t.stopEmit(),t.clearParticlesRecursive(),t.onExpired()}this.childEmitters.length=0;let t=this.particles.length;for(;t--;){const e=this.particles[t];this.system?.dispatch("PARTICLE_DEAD",e),this.bindEmitterEvent&&this.dispatch("PARTICLE_DEAD",e),null!=this.system?this.system.pool.expire(e.reset()):e.reset()}this.particles.length=0}detachChildEmitterFromParticle(e,t){this.copyPositionToChildSpace(e,t.position),e.parentParticle=null,e.stopEmit()}update(e){if(!this.isEmitting&&0===this.particles.length&&0===this.childEmitters.length)return;this.age+=e,(this.dead||this.age>=this.life)&&this.destroy(),this.isEmitting&&this.generate(e),this.integrate(e);let t=this.particles.length;for(;t--;){const e=this.particles[t];e.dead&&(this.system&&this.system.dispatch("PARTICLE_DEAD",e),this.bindEmitterEvent&&this.dispatch("PARTICLE_DEAD",e),this.system.pool.expire(e.reset()),this.particles.splice(t,1))}this.updateEmitterBehaviours(e),this.updateChildren(e),this.isEmitting||0!==this.particles.length||0!==this.childEmitters.length||this.onExpired()}updateChildren(e){for(const t of this.childEmitters)null!=t.parentParticle&&this.copyPositionToChildSpace(t,t.parentParticle.position),t.update(e)}copyPositionToChildSpace(e,t){const a=this._space??"world",r=e._space??"world";a!==r&&null!=this.transformRoot?(Pe.copy(t),"local"===a&&"world"===r?Pe.applyMatrix4(this.transformRoot.matrixWorld):"world"===a&&"local"===r&&(Te.copy(this.transformRoot.matrixWorld).invert(),Pe.applyMatrix4(Te)),e.position.copy(Pe)):e.position.copy(t)}clone(){const e=new Ie(this.transformRoot);return e.setRate(this.rate.clone()),e.behaviours=this.behaviours,e.initializers=this.initializers,e._space=this._space,e.body=this.body,e.parent=this.parent,e.system=this.system,e}setParentRecursive(e){this.system=e,this.childEmitters.forEach(t=>t.setParentRecursive(e))}}const Pe=new i.Vector3,Te=new i.Matrix4;export class EmitterPool{constructor(e){this.creator=e,this.instances=[]}get(){0==this.instances.length&&this.instances.push(this.creator());return this.instances.pop()}release(e){this.instances.push(e)}dispose(){this.instances.length=0}}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
2
|
import type { ViewController } from '../../gameplay/services/render.js';
|
|
3
|
+
import type { RenderingView } from '../../rendering.js';
|
|
4
|
+
/** Compile concurrently prepared CPU effects together without skipping any output variant. */
|
|
5
|
+
export declare function prepareVfxRendering(bodies: readonly unknown[], view: ViewController, renderingView: RenderingView): Promise<void>;
|
|
3
6
|
/** Build the same render variants as gameplay without running emission or physics. */
|
|
4
7
|
export declare function createVfxRenderPreparation(bodies: readonly unknown[], view: ViewController): {
|
|
5
8
|
scene: THREE.Scene;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Particle as e,Emitter as
|
|
1
|
+
import{Particle as e,Emitter as t}from"@hology/nebula";import*as r from"three";import{OnceRate as o}from"./rates.js";import{MultiRenderer as n}from"./vfx-renderers.js";const a=new WeakMap;export function prepareVfxRendering(e,t,o){const n=createVfxRenderPreparation(e,t);return new Promise((e,t)=>{let s=a.get(o);null==s&&(s=[],a.set(o,s),setTimeout(()=>{!async function(e,t){a.delete(e);const o=new r.Scene;for(const e of t)o.add(e.preparation.scene);try{await e.compileAsync(o),"webgpu"!==e.activeBackend&&e.initTextures(o);for(const e of t)e.resolve()}catch(e){for(const r of t)r.reject(e)}finally{o.clear()}}(o,s)},0)),s.push({preparation:n,resolve:e,reject:t})}).finally(()=>n.dispose())}export function createVfxRenderPreparation(a,c){const i=new r.Scene,f=new n(i,i,r,c),l=new Set,p=new Set,d=new t;d.setRate(new o(0,1));const m=[],u=()=>{i.traverse(e=>{if(!(e instanceof r.Mesh))return;l.add(e.geometry);const t=Array.isArray(e.material)?e.material:[e.material];for(const e of t)a.some(t=>t instanceof r.Object3D&&s(t,e))||p.add(e)});for(const e of m)f.onParticleDead(e);for(const e of l)e.dispose();for(const e of p)e.dispose();i.clear()};try{for(const t of a){const o=new e;if(o.parent=d,o.life=1,o.useAlpha=!0,o.useColor=!0,t instanceof r.Object3D){const e=t.clone();e.traverse(e=>{e instanceof r.Mesh&&(e.geometry=e.geometry.clone(),l.add(e.geometry))}),o.body=e}else o.body=t;f.onParticleCreated(o),m.push(o)}return f.onSystemUpdate(),{scene:i,dispose:u}}catch(e){throw u(),e}}function s(e,t){let o=!1;return e.traverse(e=>{(e instanceof r.Mesh||e instanceof r.Sprite)&&(o||(o=Array.isArray(e.material)?e.material.includes(t):e.material===t))}),o}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,32 +1,14 @@
|
|
|
1
1
|
import { Subject } from 'rxjs';
|
|
2
2
|
import { Vector3 } from 'three';
|
|
3
3
|
import { ActorComponent } from '../../component.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
/** Actor-local offset from position to the incoming Bezier handle. */
|
|
8
|
-
inTangent: Vector3;
|
|
9
|
-
/** Actor-local offset from position to the outgoing Bezier handle. */
|
|
10
|
-
outTangent: Vector3;
|
|
11
|
-
}
|
|
12
|
-
export interface ClosestSplinePoint {
|
|
13
|
-
position: Vector3;
|
|
14
|
-
tangent: Vector3;
|
|
15
|
-
t: number;
|
|
16
|
-
distance: number;
|
|
17
|
-
distanceAlongSpline: number;
|
|
18
|
-
}
|
|
19
|
-
/** A generic cubic Bezier spline whose points and tangents are actor-local. */
|
|
4
|
+
import { type SplinePoint } from '../../../../navigation/spline-data.js';
|
|
5
|
+
export { packSplineData, parseSplineData, type SplinePoint, type ClosestSplinePoint } from '../../../../navigation/spline-data.js';
|
|
6
|
+
/** Editable component around the renderer-independent spline model. */
|
|
20
7
|
export declare class SplineComponent extends ActorComponent {
|
|
21
|
-
|
|
8
|
+
private readonly data;
|
|
22
9
|
splineData: string;
|
|
23
|
-
/** Emits after an in-place authoring/runtime edit. Consumers can update incrementally. */
|
|
24
10
|
readonly changed: Subject<SplineComponent>;
|
|
25
|
-
|
|
26
|
-
private _closed;
|
|
27
|
-
private arcLengths;
|
|
28
|
-
private arcTs;
|
|
29
|
-
private _length;
|
|
11
|
+
constructor();
|
|
30
12
|
onInit(): void;
|
|
31
13
|
onEndPlay(): void;
|
|
32
14
|
get points(): readonly SplinePoint[];
|
|
@@ -36,30 +18,16 @@ export declare class SplineComponent extends ActorComponent {
|
|
|
36
18
|
setPoints(points: readonly SplinePoint[], closed?: boolean): void;
|
|
37
19
|
updatePoint(index: number, point: SplinePoint): void;
|
|
38
20
|
addPoint(point: SplinePoint, index?: number): void;
|
|
39
|
-
|
|
40
|
-
insertPointOnSegment(segmentIndex: number, t?: number): number;
|
|
21
|
+
insertPointOnSegment(index: number, t?: number): number;
|
|
41
22
|
removePoint(index: number): void;
|
|
42
23
|
setClosed(closed: boolean): void;
|
|
43
24
|
applySceneParameters(parameters: {
|
|
44
25
|
splineData?: string;
|
|
45
26
|
}, changed: Set<'splineData'>): boolean;
|
|
46
|
-
/** Samples an actor-local position using normalized t. */
|
|
47
27
|
samplePosition(t: number, target?: Vector3): Vector3;
|
|
48
28
|
sampleTangent(t: number, target?: Vector3): Vector3;
|
|
49
29
|
samplePositionAtDistance(distance: number, target?: Vector3): Vector3;
|
|
50
30
|
sampleTangentAtDistance(distance: number, target?: Vector3): Vector3;
|
|
51
|
-
|
|
52
|
-
closestPoint(point: Vector3, target?: Vector3): ClosestSplinePoint;
|
|
53
|
-
private commitChange;
|
|
54
|
-
private get segmentCount();
|
|
55
|
-
private resolveSegment;
|
|
56
|
-
private rebuildArcLengths;
|
|
57
|
-
private tAtDistance;
|
|
58
|
-
private distanceAtT;
|
|
31
|
+
closestPoint(point: Vector3, target?: Vector3): import("./spline-component.js").ClosestSplinePoint;
|
|
59
32
|
}
|
|
60
|
-
export declare function packSplineData(points: readonly SplinePoint[], closed: boolean): string;
|
|
61
|
-
export declare function parseSplineData(data: string): {
|
|
62
|
-
points: SplinePoint[];
|
|
63
|
-
closed: boolean;
|
|
64
|
-
};
|
|
65
33
|
//# sourceMappingURL=spline-component.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{__decorate as t,__metadata as
|
|
1
|
+
import{__decorate as t,__metadata as a}from"tslib";import{Subject as e}from"rxjs";import{Vector3 as s}from"three";import{Parameter as i}from"../../../../shader/parameter.js";import{ActorComponent as n,Component as o}from"../../component.js";import{SplineData as r}from"../../../../navigation/spline-data.js";export{packSplineData,parseSplineData}from"../../../../navigation/spline-data.js";let p=class extends n{constructor(){super(),this.data=new r,this.splineData=this.data.splineData,this.changed=new e,this.data.changed.subscribe(()=>{this.splineData=this.data.splineData,this.changed.next(this)})}onInit(){this.setData(this.splineData,!1)}onEndPlay(){this.data.changed.complete(),this.changed.complete()}get points(){return this.data.points}get closed(){return this.data.closed}get length(){return this.data.length}setData(t,a=!0){this.data.setData(t,a),this.splineData=this.data.splineData}setPoints(t,a=this.closed){this.data.setPoints(t,a)}updatePoint(t,a){this.data.updatePoint(t,a)}addPoint(t,a=this.points.length){this.data.addPoint(t,a)}insertPointOnSegment(t,a=.5){return this.data.insertPointOnSegment(t,a)}removePoint(t){this.data.removePoint(t)}setClosed(t){this.data.setClosed(t)}applySceneParameters(t,a){return!(1!==a.size||!a.has("splineData")||"string"!=typeof t.splineData)&&(this.setData(t.splineData),!0)}samplePosition(t,a=new s){return this.data.samplePosition(t,a)}sampleTangent(t,a=new s){return this.data.sampleTangent(t,a)}samplePositionAtDistance(t,a=new s){return this.data.samplePositionAtDistance(t,a)}sampleTangentAtDistance(t,a=new s){return this.data.sampleTangentAtDistance(t,a)}closestPoint(t,a=new s){return this.data.closestPoint(t,a)}};t([i({label:"Spline Data",type:String}),a("design:type",String)],p.prototype,"splineData",void 0),p=t([o({inEditor:!0,typeId:"SplineComponent"}),a("design:paramtypes",[])],p);export{p as SplineComponent};/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NavMesh } from 'recast-navigation';
|
|
2
2
|
import { BaseActor } from "../../index.js";
|
|
3
|
-
export declare function safeRecastInit(): Promise<
|
|
3
|
+
export declare function safeRecastInit(): Promise<void>;
|
|
4
4
|
export type NavMeshLoader = {
|
|
5
5
|
save(): any;
|
|
6
6
|
};
|
|
@@ -10,6 +10,7 @@ declare class NavMeshActor extends BaseActor {
|
|
|
10
10
|
private world;
|
|
11
11
|
private editorVisualisation;
|
|
12
12
|
debug: boolean;
|
|
13
|
+
dynamic: boolean;
|
|
13
14
|
refreshMs: number;
|
|
14
15
|
private tileSize;
|
|
15
16
|
walkableClimb: number;
|
|
@@ -20,10 +21,23 @@ declare class NavMeshActor extends BaseActor {
|
|
|
20
21
|
private debugDrawer;
|
|
21
22
|
private bounds;
|
|
22
23
|
private recreateInterval;
|
|
24
|
+
private ended;
|
|
25
|
+
private startTimer;
|
|
26
|
+
private bakeAbort;
|
|
27
|
+
private generationDisposed;
|
|
28
|
+
private staticNavMesh?;
|
|
29
|
+
private activeBakeKey;
|
|
30
|
+
private initializing;
|
|
31
|
+
private sourceRevision;
|
|
32
|
+
private sourceCheckedAt;
|
|
23
33
|
onInit(): Promise<void>;
|
|
24
34
|
onEndPlay(): void;
|
|
25
|
-
|
|
35
|
+
private resetGeneration;
|
|
36
|
+
private stopNavigation;
|
|
37
|
+
private currentBounds;
|
|
26
38
|
private init;
|
|
39
|
+
applySceneParameters(parameters: Record<string, unknown>, changedParameters: ReadonlySet<string>): boolean;
|
|
40
|
+
private initDynamic;
|
|
27
41
|
}
|
|
28
42
|
export default NavMeshActor;
|
|
29
43
|
//# sourceMappingURL=navmesh-actor.d.ts.map
|
|
@@ -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 c,ShapeType as l,TriMesh as h}from"@dimforge/rapier3d-simd-compat";import{init as u}from"@recast-navigation/core";import{DebugDrawer as p,getPositionsAndIndices as d}from"@recast-navigation/three";import{BehaviorSubject as f,Subject as m,debounceTime as w,filter as g,firstValueFrom as b,takeUntil as y}from"rxjs";import*as x from"three";import{BufferGeometryUtils as M,ConvexHull as v}from"three/examples/jsm/Addons.js";import{Actor as B,BaseActor as S,Parameter as z,PhysicsSystem as j,ViewController as k,World as A,attach as C,inject as P}from"../../index.js";import{sleepDelay as V}from"../../../utils/async.js";import{hasSharedArrayBufferSupport as D,toSharedFloat32Array as F,toSharedUint32Array as I}from"../../../utils/buffer.js";import{DynamicTiledNavMesh as N,MAX_NAV_MESH_WORKERS as H}from"../../ai/dynamic-tiled-navmesh.js";import{isNavigationAreaContributor as G}from"../../ai/navigation-areas.js";import{TriggerVolumeMesh as E}from"./components/volume-editor-component.js";var R;!function(e){e[e.none=0]="none",e[e.starting=1]="starting",e[e.started=2]="started"}(R||(R={}));let T=R.none,U=new f(!1);export async function safeRecastInit(){return T===R.none?(T=R.starting,u().then(()=>{U.next(!0),T=R.started})):b(U.pipe(g(e=>e)))}new x.Box3(new x.Vector3(-100,-100,-100),new x.Vector3(100,100,100));const O=Math.max(1,Math.min(H,(navigator.hardwareConcurrency??2)-1));let q=!1,W=class extends S{constructor(){super(...arguments),this.physics=P(j),this.view=P(k),this.world=P(A),this.editorVisualisation=C(E),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;K.setFromCenterAndSize(this.position,this.object.scale).equals(this.bounds)||(this.disposed.next(!0),this.disposed=new m,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 N({navMeshBounds:this.bounds,recastConfig:i,maxTiles:1024,workers:O,cacheId:"nav"+this.object.userData?.src?.id});this.navMesh=r.navMesh;const a=this.tileSize*i.cs*2,c=performance.now(),l=new Map,h=new p;h.userData.isDebugDrawer=!0,this.debugDrawer=h;r.navMesh;const u=this.bounds,f=()=>{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=l.get(r)?.mesh,c=a??J(i);if($(i,c),null!=c){o.copy(c.geometry.boundingBox),o.min.add(c.position),o.max.add(c.position);const e=o.intersectsBox(t)||!0,s=o.intersectsBox(this.bounds);l.set(r,{pos:i.translation(),mesh:c}),e&&s&&n.push(c)}}return n},m=new x.Box3,g=new Map,b=new Map,M=new Map,v=new Map;let B=!0,S=performance.now(),z=!1;const j=function(e,t){let n=!1;return(async()=>{for(;!n;)await e(),await V(t)})(),()=>{n=!0}}(async()=>{if(z)return;const e=new x.Box3,t=f();for(const n of t){const t=g.get(n);!0!==t?.equals(n.position)&&(null!=t&&e.expandByPoint(t),e.expandByObject(n),g.set(n,n.position.clone()))}const n=[],s=new Set;for(const t of this.world.actors){if(!G(t))continue;s.add(t);const o=t.getNavigationAreaModifiers();n.push(...o);const i=Q(o),r=JSON.stringify(o);if(M.get(t)!==r){const n=b.get(t);null!=n&&e.union(n),e.union(i),b.set(t,i),M.set(t,r)}}for(const t of[...b.keys()])s.has(t)||(e.union(b.get(t)),b.delete(t),M.delete(t));const o=e.isEmpty()?[]:r.getTilesForBounds(e.expandByScalar(50));o.length>500&&console.warn("Too many tiles to update. Consider increasing tile size");const i=o.slice(0,500);if(0!=i.length){const s=[];for(const n of t)m.setFromObject(n),m.intersectsBox(e)&&s.push(n);let[o,a]=d(s);a=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,a,u),D&&(o=F(o),a=I(a));const c=B;B=!1,await Promise.all(i.map(e=>{S=performance.now();const t=function(e,t,n){const s=(e.recastConfig.walkableRadius+3)*e.recastConfig.cs,o=new x.Vector3(e.navMeshBoundsMin.x+t[0]*e.tcs-s,e.navMeshBoundsMin.y,e.navMeshBoundsMin.z+t[1]*e.tcs-s),i=new x.Vector3(o.x+e.tcs+2*s,e.navMeshBoundsMax.y,o.z+e.tcs+2*s),r=new x.Box3(o,i);return n.filter(e=>Q([e]).intersectsBox(r))}(r,e,n);return r.buildTile(o,a,e,c,t).then(()=>{const t=e[0]+","+e[1];v.set(t,(v.get(t)??0)+1),this.debug})})).then(()=>{this.debug,z=!1})}else z=!1},this.refreshMs??1e4);this.disposed.subscribe(()=>j()),r.onNavMeshUpdate.pipe(y(this.disposed),w(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 k=performance.now()-c;k>1e3&&console.warn(`NavMesh update took ${k} ms. Consider changing tileSize or other parameter that may affect performance`)}};e([z(),t("design:type",Boolean)],W.prototype,"debug",void 0),e([z(),t("design:type",Number)],W.prototype,"walkableClimb",void 0),e([z({range:[0,89]}),t("design:type",Number)],W.prototype,"walkableSlopeAngle",void 0),e([z(),t("design:type",Number)],W.prototype,"walkableHeight",void 0),e([z({range:[.01,10]}),t("design:type",Number)],W.prototype,"cellSize",void 0),W=e([B({icon:"route"})],W);export default W;const _=new Map,L=new WeakMap;function X(e){if(e.shape instanceof c)return function(e){const t=e.shape;if(t.type!==l.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,c=s.scale.z,h=s.scale.y,u=i+1,p=new x.PlaneGeometry(c,a,r,i);p.rotateX(-Math.PI/2);const d=p.attributes.position.array;let f=0;for(let e=0;e<u;e++)for(let t=0;t<u;t++)d[f+1]=o[t*u+e]*h,f+=3,0!=d[f+1]&&(n=!0);if(!n){const e=new x.PlaneGeometry(a,c,2,2);return e.rotateX(-Math.PI/2),e}return p}(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=L.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 L.set(e,s),s}(e.shape.vertices);let n=_.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 v).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),_.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=M.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 J(e){const t=X(e);if(null==t)return null;const n=Y,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 K=new x.Box3;function Q(e){const t=new x.Box3;for(const n of e)for(const e of n.vertices)t.expandByPoint(new x.Vector3(e[0],n.minHeight,e[2])),t.expandByPoint(new x.Vector3(e[0],n.maxHeight,e[2]));return t}const Y=new x.MeshBasicMaterial({wireframe:!1,color:16711680,side:x.FrontSide});/*
|
|
1
|
+
import{__decorate as e,__metadata as t}from"tslib";import{importNavMesh as i}from"recast-navigation";import{bakeNavigation as s,collectNavigationInputs as n,landscapeNavigationGeometry as o,navigationConfig as a,navigationEntries as r,navigationSettings as h,navigationShapeGeometry as c,navigationGeometryInput as d}from"../../../navigation/bake.js";import{navigationCacheKey as l,readEditorNavigation as u,writeEditorNavigation as p}from"../../../navigation/cache.js";import{executeTask as f,hasWorkerBuilder as g}from"../../../worker/index.js";import{Ball as m,Capsule as b,Cone as w,ConvexPolyhedron as v,Cuboid as y,Cylinder as x,Heightfield as M,ShapeType as B,TriMesh as D}from"@dimforge/rapier3d-simd-compat";import{init as k}from"@recast-navigation/core";import{DebugDrawer as S,getPositionsAndIndices as A}from"@recast-navigation/three";import{Subject as j,debounceTime as z,takeUntil as N}from"rxjs";import*as C from"three";import{Mesh as V}from"three";import{BufferGeometryUtils as P,ConvexHull as I}from"three/examples/jsm/Addons.js";import{Actor as G,BaseActor as F,Parameter as R,PhysicsSystem as W,ViewController as E,World as H,attach as T,inject as _}from"../../index.js";import{sleepDelay as K}from"../../../utils/async.js";import{hasSharedArrayBufferSupport as U,toSharedFloat32Array as q,toSharedUint32Array as O}from"../../../utils/buffer.js";import{DynamicTiledNavMesh as L,MAX_NAV_MESH_WORKERS as X}from"../../ai/dynamic-tiled-navmesh.js";import{isNavigationAreaContributor as J}from"../../ai/navigation-areas.js";import{TriggerVolumeMesh as Q}from"./components/volume-editor-component.js";let $;export function safeRecastInit(){return $??($=k().catch(e=>{throw $=void 0,e}))}new C.Box3(new C.Vector3(-100,-100,-100),new C.Vector3(100,100,100));const Y=Math.max(1,Math.min(X,(navigator.hardwareConcurrency??2)-1));let Z=class extends F{constructor(){super(...arguments),this.physics=_(W),this.view=_(E),this.world=_(H),this.editorVisualisation=T(Q),this.debug=!0,this.dynamic=!1,this.refreshMs=4e3,this.tileSize=50,this.walkableClimb=.3,this.walkableSlopeAngle=45,this.walkableHeight=2,this.cellSize=.2,this.bounds=new C.Box3,this.ended=!1,this.bakeAbort=new AbortController,this.generationDisposed=new j,this.activeBakeKey="",this.initializing=!1,this.sourceRevision=-1,this.sourceCheckedAt=0}async onInit(){this.disposed.subscribe(()=>this.stopNavigation()),await safeRecastInit(),this.ended||(this.object.userData.navigationBake&&!this.dynamic?await this.init():this.startTimer=setTimeout(()=>{this.init()},10),this.ended||(this.recreateInterval=setInterval(()=>{"_hology_transform_group"!==this.object.parent?.name&&(!this.object.userData.navigationBakeSource&&this.currentBounds().equals(this.bounds)||this.init())},2e3)))}onEndPlay(){this.stopNavigation()}resetGeneration(){this.generationDisposed.next(!0),this.generationDisposed.complete(),this.generationDisposed=new j}stopNavigation(){this.ended||(this.ended=!0,clearTimeout(this.startTimer),clearInterval(this.recreateInterval),this.bakeAbort.abort(),this.generationDisposed.next(!0),this.generationDisposed.complete(),this.staticNavMesh?.destroy(),this.staticNavMesh=void 0,this.debugDrawer?.removeFromParent(),this.debugDrawer?.dispose(),this.navMesh=void 0)}currentBounds(){this.object.updateWorldMatrix(!0,!1);const e=this.object.getWorldPosition(new C.Vector3),t=this.object.getWorldScale(new C.Vector3);return t.set(Math.abs(t.x),Math.abs(t.y),Math.abs(t.z)),(new C.Box3).setFromCenterAndSize(e,t)}async init(){if(!this.ended&&!this.initializing){this.initializing=!0;try{const e=this.currentBounds(),t=a(e,this),o=h(e,t),m=this.object.userData.navigationBake,b=this.object.userData.navigationBakeSource,w=this.object.userData.navigationBakeRevision?.()??0;if(b&&w===this.sourceRevision&&e.equals(this.bounds)&&performance.now()-this.sourceCheckedAt<3e4&&this.staticNavMesh)return;if(!this.dynamic&&(m||b)){let v,y,x=m?.file??"",M=!1;if(m&&m.settings===o){if(this.activeBakeKey===x)return;const e=await fetch(m.file,{signal:this.bakeAbort.signal});if(!e.ok)throw new Error("Unable to load baked navmesh: "+m.file);v=new Uint8Array(await e.arrayBuffer())}else if(b){const a=await b();if(x=await l(a,o),this.sourceRevision=w,this.sourceCheckedAt=performance.now(),x===this.activeBakeKey)return;if(v=await u(x),v)try{y=i(v).navMesh}catch{v=void 0}if(!v){const i=r(a.objects,a.assets);a.contribute=async e=>{const t=this.world.actors.find(t=>t.__netSceneId?.replace(/^\//,"")===e.id);if(!t||t===this||["WaterVolume","WaterSplineVolume"].includes(e.source.actor?.type))return;const i=[];return this.physics.forEachStaticActorNavigationCollider(t,e=>{const t=c(e.shape),s=e.translation(),n=e.rotation(),o=(new C.Matrix4).compose(new C.Vector3(s.x,s.y,s.z),new C.Quaternion(n.x,n.y,n.z,n.w),new C.Vector3(1,1,1));i.push(d(t,o)),t.dispose()}),{geometry:i,modifiers:J(t)?t.getNavigationAreaModifiers():[]}};const h=(t.walkableRadius+3)*t.cs,l=await n(a,i,e.clone().expandByScalar(h)),u={id:this.__netSceneId??"",bounds:e,config:t,settings:o};v=await s(u,l,g()?async e=>(await f("navmesh.buildTile",e,{signal:this.bakeAbort.signal,priority:"background"})).navMeshData:void 0,this.bakeAbort.signal),M=!0}}if(this.ended)return void y?.destroy();if(M&&b){const e=this.currentBounds(),t=h(e,a(e,this));if(x!==await l(await b(),t))return void(this.sourceRevision=-1);if(await p(this.__netSceneId??String(this.id),x,v).catch(e=>console.warn("Could not save editor navmesh",e)),this.ended)return}if(v){const t=y??i(v).navMesh;return this.resetGeneration(),this.staticNavMesh?.destroy(),this.staticNavMesh=this.navMesh=t,this.bounds.copy(e),this.activeBakeKey=x,this.debugDrawer?.removeFromParent(),this.debugDrawer?.dispose(),this.debugDrawer=new S,this.debugDrawer.userData.isDebugDrawer=!0,this.debugDrawer.visible=this.debug,this.debug&&this.debugDrawer.drawNavMesh(t),void this.object.parent?.add(this.debugDrawer)}}if(this.activeBakeKey===o&&this.navMesh)return;this.resetGeneration(),this.staticNavMesh?.destroy(),this.staticNavMesh=void 0,this.debugDrawer?.removeFromParent(),this.debugDrawer?.dispose(),this.activeBakeKey=o,this.initDynamic()}catch(e){this.ended||"AbortError"===e?.name||(console.warn("Could not load or bake static navigation; using live generation",e),this.navMesh||this.initDynamic())}finally{this.initializing=!1}}}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,e.debug&&this.navMesh&&(this.debugDrawer.clear(),this.debugDrawer.drawNavMesh(this.navMesh)),1===t.size)}initDynamic(){this.bounds.copy(this.currentBounds());const e=this.bounds.max.x-this.bounds.min.x,t=this.bounds.max.z-this.bounds.min.z,i=Math.max(1,Math.min(e,t)),s=Math.min(400,Math.max(50,Math.ceil(i/2))),n=Math.max(this.cellSize,.01),a={tileSize:Math.max(32,Math.floor(s/n)),walkableClimb:this.walkableClimb/n,walkableSlopeAngle:this.walkableSlopeAngle,walkableRadius:2,walkableHeight:this.walkableHeight/n,detailSampleDist:1,minRegionArea:6,mergeRegionArea:400,cs:n,ch:n,maxSimplificationError:1.3,maxEdgeLen:200},r=new L({navMeshBounds:this.bounds,recastConfig:a,maxTiles:1024,workers:Y,cacheId:"nav"+this.object.userData?.src?.id});this.navMesh=r.navMesh;const h=this.tileSize*a.cs*2,c=performance.now(),d=new Map,l=[],u=this.bounds.clone().expandByScalar((a.walkableRadius+3)*a.cs);this.world.scene.traverse(e=>{const t=e.userData.src??e.userData._src;if("landscape"===t?.shape&&!1!==t.collisionDetection){e.updateWorldMatrix(!0,!1);for(const i of o(t,e.matrixWorld,u)){const e=new C.BufferGeometry;e.setAttribute("position",new C.BufferAttribute(i.positions,3)),e.setIndex(new C.BufferAttribute(i.indices,1)),l.push(new V(e,ae))}}}),this.generationDisposed.subscribe(()=>{for(const e of l)e.geometry.dispose()});const p=new S;p.userData.isDebugDrawer=!0,this.debugDrawer=p;r.navMesh;this.bounds;const f=()=>{const e=this.view.getCamera().getWorldPosition(new C.Vector3),t=new C.Box3((new C.Vector3).copy(e).subScalar(h),(new C.Vector3).copy(e).addScalar(h)),i=l.slice(),s=this.physics.world.bodies;if(null==s)return[];const n=new C.Box3;for(const e of s.getAll())for(let s=0,o=e.numColliders();s<o;s++){const o=e.collider(s);if(this.physics.isLandscapeNavigationCollider(o))continue;if(o.isSensor()||null!=o.parent().userData&&!0===o.parent().userData.ignoreForNavMesh)continue;const a=e.handle+","+s,r=d.get(a)?.mesh,h=r??se(o);if(h&&(ne(o,h),null!=h)){h.updateMatrixWorld(!0),n.copy(h.geometry.boundingBox).applyMatrix4(h.matrixWorld);const e=n.intersectsBox(t)||!0,s=n.intersectsBox(this.bounds);d.set(a,{pos:o.translation(),mesh:h}),e&&s&&i.push(h)}}return i},g=new C.Box3,m=new Map,b=new Map,w=new Map,v=new Map;let y=!0,x=performance.now(),M=!1;const B=function(e,t){let i=!1;return(async()=>{for(;!i;)await e(),await K(t)})(),()=>{i=!0}}(async()=>{if(M)return;console.time("generate navmesh");const e=new C.Box3,t=f();for(const i of t){const t=m.get(i);!0!==t?.equals(i.position)&&(null!=t&&e.expandByPoint(t),e.expandByObject(i),m.set(i,i.position.clone()))}const i=[],s=new Set;for(const t of this.world.actors){if(!J(t))continue;s.add(t);const n=t.getNavigationAreaModifiers();i.push(...n);const o=oe(n),a=JSON.stringify(n);if(w.get(t)!==a){const i=b.get(t);null!=i&&e.union(i),e.union(o),b.set(t,o),w.set(t,a)}}for(const t of[...b.keys()])s.has(t)||(e.union(b.get(t)),b.delete(t),w.delete(t));const n=e.isEmpty()?[]:r.getTilesForBounds(e.expandByScalar(50));n.length>500&&console.warn("Too many tiles to update. Consider increasing tile size");const o=n;if(0!=o.length){const s=[];for(const i of t)g.setFromObject(i),g.intersectsBox(e)&&s.push(i);let[n,a]=A(s);U&&(n=q(n),a=O(a)),console.timeEnd("generate navmesh");const h=!1;y=!1,await Promise.all(o.map(e=>{x=performance.now();const t=function(e,t,i){const s=(e.recastConfig.walkableRadius+3)*e.recastConfig.cs,n=new C.Vector3(e.navMeshBoundsMin.x+t[0]*e.tcs-s,e.navMeshBoundsMin.y,e.navMeshBoundsMin.z+t[1]*e.tcs-s),o=new C.Vector3(n.x+e.tcs+2*s,e.navMeshBoundsMax.y,n.z+e.tcs+2*s),a=new C.Box3(n,o);return i.filter(e=>oe([e]).intersectsBox(a))}(r,e,i);return r.buildTile(n,a,e,h,t).then(()=>{const t=e[0]+","+e[1];v.set(t,(v.get(t)??0)+1),this.debug})})).then(()=>{this.debug,M=!1})}else M=!1},this.refreshMs??1e4);this.generationDisposed.subscribe(()=>B()),r.onNavMeshUpdate.pipe(N(this.generationDisposed),z(200)).subscribe(()=>{p.clear(),this.debug&&p.drawNavMesh(r.navMesh)}),console.log("Create navmesh with debug",this.debug),p.visible=this.debug,this.object.rotation.set(0,0,0),this.object.updateMatrix(),this.object.updateMatrixWorld(),this.object.parent?.add(p),this.generationDisposed.subscribe(()=>{r?.destroy(),p.removeFromParent(),p.dispose(),this.debugDrawer===p&&(this.debugDrawer=void 0),this.navMesh===r.navMesh&&(this.navMesh=void 0)});const D=performance.now()-c;D>1e3&&console.warn(`NavMesh update took ${D} ms. Consider changing tileSize or other parameter that may affect performance`)}};e([R(),t("design:type",Boolean)],Z.prototype,"debug",void 0),e([R({help:"Generate navigation from live physics and refresh moving geometry. Disables static baked navigation."}),t("design:type",Boolean)],Z.prototype,"dynamic",void 0),e([R(),t("design:type",Number)],Z.prototype,"walkableClimb",void 0),e([R({range:[0,89]}),t("design:type",Number)],Z.prototype,"walkableSlopeAngle",void 0),e([R(),t("design:type",Number)],Z.prototype,"walkableHeight",void 0),e([R({range:[.01,10]}),t("design:type",Number)],Z.prototype,"cellSize",void 0),Z=e([G({icon:"route"})],Z);export default Z;const ee=new Map,te=new WeakMap;function ie(e){if(e.shape instanceof M)return function(e){const t=e.shape;if(t.type!==B.HeightField)throw new Error("The provided collider is not a height field.");let i=!1;const s=t,n=s.heights,o=s.nrows,a=s.ncols,r=s.scale.x,h=s.scale.z,c=s.scale.y,d=o+1,l=new C.PlaneGeometry(h,r,a,o);l.rotateX(-Math.PI/2);const u=l.attributes.position.array;let p=0;for(let e=0;e<d;e++)for(let t=0;t<d;t++)u[p+1]=n[t*d+e]*c,p+=3,0!=u[p+1]&&(i=!0);if(!i){const e=new C.PlaneGeometry(r,h,2,2);return e.rotateX(-Math.PI/2),e}return l}(e);if(e.shape instanceof m)return new C.SphereGeometry(e.shape.radius);if(e.shape instanceof y){const t=e.shape.halfExtents;return new C.BoxGeometry(2*t.x,2*t.y,2*t.z)}if(e.shape instanceof v){const t=function(e){const t=te.get(e);if(void 0!==t)return t;const i=new Uint32Array(e.buffer,e.byteOffset,e.byteLength>>>2);let s=2166136261;for(let e=0;e<i.length;e++)s=Math.imul(s^i[e],16777619)>>>0;return te.set(e,s),s}(e.shape.vertices);let i=ee.get(t);return i||(i=function(e){const t=[];for(let i=0;i<e.length;i+=3)t.push(new C.Vector3(e[i],e[i+1],e[i+2]));const i=(new I).setFromPoints(t),s=[];i.faces.forEach(e=>{const t=e.edge.head().point,i=e.edge.next.head().point,n=e.edge.next.next.head().point;s.push(t.x,t.y,t.z),s.push(i.x,i.y,i.z),s.push(n.x,n.y,n.z)});const n=new C.BufferGeometry;return n.setAttribute("position",new C.Float32BufferAttribute(s,3)),n}(e.shape.vertices),ee.set(t,i)),i}if(e.shape instanceof D){const t=e.shape.vertices,i=e.shape.indices;let s=new C.BufferGeometry;return s.setAttribute("position",new C.Float32BufferAttribute(t,3)),null!=i?s.setIndex(new C.Uint32BufferAttribute(i,1)):s=P.mergeVertices(s),s.computeVertexNormals(),s}if(e.shape instanceof x){const t=e.shape.halfHeight,i=e.shape.radius;return new C.CylinderGeometry(i,i,2*t)}if(e.shape instanceof w){const t=e.shape.halfHeight,i=e.shape.radius;return new C.ConeGeometry(i,2*t)}if(e.shape instanceof b){const t=e.shape.halfHeight,i=e.shape.radius;return new C.CapsuleGeometry(i,2*t)}return console.warn("Unsupported shape",e.shape.type,e),null}function se(e){const t=ie(e);if(null==t)return null;const i=ae,s=new C.Mesh(t,i);return s.geometry.computeBoundingBox(),s}function ne(e,t){const i=e.translation(),s=e.rotation();t.position.set(i.x,i.y,i.z),t.quaternion.set(s.x,s.y,s.z,s.w)}new C.Box3;function oe(e){const t=new C.Box3;for(const i of e)for(const e of i.vertices)t.expandByPoint(new C.Vector3(e[0],i.minHeight,e[2])),t.expandByPoint(new C.Vector3(e[0],i.maxHeight,e[2]));return t}const ae=new C.MeshBasicMaterial({wireframe:!1,color:16711680,side:C.FrontSide});/*
|
|
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 {
|
|
1
|
+
import { Material, Vector3 } from 'three';
|
|
2
2
|
import { type NavigationAreaContributor, type NavigationAreaModifier } from '../../ai/navigation-areas.js';
|
|
3
3
|
import { BaseActor } from '../actor.js';
|
|
4
4
|
import { SplineComponent } from './components/spline-component.js';
|
|
@@ -19,5 +19,5 @@ export declare class WaterSplineVolume extends BaseActor implements NavigationAr
|
|
|
19
19
|
containsWaterPoint(point: Vector3): boolean;
|
|
20
20
|
getNavigationAreaModifiers(): NavigationAreaModifier[];
|
|
21
21
|
}
|
|
22
|
-
export
|
|
22
|
+
export { createWaterSplineSurfaceGeometry } from '../../../navigation/water-spline-geometry.js';
|
|
23
23
|
//# sourceMappingURL=water-spline-volume-actor.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{__decorate as t,__metadata as e}from"tslib";import{BufferGeometry as i,EdgesGeometry as s,Float32BufferAttribute as r,Group as o,DoubleSide as a,LineBasicMaterial as n,LineSegments as l,Mesh as h,MeshBasicMaterial as c,Material as u,Matrix4 as p,Vector3 as d}from"three";import{takeUntil as f}from"rxjs";import{NavigationArea as m}from"../../ai/navigation-areas.js";import{Parameter as g}from"../../../shader/parameter.js";import{Actor as w,BaseActor as y}from"../actor.js";import{ActorComponent as b,Component as S,attach as x}from"../component.js";import{SplineComponent as M}from"./components/spline-component.js";import{WaterPostProcessSettings as z}from"./components/water-post-process-settings.js";const v=1477586;let A=class extends b{constructor(){super(...arguments),this.fallbackMaterial=new c({color:v,transparent:!0,opacity:.65,side:a}),this.surface=new h(new i,this.fallbackMaterial),this.initialized=!1}onInit(){this.initialized=!0,this.surface.name="Water Surface",this.actor.object.add(this.surface),this.actor.spline.changed.pipe(f(this.disposed)).subscribe(()=>this.rebuild()),this.setMaterial(this.actor.surfaceMaterial),this.rebuild()}setMaterial(t){this.surface.material=t??this.fallbackMaterial}rebuild(){if(!this.initialized)return;const t=this.surface.geometry;this.surface.geometry=F(this.actor.spline,this.actor.width,this.actor.surfaceSampleDistance,this.actor.surfaceWidthSubdivisions),this.surface.visible=this.actor.showSurface,t.dispose()}onEndPlay(){this.surface.geometry.dispose(),this.fallbackMaterial.dispose(),this.surface.removeFromParent()}};A=t([S({inEditor:!0})],A);let T=class extends b{constructor(){super(...arguments),this.visual=new o,this.fillMaterial=new c({color:v,transparent:!0,opacity:.18,depthWrite:!1}),this.edgeMaterial=new n({color:3520767}),this.fill=new h(new i,this.fillMaterial),this.edges=new l(new i,this.edgeMaterial),this.initialized=!1}onInit(){this.initialized=!0,this.visual.name="Water Spline Volume Visualization",this.visual.userData.isEditorVisualization=!0,this.fill.frustumCulled=!1,this.edges.frustumCulled=!1,this.visual.add(this.fill,this.edges),this.actor.object.add(this.visual),this.actor.spline.changed.pipe(f(this.disposed)).subscribe(()=>this.rebuild()),this.rebuild()}rebuild(){if(!this.initialized)return;const t=function(t,e,s,o=!1,a=.5){const n=new i,l=D(t,e,s,a);if(l.length<2)return n.setAttribute("position",new r([],3)),n;const h=l.flatMap(t=>[...t.leftTop.toArray(),...t.rightTop.toArray(),...t.leftBottom.toArray(),...t.rightBottom.toArray()]),c=[];for(let t=1;t<l.length;t++){const e=4*(t-1),i=4*t;o||k(c,e,i,i+1,e+1),k(c,e+2,e+3,i+3,i+2),k(c,e,e+2,i+2,i),k(c,e+1,i+1,i+3,e+3)}if(!t.closed){k(c,0,1,3,2);const t=4*(l.length-1);k(c,t,t+2,t+3,t+1)}return n.setAttribute("position",new r(h,3)),n.setIndex(c),n.computeVertexNormals(),n.computeBoundingBox(),n.computeBoundingSphere(),n}(this.actor.spline,this.actor.width,this.actor.depth,this.actor.showSurface,this.actor.surfaceSampleDistance),e=0===t.getAttribute("position").count?new i:new s(t,20),o=this.fill.geometry,a=this.edges.geometry;this.fill.geometry=t,this.edges.geometry=e,o.dispose(),a.dispose(),this.visual.updateMatrixWorld(!0)}onEndPlay(){this.fill.geometry.dispose(),this.edges.geometry.dispose(),this.fillMaterial.dispose(),this.edgeMaterial.dispose(),this.visual.removeFromParent()}};T=t([S({inEditor:!0,editorOnly:!0})],T);let W=class extends y{constructor(){super(...arguments),this.spline=x(M),this.surface=x(A),this.editorVisual=x(T),this.underwater=x(z),this.width=5,this.depth=2,this.showSurface=!0,this.surfaceSampleDistance=1,this.surfaceWidthSubdivisions=1}applySceneParameters(t,e){if(![...e].every(t=>["width","depth","showSurface","surfaceMaterial","surfaceSampleDistance","surfaceWidthSubdivisions"].includes(t)))return!1;const i=e.has("width")?t.width:this.width,s=e.has("depth")?t.depth:this.depth;if("number"!=typeof i||!Number.isFinite(i)||i<=0)return!1;if("number"!=typeof s||!Number.isFinite(s)||s<=0)return!1;if(e.has("showSurface")&&"boolean"!=typeof t.showSurface)return!1;if(e.has("surfaceMaterial")&&null!=t.surfaceMaterial&&!(t.surfaceMaterial instanceof u))return!1;const r=e.has("surfaceSampleDistance")?t.surfaceSampleDistance:this.surfaceSampleDistance,o=e.has("surfaceWidthSubdivisions")?t.surfaceWidthSubdivisions:this.surfaceWidthSubdivisions;return!("number"!=typeof r||!Number.isFinite(r)||r<=0)&&(!("number"!=typeof o||!Number.isFinite(o)||o<1)&&(this.width=i,this.depth=s,e.has("showSurface")&&(this.showSurface=t.showSurface),e.has("surfaceMaterial")&&(this.surfaceMaterial=t.surfaceMaterial,this.surface.setMaterial(this.surfaceMaterial)),this.surfaceSampleDistance=r,this.surfaceWidthSubdivisions=Math.round(o),this.editorVisual.rebuild(),this.surface.rebuild(),!0))}containsWaterPoint(t){this.object.updateWorldMatrix(!0,!1);const e=E.copy(t).applyMatrix4(q.copy(this.object.matrixWorld).invert()),i=D(this.spline,this.width,this.depth,this.surfaceSampleDistance);for(let t=1;t<i.length;t++){const s=i[t-1],r=i[t];if(C(e,s.leftTop,s.rightTop,r.rightTop,r.leftTop,this.depth))return!0}return!1}getNavigationAreaModifiers(){const t=D(this.spline,this.width,this.depth);if(t.length<2)return[];this.object.updateWorldMatrix(!0,!1);const e=this.object.matrixWorld,i=[];for(let s=1;s<t.length;s++){const r=t[s-1],o=t[s],a=[r.leftTop,r.rightTop,o.rightTop,o.leftTop,r.leftBottom,r.rightBottom,o.rightBottom,o.leftBottom].map(t=>t.clone().applyMatrix4(e));i.push({area:m.Water,vertices:a.slice(0,4).map(t=>t.toArray()),minHeight:Math.min(...a.map(t=>t.y)),maxHeight:Math.max(...a.map(t=>t.y))})}return i}};t([g({range:[.01,1e3]}),e("design:type",Number)],W.prototype,"width",void 0),t([g({range:[.01,1e3]}),e("design:type",Number)],W.prototype,"depth",void 0),t([g({group:"Surface"}),e("design:type",Boolean)],W.prototype,"showSurface",void 0),t([g({type:u,group:"Surface",requires:{showSurface:!0},help:"Optional. Uses a translucent debug material when unset."}),e("design:type",u)],W.prototype,"surfaceMaterial",void 0),t([g({range:[.1,100],group:"Surface",requires:{showSurface:!0},help:"Target spacing in world units between sections along the spline."}),e("design:type",Number)],W.prototype,"surfaceSampleDistance",void 0),t([g({range:[1,64],stepSize:1,group:"Surface",requires:{showSurface:!0},help:"Subdivisions across the water. Increase for vertex displacement and detailed bank effects."}),e("design:type",Number)],W.prototype,"surfaceWidthSubdivisions",void 0),W=t([w({typeId:"WaterSplineVolume",icon:"ripple"})],W);export{W as WaterSplineVolume};function D(t,e,i,s=.5){if(t.points.length<2||t.length<=0||e<=0||i<0)return[];const r=Math.min(513,Math.max(2,Math.ceil(t.length/Math.max(.01,s))+1)),o=[],a=Array.from({length:r},(e,i)=>t.samplePositionAtDistance(t.length*i/(r-1),new d)),n=Math.min(.5*e,.25*t.length),l=Array.from({length:r},(e,i)=>{const s=t.length*i/(r-1),o=V(t,s-n,new d),a=V(t,s+n,new d).sub(o).setY(0);return a.lengthSq()<1e-8&&t.sampleTangentAtDistance(s,a).setY(0),a.lengthSq()<1e-8&&a.set(1,0,0),a.normalize(),new d(-a.z,0,a.x)}),h=.5*e,c=Array(r).fill(h),u=Array(r).fill(h);for(let e=0;e<r;e++){if(!t.closed&&(0===e||e===r-1))continue;const i=a[0===e?r-2:e-1],s=a[e],o=a[e===r-1?1:e+1],n=Math.min(h,.6*P(i,s,o)),l=N(i,s,o);l>1e-8?c[e]=n:l<-1e-8&&(u[e]=n)}j(c,a),j(u,a);for(let e=0;e<r;e++){const s=l[e],n=a[e].clone().addScaledVector(s,c[e]),h=a[e].clone().addScaledVector(s,-u[e]);o.push({leftTop:n,rightTop:h,leftBottom:n.clone().addScaledVector(I,i),rightBottom:h.clone().addScaledVector(I,i),flowDirection:t.sampleTangentAtDistance(t.length*e/(r-1),new d).normalize(),distanceAlongSpline:t.length*e/(r-1),surfaceWidth:c[e]+u[e]})}return function(t,e,i){B(t.map(t=>t.leftTop),e),B(t.map(t=>t.rightTop),e);for(const e of t)e.leftBottom.copy(e.leftTop).addScaledVector(I,i),e.rightBottom.copy(e.rightTop).addScaledVector(I,i),e.surfaceWidth=e.leftTop.distanceTo(e.rightTop)}(o,t.closed,i),o}function B(t,e){const i=e?t.length-1:t.length;if(!(i<3))for(let s=0;s<2;s++){const s=t.map(t=>t.clone()),r=e?i:i-1;for(let o=e?0:1;o<r;o++){const e=s[(o-1+i)%i],r=s[(o+1)%i];t[o].lerpVectors(e,r,.5).lerp(s[o],.5)}e&&t[t.length-1].copy(t[0])}}function V(t,e,i){if(!t.closed)return t.samplePositionAtDistance(e,i);const s=(e%t.length+t.length)%t.length;return t.samplePositionAtDistance(s,i)}function j(t,e){for(let i=1;i<t.length;i++)t[i]=Math.min(t[i],t[i-1]+.5*e[i].distanceTo(e[i-1]));for(let i=t.length-2;i>=0;i--)t[i]=Math.min(t[i],t[i+1]+.5*e[i].distanceTo(e[i+1]))}function N(t,e,i){return(e.x-t.x)*(i.z-e.z)-(e.z-t.z)*(i.x-e.x)}function P(t,e,i){const s=Math.hypot(e.x-t.x,e.z-t.z),r=Math.hypot(i.x-e.x,i.z-e.z),o=Math.hypot(i.x-t.x,i.z-t.z),a=Math.abs((e.x-t.x)*(i.z-t.z)-(e.z-t.z)*(i.x-t.x));return s<1e-6||r<1e-6||o<1e-6||a<1e-8?1/0:s*r*o/(2*a)}export function createWaterSplineSurfaceGeometry(t,e,i,s){return F(t,e,i,s)}function F(t,e,s,o){const a=new i,n=D(t,e,0,s),l=Math.max(1,Math.round(o))+1;if(n.length<2)return a.setAttribute("position",new r([],3)),a;const h=[],c=[],u=[],p=[],d=[];for(const e of n)for(let i=0;i<l;i++){const s=i/(l-1);h.push(...e.leftTop.clone().lerp(e.rightTop,s).toArray()),c.push(s,t.length>0?e.distanceAlongSpline/t.length:0),u.push(...e.flowDirection.toArray()),p.push(Math.min(s,1-s)*e.surfaceWidth),d.push(e.distanceAlongSpline)}const f=[];for(let t=1;t<n.length;t++)for(let e=1;e<l;e++){const i=(t-1)*l+e-1,s=t*l+e-1;k(f,i,s,s+1,i+1)}return a.setAttribute("position",new r(h,3)),a.setAttribute("uv",new r(c,2)),a.setAttribute("waterFlowDirection",new r(u,3)),a.setAttribute("waterDistanceFromBank",new r(p,1)),a.setAttribute("waterDistanceAlongSpline",new r(d,1)),a.setIndex(f),a.computeVertexNormals(),a.computeBoundingBox(),a.computeBoundingSphere(),a}function k(t,e,i,s,r){t.push(e,i,s,e,s,r)}const I=new d(0,-1,0),q=new p,E=new d;function C(t,e,i,s,r,o){const a=H(t,e,i,s)??H(t,e,s,r);return null!=a&&t.y<=a&&t.y>=a-o}function H(t,e,i,s){const r=(i.z-s.z)*(e.x-s.x)+(s.x-i.x)*(e.z-s.z);if(Math.abs(r)<1e-8)return null;const o=((i.z-s.z)*(t.x-s.x)+(s.x-i.x)*(t.z-s.z))/r,a=((s.z-e.z)*(t.x-s.x)+(e.x-s.x)*(t.z-s.z))/r,n=1-o-a;return o>=0&&a>=0&&n>=0?o*e.y+a*i.y+n*s.y:null}/*
|
|
1
|
+
import{__decorate as e,__metadata as t}from"tslib";import{createCrossSections as i,createSurfaceGeometry as s,createVolumeGeometry as r}from"../../../navigation/water-spline-geometry.js";import{BufferGeometry as a,EdgesGeometry as o,Group as n,DoubleSide as h,LineBasicMaterial as l,LineSegments as u,Mesh as c,MeshBasicMaterial as p,Material as d,Matrix4 as f,Vector3 as m}from"three";import{takeUntil as g}from"rxjs";import{NavigationArea as b}from"../../ai/navigation-areas.js";import{Parameter as y}from"../../../shader/parameter.js";import{Actor as w,BaseActor as S}from"../actor.js";import{ActorComponent as v,Component as M,attach as x}from"../component.js";import{SplineComponent as z}from"./components/spline-component.js";import{WaterPostProcessSettings as W}from"./components/water-post-process-settings.js";const j=1477586;let D=class extends v{constructor(){super(...arguments),this.fallbackMaterial=new p({color:j,transparent:!0,opacity:.65,side:h}),this.surface=new c(new a,this.fallbackMaterial),this.initialized=!1}onInit(){this.initialized=!0,this.surface.name="Water Surface",this.actor.object.add(this.surface),this.actor.spline.changed.pipe(g(this.disposed)).subscribe(()=>this.rebuild()),this.setMaterial(this.actor.surfaceMaterial),this.rebuild()}setMaterial(e){this.surface.material=e??this.fallbackMaterial}rebuild(){if(!this.initialized)return;const e=this.surface.geometry;this.surface.geometry=s(this.actor.spline,this.actor.width,this.actor.surfaceSampleDistance,this.actor.surfaceWidthSubdivisions),this.surface.visible=this.actor.showSurface,e.dispose()}onEndPlay(){this.surface.geometry.dispose(),this.fallbackMaterial.dispose(),this.surface.removeFromParent()}};D=e([M({inEditor:!0})],D);let N=class extends v{constructor(){super(...arguments),this.visual=new n,this.fillMaterial=new p({color:j,transparent:!0,opacity:.18,depthWrite:!1}),this.edgeMaterial=new l({color:3520767}),this.fill=new c(new a,this.fillMaterial),this.edges=new u(new a,this.edgeMaterial),this.initialized=!1}onInit(){this.initialized=!0,this.visual.name="Water Spline Volume Visualization",this.visual.userData.isEditorVisualization=!0,this.fill.frustumCulled=!1,this.edges.frustumCulled=!1,this.visual.add(this.fill,this.edges),this.actor.object.add(this.visual),this.actor.spline.changed.pipe(g(this.disposed)).subscribe(()=>this.rebuild()),this.rebuild()}rebuild(){if(!this.initialized)return;const e=r(this.actor.spline,this.actor.width,this.actor.depth,this.actor.showSurface,this.actor.surfaceSampleDistance),t=0===e.getAttribute("position").count?new a:new o(e,20),i=this.fill.geometry,s=this.edges.geometry;this.fill.geometry=e,this.edges.geometry=t,i.dispose(),s.dispose(),this.visual.updateMatrixWorld(!0)}onEndPlay(){this.fill.geometry.dispose(),this.edges.geometry.dispose(),this.fillMaterial.dispose(),this.edgeMaterial.dispose(),this.visual.removeFromParent()}};N=e([M({inEditor:!0,editorOnly:!0})],N);let T=class extends S{constructor(){super(...arguments),this.spline=x(z),this.surface=x(D),this.editorVisual=x(N),this.underwater=x(W),this.width=5,this.depth=2,this.showSurface=!0,this.surfaceSampleDistance=1,this.surfaceWidthSubdivisions=1}applySceneParameters(e,t){if(![...t].every(e=>["width","depth","showSurface","surfaceMaterial","surfaceSampleDistance","surfaceWidthSubdivisions"].includes(e)))return!1;const i=t.has("width")?e.width:this.width,s=t.has("depth")?e.depth:this.depth;if("number"!=typeof i||!Number.isFinite(i)||i<=0)return!1;if("number"!=typeof s||!Number.isFinite(s)||s<=0)return!1;if(t.has("showSurface")&&"boolean"!=typeof e.showSurface)return!1;if(t.has("surfaceMaterial")&&null!=e.surfaceMaterial&&!(e.surfaceMaterial instanceof d))return!1;const r=t.has("surfaceSampleDistance")?e.surfaceSampleDistance:this.surfaceSampleDistance,a=t.has("surfaceWidthSubdivisions")?e.surfaceWidthSubdivisions:this.surfaceWidthSubdivisions;return!("number"!=typeof r||!Number.isFinite(r)||r<=0)&&(!("number"!=typeof a||!Number.isFinite(a)||a<1)&&(this.width=i,this.depth=s,t.has("showSurface")&&(this.showSurface=e.showSurface),t.has("surfaceMaterial")&&(this.surfaceMaterial=e.surfaceMaterial,this.surface.setMaterial(this.surfaceMaterial)),this.surfaceSampleDistance=r,this.surfaceWidthSubdivisions=Math.round(a),this.editorVisual.rebuild(),this.surface.rebuild(),!0))}containsWaterPoint(e){this.object.updateWorldMatrix(!0,!1);const t=P.copy(e).applyMatrix4(F.copy(this.object.matrixWorld).invert()),s=i(this.spline,this.width,this.depth,this.surfaceSampleDistance);for(let e=1;e<s.length;e++){const i=s[e-1],r=s[e];if(V(t,i.leftTop,i.rightTop,r.rightTop,r.leftTop,this.depth))return!0}return!1}getNavigationAreaModifiers(){const e=i(this.spline,this.width,this.depth);if(e.length<2)return[];this.object.updateWorldMatrix(!0,!1);const t=this.object.matrixWorld,s=[];for(let i=1;i<e.length;i++){const r=e[i-1],a=e[i],o=[r.leftTop,r.rightTop,a.rightTop,a.leftTop,r.leftBottom,r.rightBottom,a.rightBottom,a.leftBottom].map(e=>e.clone().applyMatrix4(t));s.push({area:b.Water,vertices:o.slice(0,4).map(e=>e.toArray()),minHeight:Math.min(...o.map(e=>e.y)),maxHeight:Math.max(...o.map(e=>e.y))})}return s}};e([y({range:[.01,1e3]}),t("design:type",Number)],T.prototype,"width",void 0),e([y({range:[.01,1e3]}),t("design:type",Number)],T.prototype,"depth",void 0),e([y({group:"Surface"}),t("design:type",Boolean)],T.prototype,"showSurface",void 0),e([y({type:d,group:"Surface",requires:{showSurface:!0},help:"Optional. Uses a translucent debug material when unset."}),t("design:type",d)],T.prototype,"surfaceMaterial",void 0),e([y({range:[.1,100],group:"Surface",requires:{showSurface:!0},help:"Target spacing in world units between sections along the spline."}),t("design:type",Number)],T.prototype,"surfaceSampleDistance",void 0),e([y({range:[1,64],stepSize:1,group:"Surface",requires:{showSurface:!0},help:"Subdivisions across the water. Increase for vertex displacement and detailed bank effects."}),t("design:type",Number)],T.prototype,"surfaceWidthSubdivisions",void 0),T=e([w({typeId:"WaterSplineVolume",icon:"ripple"})],T);export{T as WaterSplineVolume};export{createWaterSplineSurfaceGeometry}from"../../../navigation/water-spline-geometry.js";const F=new f,P=new m;function V(e,t,i,s,r,a){const o=k(e,t,i,s)??k(e,t,s,r);return null!=o&&e.y<=o&&e.y>=o-a}function k(e,t,i,s){const r=(i.z-s.z)*(t.x-s.x)+(s.x-i.x)*(t.z-s.z);if(Math.abs(r)<1e-8)return null;const a=((i.z-s.z)*(e.x-s.x)+(s.x-i.x)*(e.z-s.z))/r,o=((s.z-t.z)*(e.x-s.x)+(t.x-s.x)*(e.z-s.z))/r,n=1-a-o;return a>=0&&o>=0&&n>=0?a*t.y+o*i.y+n*s.y:null}/*
|
|
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 o,TrianglesArray as r,VerticesArray as l,allocCompactHeightfield as a,allocContourSet as s,allocHeightfield as n,allocPolyMesh as
|
|
1
|
+
import{NavMeshCreateParams as e,Raw as t,RecastBuildContext as i,TriangleAreasArray as o,TrianglesArray as r,VerticesArray as l,allocCompactHeightfield as a,allocContourSet as s,allocHeightfield as n,allocPolyMesh as d,allocPolyMeshDetail as c,buildCompactHeightfield as h,buildContours as g,buildDistanceField as u,buildPolyMesh as m,buildPolyMeshDetail as f,buildRegions as b,calcGridSize as S,cloneRcConfig as p,createHeightfield as w,createNavMeshData as R,createRcConfig as C,erodeWalkableArea as M,filterLedgeSpans as y,filterLowHangingWalkableObstacles as _,filterWalkableLowHeightSpans as x,freeCompactHeightfield as A,freeContourSet as k,freeHeightfield as v,freePolyMesh as z,freePolyMeshDetail as E,FloatArray as T,markConvexPolyArea as B,markWalkableTriangles as H,rasterizeTriangles as O,vec3 as P}from"@recast-navigation/core";import{dtIlog2 as G,dtNextPow2 as L}from"@recast-navigation/generators";import*as D from"three";import{NavigationArea as F,NavigationTraversalFlag as W}from"./navigation-areas.js";export const buildConfig=({recastConfig:e,navMeshBounds:[t,i]})=>{const o=C(e),r=S(t,i,o.cs);o.width=r.width,o.height=r.height,o.minRegionArea=o.minRegionArea*o.minRegionArea,o.mergeRegionArea=o.mergeRegionArea*o.mergeRegionArea,o.tileSize=Math.floor(o.tileSize),o.borderSize=o.walkableRadius+3,o.width=o.tileSize+2*o.borderSize,o.height=o.tileSize+2*o.borderSize,o.detailSampleDist=o.detailSampleDist<.9?0:o.cs*o.detailSampleDist,o.detailSampleMaxError=o.ch*o.detailSampleMaxError;const l=Math.floor(o.tileSize),a=Math.floor((r.width+l-1)/l),s=Math.floor((r.height+l-1)/l),n=o.tileSize*o.cs,d=P.fromArray(t);let c=Math.min(Math.floor(G(L(a*s))),14);c>14&&(c=14);const h=22-c;return{config:o,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:P,recastConfig:G,tileX:L,tileY:$,tileBoundsMin:V,tileBoundsMax:I,keepIntermediates:K,areaModifiers:X=[]})=>{const Y=new i,j=S,N=S.length/3,U=new l;U.copy(j);const q=C.length/3,J=[()=>U.destroy()],Q={tileX:L,tileY:$},Z=()=>{for(const e of J)e();J.length=0,K||(Q.compactHeightfield&&A(Q.compactHeightfield),Q.heightfield&&v(Q.heightfield),Q.contourSet&&k(Q.contourSet))},ee=e=>(Y.log(t.Module.RC_LOG_ERROR,e),Z(),{success:!1,error:e,tileIntermediates:Q,buildContext:Y}),{config:te}=buildConfig({recastConfig:G,navMeshBounds:P}),ie=p(te);J.push(()=>t.Module.destroy(te),()=>t.Module.destroy(ie));const oe=[...V],re=[...I];oe[0]-=ie.borderSize*ie.cs,oe[2]-=ie.borderSize*ie.cs,re[0]+=ie.borderSize*ie.cs,re[2]+=ie.borderSize*ie.cs,ie.set_bmin(0,oe[0]),ie.set_bmin(1,oe[1]),ie.set_bmin(2,oe[2]),ie.set_bmax(0,re[0]),ie.set_bmax(1,re[1]),ie.set_bmax(2,re[2]),Y.resetTimers(),Y.startTimer(t.Module.RC_TIMER_TOTAL),Y.log(t.Module.RC_LOG_PROGRESS,`Building tile at x: ${L}, y: ${$}`),Y.log(t.Module.RC_LOG_PROGRESS,` - ${te.width} x ${te.height} cells`),Y.log(t.Module.RC_LOG_PROGRESS,` - ${N/1e3}fK verts, ${q/1e3}K tris`);const le=n();if(Q.heightfield=le,!w(Y,le,ie.width,ie.height,oe,re,ie.cs,ie.ch))return ee("Could not create heightfield");const ae=((e,t,i)=>{const o=[],r=new D.Vector3,l=new D.Vector3,a=new D.Vector3,s=new D.Triangle;for(let n=0;n<t.length;n+=3){const d=t[n],c=t[n+1],h=t[n+2];r.fromArray(e,3*d),l.fromArray(e,3*c),a.fromArray(e,3*h),s.set(r,l,a),s.intersectsBox(i)&&o.push(d,c,h)}return o})(S,C,new D.Box3(new D.Vector3(oe[0],oe[1],oe[2]),new D.Vector3(re[0],re[1],re[2]))),se=ae.length/3,ne=new r;J.push(()=>ne.destroy()),ne.copy(ae);const de=new o;de.resize(se),H(Y,ie.walkableSlopeAngle,U,N,ne,se,de);const ce=O(Y,U,N,ne,de,se,le,ie.walkableClimb);if(de.destroy(),!ce)return ee("Could not rasterize triangles");_(Y,ie.walkableClimb,le),y(Y,ie.walkableHeight,ie.walkableClimb,le),x(Y,ie.walkableHeight,le);const he=a();if(Q.compactHeightfield=he,!h(Y,ie.walkableHeight,ie.walkableClimb,le,he))return ee("Could not build compact heightfield");if(K||(v(Q.heightfield),Q.heightfield=void 0),!M(Y,ie.walkableRadius,he))return ee("Could not erode walkable area");for(const e of X){if(e.vertices.length<3)continue;const t=new T;t.copy(e.vertices.flat()),B(Y,t,e.vertices.length,e.minHeight,e.maxHeight,e.area,he),t.destroy()}if(!u(Y,he))return ee("Failed to build distance field");if(!b(Y,he,ie.borderSize,ie.minRegionArea,ie.mergeRegionArea))return ee("Failed to build regions");const ge=s();if(Q.contourSet=ge,!g(Y,he,ie.maxSimplificationError,ie.maxEdgeLen,ge,t.Module.RC_CONTOUR_TESS_WALL_EDGES))return ee("Failed to create contours");const ue=d();if(J.push(()=>z(ue)),!m(Y,ge,ie.maxVertsPerPoly,ue))return ee("Failed to triangulate contours");if(0===ue.npolys())return Z(),{success:!0,tileIntermediates:Q,buildContext:Y};const me=c();if(J.push(()=>E(me)),!f(Y,ue,he,ie.detailSampleDist,ie.detailSampleMaxError,me))return ee("Failed to build detail mesh");K||(A(he),Q.compactHeightfield=void 0,k(ge),Q.contourSet=void 0);for(let e=0;e<ue.npolys();e++){ue.areas(e)==t.Recast.WALKABLE_AREA&&ue.setAreas(e,0);const i=ue.areas(e);i==F.Ground&&ue.setFlags(e,W.Walk),i==F.Water&&ue.setFlags(e,W.Swim)}const fe=new e;J.push(()=>t.Module.destroy(fe.raw)),fe.setPolyMeshCreateParams(ue),fe.setPolyMeshDetailCreateParams(me),fe.setWalkableHeight(ie.walkableHeight),fe.setWalkableRadius(ie.walkableRadius),fe.setWalkableClimb(ie.walkableClimb),fe.setCellSize(ie.cs),fe.setCellHeight(ie.ch),fe.setBuildBvTree(!0),fe.setTileX(L),fe.setTileY($);const be=R(fe);return be.success?(Y.log(t.Module.RC_LOG_PROGRESS,`>> Polymesh: ${ue.nverts()} vertices ${ue.npolys()} polygons`),Z(),{success:!0,data:be.navMeshData,tileIntermediates:Q,buildContext:Y}):ee("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
|
*/
|