@hology/core 0.0.244 → 0.0.246
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/vfx/gpu-sprite-compiler.js +1 -1
- package/dist/effects/vfx/gpu-sprite-runtime.d.ts +18 -1
- package/dist/effects/vfx/gpu-sprite-runtime.js +1 -1
- package/dist/effects/vfx/gpu-sprite-template.d.ts +1 -0
- package/dist/effects/vfx/gpu-sprite-template.js +1 -1
- package/dist/effects/vfx/gpu-sprite-world-runtime.d.ts +2 -1
- package/dist/effects/vfx/gpu-sprite-world-runtime.js +1 -1
- package/dist/effects/vfx/gpu-vfx-world-service.d.ts +7 -1
- package/dist/effects/vfx/gpu-vfx-world-service.js +1 -1
- package/dist/effects/vfx/initializsers.d.ts +15 -0
- package/dist/effects/vfx/initializsers.js +1 -1
- package/dist/effects/vfx/vfx-actor.d.ts +2 -0
- package/dist/effects/vfx/vfx-actor.js +1 -1
- package/dist/effects/vfx/vfx-asset.d.ts +7 -3
- package/dist/effects/vfx/vfx-asset.js +1 -1
- package/dist/effects/vfx/vfx-binding-runtime.d.ts +1 -1
- package/dist/effects/vfx/vfx-binding-runtime.js +1 -1
- package/dist/effects/vfx/vfx-defs.d.ts +53 -3
- package/dist/effects/vfx/vfx-defs.js +1 -1
- package/dist/effects/vfx/vfx-input-runtime.d.ts +2 -2
- package/dist/effects/vfx/vfx-input-runtime.js +1 -1
- package/dist/effects/vfx/vfx-materializer.js +1 -1
- package/dist/effects/vfx/vfx-mesh-sampler.d.ts +25 -0
- package/dist/effects/vfx/vfx-mesh-sampler.js +4 -0
- package/dist/rendering/node-material-webgpu-resolver.js +1 -1
- package/dist/scene/materializer.d.ts +3 -0
- package/dist/scene/materializer.js +1 -1
- package/dist/scene/storage/packed-json.js +1 -1
- package/dist/test/gpu-sprite-compiler.test.js +1 -1
- package/dist/test/packed-json.test.js +1 -1
- package/dist/test/vfx-mesh-sampler.test.d.ts +2 -0
- package/dist/test/vfx-mesh-sampler.test.js +4 -0
- package/package.json +2 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -30,6 +30,7 @@ export type GpuSpriteTemplateBuildOptions = {
|
|
|
30
30
|
resolveCustomMaterial?: (output: import('./vfx-asset.js').SpriteOutput | import('./vfx-asset.js').MeshOutput | import('./vfx-asset.js').DecalOutput | import('./vfx-asset.js').ShapeOutput) => Promise<NodeShaderMaterial | null>;
|
|
31
31
|
/** Asset-system revision/hash when an asset object may be mutated in place. */
|
|
32
32
|
revision?: string | number;
|
|
33
|
+
meshInputs?: ReadonlyMap<string, THREE.Object3D>;
|
|
33
34
|
};
|
|
34
35
|
/** Caches immutable compiled asset templates by asset object revision. */
|
|
35
36
|
export declare class GpuSpriteTemplateCache {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as e from"three";import{compileGpuSpriteEmitters as t,materializeGpuSpriteRenderClasses as i}from"./gpu-sprite-compiler.js";import{getCompiledGpuRenderClassMaterial as
|
|
1
|
+
import*as e from"three";import{compileGpuSpriteEmitters as t,materializeGpuSpriteRenderClasses as i}from"./gpu-sprite-compiler.js";import{getCompiledGpuRenderClassMaterial as s}from"./gpu-sprite-runtime.js";import{VfxMeshSampler as a}from"./vfx-mesh-sampler.js";export class GpuSpriteTemplateCache{constructor(){this.entries=new WeakMap,this.builds=0,this.hits=0,this.invalidations=0}resolve(e,t){const i=this.entries.get(e);if(null!=i&&i.revision===t.revision)return this.hits+=1,i.result;null!=i&&(this.invalidations+=1),this.builds+=1;const s=buildGpuSpriteTemplate(e,t).catch(t=>{throw this.entries.get(e)?.result===s&&this.entries.delete(e),t});return this.entries.set(e,{revision:t.revision,result:s}),s}invalidate(e){this.entries.delete(e)&&(this.invalidations+=1)}clear(){this.entries=new WeakMap,this.invalidations+=1}getStats(){return{builds:this.builds,hits:this.hits,invalidations:this.invalidations}}}export async function buildGpuSpriteTemplate(r,n){const o=t(r,[new e.Vector3]);if(!o.compatibility.compatible||0===o.emitters.length)return{template:null,compatibility:o.compatibility,reasons:o.compatibility.issues.map(e=>e.message)};try{const e=await i(null==n.revision?o.renderClasses:o.renderClasses.map(e=>({...e,key:`${n.revision}:${e.key}`})),n.assets,n.resolveCustomMaterial,n.renderClassCache);for(const e of o.emitters){const t=null==e.meshSamplerInputId?void 0:n.meshInputs?.get(e.meshSamplerInputId);if(null==t&&null==e.meshSamplerAssetId)continue;if(null==t&&null==n.assets.getMesh)throw new Error("GPU mesh position sampling requires a mesh asset provider");let i=null==t?null:new a(t,!1);if(!0!==i?.valid&&null!=e.meshSamplerAssetId){if(null==n.assets.getMesh)throw new Error("GPU mesh position sampling requires a mesh asset provider");i=new a(await n.assets.getMesh(e.meshSamplerAssetId),!0)}if(!0!==i?.valid)throw new Error(`Mesh sampler source "${e.meshSamplerAssetId??e.meshSamplerInputId}" has no supported static geometry`);e.meshSampler=i}for(const t of e.renderClasses)null!=t.material&&s(t);const t=o.emitters.reduce((e,t)=>e+t.capacity,0),l=o.emitters.map(e=>"continuous"===e.rate.type?e.rate.duration<0?Number.POSITIVE_INFINITY:e.rate.delay+e.rate.duration+e.life:e.rate.delay+e.life),c=e=>{const t=o.emitters[e].parentEmitter;return l[e]+(null==t?0:c(t))},p=o.emitters.reduce((e,t,i)=>Math.max(e,c(i)),0);return{template:{assetId:r.id,emitters:o.emitters,renderClasses:e.renderClasses,compatibility:o.compatibility,reasons:e.unsupported,particleCapacity:t,duration:p,inputBindings:o.inputBindings},compatibility:o.compatibility,reasons:e.unsupported}}catch(e){const t=e instanceof Error?e.message:String(e);return{template:null,compatibility:{compatible:!1,issues:[...o.compatibility.issues,{severity:"error",code:"material-graph",emitter:"material",message:t}]},reasons:[`GPU material compilation failed: ${t}`]}}}export function instantiateGpuSpriteTemplate(e,t){return t.flatMap((t,i)=>e.emitters.map(s=>({...s,parentEmitter:null==s.parentEmitter?void 0:i*e.emitters.length+s.parentEmitter,position:!0===s.localSpace||null!=s.parentEmitter?s.position.clone():s.position.clone().add(t),velocityMin:s.velocityMin.clone(),velocityMax:s.velocityMax.clone(),force:s.force.clone(),randomDrift:s.randomDrift.clone(),moveToTarget:s.moveToTarget.clone(),colorStart:s.colorStart.clone(),colorEnd:s.colorEnd.clone(),meshRotation:s.meshRotation.clone(),meshRotationEuler:s.meshRotationEuler.clone(),meshRotationStep:s.meshRotationStep.clone(),randomMeshRotationAxis:s.randomMeshRotationAxis,radialVelocityDirection:s.radialVelocityDirection.clone(),radialVelocitySpeed:s.radialVelocitySpeed,radialVelocitySpread:s.radialVelocitySpread,hasRadialVelocity:s.hasRadialVelocity,attractionTarget:s.attractionTarget.clone(),attractionForce:s.attractionForce,attractionRadius:s.attractionRadius,hasAttraction:s.hasAttraction,alignRotationAxis:s.alignRotationAxis.clone(),alignRotationMode:s.alignRotationMode,alignRotationCenter:s.alignRotationCenter.clone(),alignRotationOffset:s.alignRotationOffset.clone(),vortexAxis:s.vortexAxis.clone(),vortexAmount:s.vortexAmount,hasVortex:s.hasVortex,disperseDistance:s.disperseDistance,dispersePlane:s.dispersePlane,disperseEasing:s.disperseEasing,hasDisperse:s.hasDisperse,curveEasing:s.curveEasing.clone(),meshScaleStart:s.meshScaleStart.clone(),meshScaleEnd:s.meshScaleEnd.clone(),spawnZoneA:s.spawnZoneA?.clone(),spawnZoneB:s.spawnZoneB?.clone(),scaleStart:[...s.scaleStart],scaleEnd:[...s.scaleEnd]})))}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -4,12 +4,13 @@ import type { VfxAsset } from '../../scene/model.js';
|
|
|
4
4
|
import type { VfxAssetProvider } from './vfx-materializer.js';
|
|
5
5
|
import type { GpuSpriteRenderClassCache } from './gpu-sprite-compiler.js';
|
|
6
6
|
import { GpuSpriteRuntime } from './gpu-sprite-runtime.js';
|
|
7
|
-
import { GpuSpriteTemplateCache } from './gpu-sprite-template.js';
|
|
7
|
+
import { GpuSpriteTemplateCache, type GpuSpriteTemplate } from './gpu-sprite-template.js';
|
|
8
8
|
import { GpuVfxScheduler, type GpuVfxInstanceHandle } from './gpu-vfx-scheduler.js';
|
|
9
9
|
export type GpuSpriteWorldTemplateSource = Readonly<{
|
|
10
10
|
asset: VfxAsset;
|
|
11
11
|
maximumInstances: number;
|
|
12
12
|
revision?: string | number;
|
|
13
|
+
preparedTemplate?: GpuSpriteTemplate;
|
|
13
14
|
}>;
|
|
14
15
|
export type GpuSpriteWorldRuntimeOptions = {
|
|
15
16
|
templates: readonly GpuSpriteWorldTemplateSource[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as e from"three";import{calculateGpuSpriteOptionalStateLayout as t,GPU_SPRITE_BASE_COMMAND_BYTES as s,GPU_SPRITE_EMITTER_BYTES as i,GPU_SPRITE_TRANSFORM_COMMAND_BYTES as r,GpuSpriteRuntime as n}from"./gpu-sprite-runtime.js";import{GpuSpriteTemplateCache as
|
|
1
|
+
import*as e from"three";import{calculateGpuSpriteOptionalStateLayout as t,GPU_SPRITE_BASE_COMMAND_BYTES as s,GPU_SPRITE_EMITTER_BYTES as i,GPU_SPRITE_TRANSFORM_COMMAND_BYTES as r,GpuSpriteRuntime as n}from"./gpu-sprite-runtime.js";import{GpuSpriteTemplateCache as a,instantiateGpuSpriteTemplate as l}from"./gpu-sprite-template.js";import{GpuVfxScheduler as o}from"./gpu-vfx-scheduler.js";export class GpuSpriteWorldRuntime{constructor(t,s,i){this.pools=i,this.activeByHandleSlot=[],this.activePages=new Set,this.positionScratch=new e.Vector3,this.activePagesDirty=!1,this.runtime=t,this.scheduler=s,this.templateCount=i.length;for(let e=0;e<t.emitters.length;e++)t.setEmitterActive(e,!1);t.setActivePages([])}static async create(h){if(0===h.templates.length)return null;const c=h.templateCache??new a,m=await Promise.all(h.templates.map(e=>null!=e.preparedTemplate?Promise.resolve({template:e.preparedTemplate,compatibility:e.preparedTemplate.compatibility,reasons:e.preparedTemplate.reasons}):c.resolve(e.asset,{assets:h.assets,renderClassCache:h.renderClassCache,resolveCustomMaterial:h.resolveCustomMaterial,revision:e.revision})));if(m.some(e=>null==e.template))return null;const u=m.map(e=>e.template),p=Math.max(1,Math.floor(h.particlePageSize??256)),d=[],f=new Map,g=[],S=[];let I=0;for(let t=0;t<u.length;t++){const s=u[t],i=h.templates[t],r=Math.max(1,Math.floor(i.maximumInstances)),n=s.renderClasses.map(e=>{let t=f.get(e);return null==t&&(t=d.length,f.set(e,t),d.push(e)),t}),a=g.length,o=new e.Vector3,c=l(s,new Array(r).fill(o));for(const e of c)e.renderClass=n[e.renderClass??0]??0,null!=e.parentEmitter&&(e.parentEmitter+=a),g.push(e);S.push({template:s,baseEmitterIndex:a,maximumInstances:r,freeSlots:Array.from({length:r},(e,t)=>r-t-1),emitterCapacities:s.emitters.map(e=>e.capacity),emitterIndexScratch:new Array(s.emitters.length).fill(0),emitterPositionOffsets:s.emitters.map(e=>e.position.clone()),maximumParticleLife:s.emitters.reduce((e,t)=>Math.max(e,t.life),0)}),I+=Math.ceil(s.particleCapacity/p)*p*r}const y=g.reduce((e,t)=>e+t.capacity,0),v=d.some(e=>null!=e.meshVertices),x=d.some(e=>!0===e.localSpace&&null==e.trail),E=g.some(e=>!0===e.localSpace&&null!=d[e.renderClass??0]?.trail),P=g.length*(E?r:s),C=v||g.some(e=>1!==e.meshScaleStart.x||1!==e.meshScaleStart.y||1!==e.meshScaleStart.z||1!==e.meshScaleEnd.x||1!==e.meshScaleEnd.y||1!==e.meshScaleEnd.z),T=g.reduce((e,t)=>{const s=d[t.renderClass??0]?.trail;return e+(null==s?0:t.capacity*(s.historyLength+1))},0),b=t({capacity:I,particleTransforms:C,vortex:g.some(e=>e.hasVortex),disperse:g.some(e=>e.hasDisperse),collisionEvents:g.some(e=>2===e.childSpawnEvent),trailHistoryEntries:T,meshSamplerEntries:g.reduce((e,t)=>{const s=t.meshSampler;return e+(null==s?0:4*s.vertexCount+2*s.triangleCount+4*s.boneCount)},0)});if(16*I*4+4*y+4*I+4*I*4+g.length*i+Math.max(s,P)+160+16*d.length*2+d.reduce((e,t)=>e+(t.meshVertices?.byteLength??0),0)+b.bytes+(v||x?16*g.length*4:0)>(h.maximumStorageBytes??Number.POSITIVE_INFINITY))return null;if(null!=h.device){const e=h.device.limits.maxStorageBufferBindingSize;if([16*I*4,4*g.reduce((e,t)=>e+t.capacity,0),4*I,4*I*4,g.length*i,Math.max(s,P),...v||x?[16*g.length*4]:[],...b.bytes>0?[b.bytes]:[]].some(t=>t>e))return null;if(7+(b.bytes>0?1:0)>h.device.limits.maxStorageBuffersPerShaderStage)return null}const B=new n(g,d,void 0,{pageSize:p,capacity:I,aliveCountReadback:h.aliveCountReadback});return new GpuSpriteWorldRuntime(B,new o({pageSize:p,maximumParticles:I}),S)}spawn(e,t){const s=this.pools[e],i=s?.freeSlots.pop();if(null==s||null==i)return null;const r=this.scheduler.createInstance({particleCapacity:s.template.particleCapacity,duration:s.template.duration});if(null==r)return s.freeSlots.push(i),null;const n=this.scheduler.arena.get(r).pages,a={handle:r,templateIndex:e,pages:n,logicalSlot:i,stoppingDeadline:Number.POSITIVE_INFINITY};this.activeByHandleSlot[r.slot]=a;for(const e of n)this.activePages.add(e);this.activePagesDirty=!0;const l=s.template.emitters.length;for(let e=0;e<l;e++){const r=s.baseEmitterIndex+i*l+e;s.emitterIndexScratch[e]=r;const n=this.runtime.emitters[r];this.positionScratch.copy(s.emitterPositionOffsets[e]),!0!==n.localSpace&&null==n.parentEmitter&&this.positionScratch.add(t),this.runtime.queueEmitterState(r,this.positionScratch,!0,!0)}return this.runtime.mapParticlePages(n,s.emitterIndexScratch,s.emitterCapacities),this.scheduler.play(r),a}destroy(e){const t=this.activeByHandleSlot[e.handle.slot];if(null==t||t.handle.generation!==e.handle.generation)return!1;const s=this.pools[t.templateIndex],i=s.template.emitters.length;for(let e=0;e<i;e++){const r=s.baseEmitterIndex+t.logicalSlot*i+e;this.runtime.queueEmitterState(r,this.runtime.emitters[r].position,!1)}for(const e of t.pages)this.activePages.delete(e);return this.activePagesDirty=!0,s.freeSlots.push(t.logicalSlot),this.activeByHandleSlot[e.handle.slot]=null,this.scheduler.destroyInstance(e.handle)}setPosition(e,t){const s=this.resolve(e);if(null==s)return!1;const i=this.pools[s.templateIndex];for(let e=0;e<i.template.emitters.length;e++){const r=this.emitterIndex(s,i,e);!0!==this.runtime.emitters[r].localSpace&&null==this.runtime.emitters[r].parentEmitter&&(this.positionScratch.copy(t).add(i.emitterPositionOffsets[e]),this.runtime.queueEmitterState(r,this.positionScratch,!0))}return!0}setTransform(e,t){const s=this.resolve(e);if(null==s)return!1;const i=this.pools[s.templateIndex];for(let e=0;e<i.template.emitters.length;e++){const r=this.emitterIndex(s,i,e),n=this.runtime.emitters[r];this.runtime.setEmitterTransform(r,t),!0!==n.localSpace&&null==n.parentEmitter&&(this.positionScratch.copy(i.emitterPositionOffsets[e]).applyMatrix4(t),this.runtime.queueEmitterState(r,this.positionScratch,!0))}return!0}pause(e){const t=this.resolve(e);return!(null==t||!this.scheduler.pause(t.handle))&&(this.forEachEmitter(t,e=>this.runtime.queueEmitterPaused(e,!0)),!0)}play(e){const t=this.resolve(e);return!(null==t||!this.scheduler.play(t.handle))&&(this.forEachEmitter(t,e=>this.runtime.queueEmitterPaused(e,!1)),!0)}restart(e){const t=this.resolve(e);return!(null==t||!this.scheduler.restart(t.handle))&&(t.stoppingDeadline=Number.POSITIVE_INFINITY,this.forEachEmitter(t,e=>{this.runtime.queueEmitterState(e,this.runtime.emitters[e].position,!0,!0)}),!0)}stop(e,t){const s=this.resolve(e),i=null==s?null:this.scheduler.getInstance(s.handle);if(null==s||null==i||!this.scheduler.stop(s.handle))return!1;const r=this.pools[s.templateIndex];return s.stoppingDeadline=i.elapsedTime+(null==t?r.maximumParticleLife:Math.max(0,t)),this.forEachEmitter(s,e=>this.runtime.queueEmitterStop(e,t)),!0}setTimescale(e,t){const s=this.resolve(e);return!(null==s||!this.scheduler.setTimescale(s.handle,t))&&(this.forEachEmitter(s,e=>this.runtime.queueEmitterTimescale(e,t)),!0)}setInput(e,t,s){const i=this.resolve(e);if(null==i)return!1;const r=this.pools[i.templateIndex];let n=!1;for(const e of r.template.inputBindings)e.inputId===t&&(n=this.runtime.setEmitterBoundValue(this.emitterIndex(i,r,e.emitterIndex),e,s)||n);return n&&this.scheduler.setDuration(i.handle,this.calculateInstanceDuration(i,r)),n}has(e){return null!=this.resolve(e)}update(e){this.scheduler.update(e),this.runtime.update(e);for(const e of this.activeByHandleSlot){if(null==e)continue;const t=this.scheduler.getInstance(e.handle);if(null==t)continue;const s=this.pools[e.templateIndex];(!1===t.playing&&t.elapsedTime>=s.template.duration||t.stopping&&t.elapsedTime>=e.stoppingDeadline)&&this.destroy(e)}this.flushActivePages()}flushActivePages(){this.activePagesDirty&&(this.runtime.setActivePages(this.activePages),this.activePagesDirty=!1)}dispose(){this.runtime.dispose();for(const e of this.activeByHandleSlot)null!=e&&this.scheduler.destroyInstance(e.handle);this.activeByHandleSlot.length=0}resolve(e){const t=this.activeByHandleSlot[e.handle.slot];return null!=t&&t.handle.generation===e.handle.generation&&null!=this.scheduler.getInstance(t.handle)?t:null}emitterIndex(e,t,s){return t.baseEmitterIndex+e.logicalSlot*t.template.emitters.length+s}forEachEmitter(e,t){const s=this.pools[e.templateIndex];for(let i=0;i<s.template.emitters.length;i++)t(this.emitterIndex(e,s,i))}calculateInstanceDuration(e,t){let s=0;for(let i=0;i<t.template.emitters.length;i++){const r=this.runtime.emitters[this.emitterIndex(e,t,i)],n="continuous"===r.rate.type?r.rate.duration<0?Number.POSITIVE_INFINITY:r.rate.delay+r.rate.duration+r.life:r.rate.delay+r.life;s=Math.max(s,n)}return s}}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -2,12 +2,15 @@ import type { RendererCustomPassContext } from '@hology/webgpu-renderer';
|
|
|
2
2
|
import * as THREE from 'three';
|
|
3
3
|
import type { VfxAsset } from '../../scene/model.js';
|
|
4
4
|
import { GpuSpriteWorldRuntime, type GpuSpriteWorldInstance } from './gpu-sprite-world-runtime.js';
|
|
5
|
+
import { type GpuSpriteTemplate } from './gpu-sprite-template.js';
|
|
5
6
|
type TemplateEntry = {
|
|
6
7
|
asset: VfxAsset;
|
|
7
8
|
revision: string | number | undefined;
|
|
8
9
|
references: number;
|
|
9
10
|
reservedInstances: number;
|
|
10
11
|
retired: boolean;
|
|
12
|
+
runtimeMeshInput: boolean;
|
|
13
|
+
template: GpuSpriteTemplate;
|
|
11
14
|
};
|
|
12
15
|
type WorldEpoch = {
|
|
13
16
|
generation: number;
|
|
@@ -64,6 +67,7 @@ export declare class GpuVfxWorldService {
|
|
|
64
67
|
private readonly entries;
|
|
65
68
|
private readonly epochs;
|
|
66
69
|
private readonly reportedCpuFallbacks;
|
|
70
|
+
private readonly cpuFallbackReasons;
|
|
67
71
|
private currentEpoch;
|
|
68
72
|
private nextGeneration;
|
|
69
73
|
private rebuildTail;
|
|
@@ -83,7 +87,8 @@ export declare class GpuVfxWorldService {
|
|
|
83
87
|
};
|
|
84
88
|
/** Activate one slot per prepared template for a real loading-frame submit. */
|
|
85
89
|
beginPipelineWarmup(): () => void;
|
|
86
|
-
prepare(asset: VfxAsset, revision?: string | number): Promise<PreparedGpuVfxEffect | null>;
|
|
90
|
+
prepare(asset: VfxAsset, revision?: string | number, meshInputs?: ReadonlyMap<string, THREE.Object3D>): Promise<PreparedGpuVfxEffect | null>;
|
|
91
|
+
getFallbackReasons(asset: VfxAsset, revision?: string | number): readonly string[];
|
|
87
92
|
private createEntry;
|
|
88
93
|
private spawn;
|
|
89
94
|
private releaseEntry;
|
|
@@ -94,6 +99,7 @@ export declare class GpuVfxWorldService {
|
|
|
94
99
|
private update;
|
|
95
100
|
private disposeRetiredEpochs;
|
|
96
101
|
private reportCpuFallback;
|
|
102
|
+
private fallbackKey;
|
|
97
103
|
executeCompute(context: RendererCustomPassContext): void;
|
|
98
104
|
executeRender(context: RendererCustomPassContext, colorTarget?: string): void;
|
|
99
105
|
getSceneTextureReads(): readonly string[];
|
|
@@ -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 h}from"../../scene/assets-provider.js";import{ShaderProvider as l}from"../../gameplay/services/shader-provider.js";import{createGpuSpriteShaderGraphResolver as c}from"./vfx-materializer.js";import{GpuSpriteRenderClassCache as u,hasGpuUnsupportedVfxInputBindings as d}from"./gpu-sprite-compiler.js";import{addGpuSpriteRenderGraphPasses as p}from"./gpu-sprite-render-graph.js";import{GpuSpriteWorldRuntime as m}from"./gpu-sprite-world-runtime.js";import{GpuSpriteTemplateCache as g}from"./gpu-sprite-template.js";import{gpuVfxCapacityClass as f}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 y=class{constructor(){this.view=i(a),this.assetLoader=i(n),this.assetsProvider=i(h),this.assetResources=i(o),this.shaderProvider=i(l),this.templateCache=new g,this.renderClassCache=new u,this.entriesByAssetId=new Map,this.entries=new Set,this.epochs=new Set,this.reportedCpuFallbacks=new Set,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){if("webgpu-experimental"!==this.view.requestedBackend)return null;if(d(e))return this.reportCpuFallback(e,t,["one or more authored VFX input bindings are not supported on GPU"]),null;let s=this.entriesByAssetId.get(e.id);if(null==s||s.asset!==e||s.revision!==t){const r=s;s={asset:e,revision:t,pending:(async()=>(null!=r&&await this.retireEntry(r.pending),this.createEntry(e,t)))()},this.entriesByAssetId.set(e.id,s)}const r=await s.pending;if(null==r)return this.reportCpuFallback(e,t,["GPU template preparation rejected this asset without a diagnostic"]),null;r.references+=1;const i=f(r.references),n=this.currentEpoch?.reservations.get(r)??0,a=r.reservedInstances;return i>r.reservedInstances&&(r.reservedInstances=i),n<i&&!await this.queueRebuild()?(r.references-=1,r.reservedInstances=a,this.reportCpuFallback(e,t,["the shared GPU VFX world could not grow within its memory budget or WebGPU device limits"]),null):new PreparedGpuVfxEffect(r,(e,t)=>this.spawn(e,t),e=>this.releaseEntry(e))}async createEntry(e,t){const s=await this.templateCache.resolve(e,{assets:this.assets,renderClassCache:this.renderClassCache,resolveCustomMaterial:this.resolveCustomMaterial,revision:t});if(null==s.template){const r=s.compatibility.issues.filter(e=>"error"===e.severity).map(e=>`${e.emitter}: ${e.message}`);return this.reportCpuFallback(e,t,r.length>0?r:s.reasons),null}const r={asset:e,revision:t,references:0,reservedInstances:1,retired:!1};return this.entries.add(r),r}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)}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 m.create({templates:e.map(e=>({asset:e.asset,revision:e.revision,maximumInstances:e.reservedInstances})),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 ${v(t+i)} MiB, exceeding the ${v(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, ${v(i)} MiB estimated storage.`),this.disposeRetiredEpochs(),!0}ensureRenderGraph(){if(null!=this.unregisterRenderGraph)return;const e=(e,t)=>p(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 ${v(e.estimatedBytes)} MiB estimated storage.`)},s=this.view.experimentalWebGpuDevice;null==s?t():s.queue.onSubmittedWorkDone().then(t,t)}}reportCpuFallback(e,t,s){const r=`${e.id}:${t??""}`;this.reportedCpuFallbacks.has(r)||(this.reportedCpuFallbacks.add(r),console.warn(`[Hology GPU VFX] Falling back to CPU for "${e.name}".`,s))}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)})}};y=e([r(),t("design:paramtypes",[])],y);export{y as GpuVfxWorldService};function v(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{createGpuSpriteShaderGraphResolver as u}from"./vfx-materializer.js";import{GpuSpriteRenderClassCache as c,hasGpuUnsupportedVfxInputBindings as d}from"./gpu-sprite-compiler.js";import{addGpuSpriteRenderGraphPasses as p}from"./gpu-sprite-render-graph.js";import{GpuSpriteWorldRuntime as m}from"./gpu-sprite-world-runtime.js";import{buildGpuSpriteTemplate as g,GpuSpriteTemplateCache as f}from"./gpu-sprite-template.js";import{gpuVfxCapacityClass as y}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 v=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 f,this.renderClassCache=new c,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-experimental"!==this.view.requestedBackend)return this.reportCpuFallback(e,t,[`GPU VFX requires the WebGPU renderer; the requested renderer is "${this.view.requestedBackend}".`]),null;if(d(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=y(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 g(e,r);if(null==i.template){const s=i.compatibility.issues.filter(e=>"error"===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 m.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 ${w(t+i)} MiB, exceeding the ${w(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, ${w(i)} MiB estimated storage.`),this.disposeRetiredEpochs(),!0}ensureRenderGraph(){if(null!=this.unregisterRenderGraph)return;const e=(e,t)=>p(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 ${w(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 u(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)})}};v=e([r(),t("design:paramtypes",[])],v);export{v as GpuVfxWorldService};function w(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,6 +1,21 @@
|
|
|
1
1
|
import { Vector3, Color } from "three";
|
|
2
2
|
import type { Emitter, Particle } from "@hology/nebula";
|
|
3
3
|
import { Initializer } from "@hology/nebula";
|
|
4
|
+
import { VfxMeshSampler, type VfxMeshAlignmentAxis, type VfxMeshSamplingMode } from './vfx-mesh-sampler.js';
|
|
5
|
+
export declare class MeshPositionInitializer extends Initializer {
|
|
6
|
+
private readonly sampler;
|
|
7
|
+
private readonly mode;
|
|
8
|
+
private readonly alignNormal;
|
|
9
|
+
private readonly normalVelocityEnabled;
|
|
10
|
+
private readonly normalVelocityMin;
|
|
11
|
+
private readonly normalVelocityMax;
|
|
12
|
+
private readonly normalVelocitySpread;
|
|
13
|
+
private readonly transform;
|
|
14
|
+
private readonly normalMatrix;
|
|
15
|
+
private readonly axisVector;
|
|
16
|
+
constructor(sampler: VfxMeshSampler | null, mode: VfxMeshSamplingMode, position: Vector3, rotationDegrees: Vector3, scale: Vector3, alignNormal: boolean, axis: VfxMeshAlignmentAxis, normalVelocityEnabled?: boolean, normalVelocityMin?: number, normalVelocityMax?: number, normalVelocitySpread?: number);
|
|
17
|
+
initialize(particle: Particle): void;
|
|
18
|
+
}
|
|
4
19
|
export declare class DefaultInitializer extends Initializer {
|
|
5
20
|
init(emitter: Emitter, particle: Particle): void;
|
|
6
21
|
initialize(target: Emitter | Particle): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Vector3 as t,Quaternion as
|
|
1
|
+
import{Vector3 as t,Quaternion as i,Euler as e,Color as o}from"three";import*as s from"three";import{Span as a,Initializer as n}from"@hology/nebula";export class MeshPositionInitializer extends n{constructor(i,e,o,a,n,l,h,m=!1,x=0,y=0,p=0){super(),this.sampler=i,this.mode=e,this.alignNormal=l,this.normalVelocityEnabled=m,this.normalVelocityMin=x,this.normalVelocityMax=y,this.normalVelocitySpread=p,this.transform=new s.Matrix4,this.normalMatrix=new s.Matrix3,this.axisVector=new t,this.transform.compose(o,r.setFromEuler(c.set(s.MathUtils.degToRad(a.x),s.MathUtils.degToRad(a.y),s.MathUtils.degToRad(a.z))),n),this.normalMatrix.getNormalMatrix(this.transform),function(t,i){t.set(i.endsWith("X")?1:0,i.endsWith("Y")?1:0,i.endsWith("Z")?1:0),i.startsWith("-")&&t.negate()}(this.axisVector,h)}initialize(i){const e=i.transform.meshNormalVelocity;if(e instanceof t&&e.set(0,0,0),!0===this.sampler?.sample(this.mode,h,m)){if(i.position.copy(h.applyMatrix4(this.transform)),m.applyNormalMatrix(this.normalMatrix).normalize(),this.normalVelocityEnabled){!function(t,i,e){if(i<=0)return void e.copy(t);const o=1-Math.random()*(1-Math.cos(Math.min(Math.PI,i))),s=Math.sqrt(Math.max(0,1-o*o)),a=Math.random()*Math.PI*2;y.set(Math.abs(t.y)<.999?0:1,Math.abs(t.y)<.999?1:0,0).cross(t).normalize(),p.crossVectors(t,y),e.copy(t).multiplyScalar(o).addScaledVector(y,Math.cos(a)*s).addScaledVector(p,Math.sin(a)*s)}(m,s.MathUtils.degToRad(this.normalVelocitySpread),x);const e=this.normalVelocityMin+Math.random()*(this.normalVelocityMax-this.normalVelocityMin);i.velocity.x+=x.x*e,i.velocity.y+=x.y*e,i.velocity.z+=x.z*e;(i.transform.meshNormalVelocity instanceof t?i.transform.meshNormalVelocity:i.transform.meshNormalVelocity=new t).copy(x).multiplyScalar(e)}this.alignNormal&&"number"!=typeof i.rotation&&(r.setFromUnitVectors(this.axisVector,m),c.setFromQuaternion(r,"XYZ"),i.rotation.set(c.x,c.y,c.z))}}}export class DefaultInitializer extends n{init(t,i){i.parent=t}initialize(t){}}export class WorldSpaceEmitterTransformInitializer extends n{constructor(t){super(),this.composeParticleRotation=t}init(i,e){const o=i._worldScale;o instanceof t&&e.position.multiply(o),r.setFromEuler(c.set(i.rotation.x,i.rotation.y,i.rotation.z)),e.position.applyQuaternion(r);const s=e.transform.meshNormalVelocity;s instanceof t&&(e.velocity.x-=s.x,e.velocity.y-=s.y,e.velocity.z-=s.z,s.applyQuaternion(r),e.velocity.x+=s.x,e.velocity.y+=s.y,e.velocity.z+=s.z),this.composeParticleRotation&&"number"!=typeof e.rotation&&(l.setFromEuler(c.set(e.rotation.x,e.rotation.y,e.rotation.z)),r.multiply(l),c.setFromQuaternion(r),e.rotation.set(c.x,c.y,c.z))}initialize(t){}}export class WorldSpaceEmitterRotationInitializer extends WorldSpaceEmitterTransformInitializer{constructor(){super(!0)}}export class Rotation extends n{constructor(t){super(),this.rotation=t}setRotation(t){this.rotation=t.clone()}initialize(t){"number"!=typeof t.rotation&&t.rotation.copy(this.rotation)}}export const randomRotationAxis=["XYZ","X","Y","Z"];export class RandomRotation extends n{constructor(t){super(),this.axis=t}initialize(t){switch(this.axis){case"XYZ":t.rotation.set(this.randomAngle(),this.randomAngle(),this.randomAngle());break;case"X":t.rotation.x=this.randomAngle();break;case"Y":t.rotation.y=this.randomAngle();break;case"Z":t.rotation.z=this.randomAngle()}}randomAngle(){return 2*Math.PI*Math.random()}}export class InitialScale extends n{constructor(t){super(),this.scale=t}setScale(t){this.scale=t}initialize(t){t.scale=this.scale}}export class RandomScale extends n{constructor(t,i){super(),this.min=t,this.max=i}setRange(t,i){this.min=t,this.max=i}initialize(t){t.scale=d(this.min,this.max)}}export class RandomColor extends n{constructor(t,i){super(),this.min=new o,this.max=new o,this.setRange(t,i)}setRange(t,i){this.min.set(t),this.max.set(i)}initialize(t){const i=Math.random();t.color.r=d(this.min.r,this.max.r,i),t.color.g=d(this.min.g,this.max.g,i),t.color.b=d(this.min.b,this.max.b,i),t.useColor=!0}}export class InitialScaleComponents extends n{constructor(t){super(),this.scale=t}setScaleComponents(t){this.scale=t.clone()}initialize(t){t.transform.initialScale=this.scale.clone()}}export class AdditiveVelocity extends n{constructor(t,i){super(),this.min=t,this.max=i}initialize(t){const i=u(this.min),e=null!=this.max?u(this.max):void 0;t.velocity.x+=null!=e?d(i.x,e.x):i.x,t.velocity.y+=null!=e?d(i.y,e.y):i.y,t.velocity.z+=null!=e?d(i.z,e.z):i.z}}export class RandomDirection extends n{constructor(t){super(),this.speed=t}setSpeed(t){this.speed=t}initialize(t){h.set(2*Math.random()-1,2*Math.random()-1,2*Math.random()-1).normalize().multiplyScalar(this.speed),t.velocity.x+=h.x,t.velocity.y+=h.y,t.velocity.z+=h.z}}export class RandomVelocity extends n{constructor(t,i){super(),this.min=t,this.max=i,this.x=new a(this.min.x,this.max.x),this.y=new a(this.min.y,this.max.y),this.z=new a(this.min.z,this.max.z)}initialize(t){t.velocity.x+=this.x.getValue(),t.velocity.y+=this.y.getValue(),t.velocity.z+=this.z.getValue()}}export const alignRotationAxes=["+X","-X","+Y","-Y","+Z","-Z"];export const alignRotationModes=["AwayFromCenter","TowardsCenter","AlongVelocity"];export class AlignRotation extends n{constructor(i,e,o=new t,s=new t){super(),this.axis=i,this.mode=e,this.offset=o,this.center=s,this.axisVec=new t(0,1,0),this.setAxis(i)}setAxis(t){switch(t){case"+X":this.axisVec.set(1,0,0);break;case"-X":this.axisVec.set(-1,0,0);break;case"+Y":this.axisVec.set(0,1,0);break;case"-Y":this.axisVec.set(0,-1,0);break;case"+Z":this.axisVec.set(0,0,1);break;case"-Z":this.axisVec.set(0,0,-1)}}initialize(t){if("number"!=typeof t.rotation){switch(this.mode){case"AwayFromCenter":h.copy(t.position).sub(this.center).normalize();break;case"TowardsCenter":h.copy(t.position).sub(this.center).normalize().multiplyScalar(-1);break;case"AlongVelocity":h.copy(t.velocity).normalize()}h.lengthSq()<1e-4&&h.set(0,1,0),r.setFromUnitVectors(this.axisVec,h),this.offset.lengthSq()>0&&(l.setFromEuler(c.set(this.offset.x,this.offset.y,this.offset.z)),r.multiply(l)),c.setFromQuaternion(r),t.rotation.x=c.x,t.rotation.y=c.y,t.rotation.z=c.z}}}const r=new i,l=new i,c=new e,h=new t,m=new t,x=new t,y=new t,p=new t;function d(t,i,e=Math.random()){return e*(i-t)+t}function u(t){return"function"==typeof t?t():t}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -35,6 +35,7 @@ export declare class VfxActor extends BaseActor {
|
|
|
35
35
|
private inputOverrides;
|
|
36
36
|
private finishRequested;
|
|
37
37
|
private completionEmitted;
|
|
38
|
+
private inputsMaterialized;
|
|
38
39
|
/**
|
|
39
40
|
* Replace visual effect with its definition in an asset
|
|
40
41
|
*/
|
|
@@ -59,6 +60,7 @@ export declare class VfxActor extends BaseActor {
|
|
|
59
60
|
private _worldPos;
|
|
60
61
|
private _worldRot;
|
|
61
62
|
private _worldEul;
|
|
63
|
+
private _worldScale;
|
|
62
64
|
private readonly previewStepSeconds;
|
|
63
65
|
private readonly maxPreviewSteps;
|
|
64
66
|
private max;
|
|
@@ -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 a}from"../../gameplay/actors/actor.js";import{inject as o}from"../../gameplay/inject.js";import{AssetLoader as n}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 m}from"./vfx-materializer.js";import{PhysicsSystem as u}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 y}from"../../scene/assets-provider.js";import{VfxInputRuntime as g}from"./vfx-input-runtime.js";import{Subject as f}from"rxjs";import{EventGraphCallable as v}from"../../gameplay/event-graph/decorators.js";import{EVENT_GRAPH_VOID_TYPE as P}from"../../gameplay/event-graph/types.js";import{VFX_ACTOR_TYPE_ID as b}from"../../gameplay/event-graph/asset-actor-types.js";import{GpuVfxWorldService as w}from"./gpu-vfx-world-service.js";import{compileGpuSpriteEmitters as S}from"./gpu-sprite-compiler.js";let E=class extends a{constructor(){super(...arguments),this.onComplete=new f,this._timescale=1,this.paused=!0,this.runtimeBackend="cpu",this.backendFallbackReasons=[],this.profilingEnabled=!1,this.simulationCpuMs=0,this.system=null,this.disposeSystem=null,this.assetLoader=o(n),this.assetManagerService=o(c),this.assetService=o(y),this.world=o(p),this.view=o(l),this.gpuWorld=o(w),this.particleSystemContainer=null,this.gpuPrepared=null,this.gpuHandle=null,this.physics=o(u),this.shaderProvider=o(d),this.inputRuntime=new g,this.inputOverrides=new Map,this.finishRequested=!1,this.completionEmitted=!1,this._worldPos=new s.Vector3,this._worldRot=new s.Quaternion,this._worldEul=new s.Euler,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.backendFallbackReasons=[],this.simulationCpuMs=0,this.sourceAsset=t,this.finishRequested=!1,this.completionEmitted=!1,this.disposeSystem&&this.disposeSystem(),this.world.scene.add(this.object),this.inputRuntime.setDefinitions(this.sourceAsset.vfx.inputs??[]);for(const[t,e]of this.inputOverrides)this.inputRuntime.setInput(t,e);null!=e&&this.setInputs(e);const a=i??this.view.vfxBackendPreference;if("cpu"!==a){if(this.gpuPrepared=await this.gpuWorld.prepare(this.sourceAsset,r),null!=this.gpuPrepared)return this.runtimeBackend="gpu",this.system=null,this.disposeSystem=null,this.particleSystemContainer=null,this.object.visible=!1,this.object.matrixAutoUpdate=!0,void(this.object.matrixWorldAutoUpdate=!0);if("gpu"===a){const t=S(this.sourceAsset,[new s.Vector3]).compatibility;this.backendFallbackReasons=t.issues.filter(t=>"error"===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:o,dispose:n,container:l}=await m(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);this.system=o,this.disposeSystem=n,this.particleSystemContainer=l,this.object.visible=!1,this.object.matrixAutoUpdate=!1,this.object.matrixWorldAutoUpdate=!1,this.particleSystemContainer.matrixAutoUpdate=!1,this.particleSystemContainer.matrixWorldAutoUpdate=!1}setInput(t,e){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 i=this.inputRuntime.getDefinition(t);null!=i&&this.gpuHandle?.setInput(i.id,this.inputRuntime.getValue(i.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.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){if(null==this.system)return;this.prepareForPlayback(),this.restart(),this.paused=!0;const r=Math.max(0,t),a=Math.min(r,Math.max(0,e)),o=r-a,n=Number(a>0)+Number(o>0),l=Math.max(n,Math.min(this.maxPreviewSteps,Math.max(1,Math.ceil(r/this.previewStepSeconds)))),p=a<=0?0:o<=0?l:Math.min(l-1,Math.max(1,Math.round(l*a/r)));this.simulatePreview(a,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.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._worldEul.setFromQuaternion(this._worldRot),this.system?.emitters.forEach(t=>{"world"===t._space&&(t.setPosition(this._worldPos),t.setRotation(this._worldEul))})}getParticleCount(){return null!=this.gpuPrepared?0:this.system?.getCount()??0}getProfilingStats(){let t=0,e=0;return this.particleSystemContainer?.traverse(i=>{const r=i,a=r.geometry;if(null==a||!1===r.visible)return;const o=r instanceof s.InstancedMesh?r.count:1,n=a.index?.count??a.getAttribute("position")?.count??0;o<=0||n<=0||(t++,e+=Math.floor(n/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.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 a=t.childEmitters??[];for(const t of a)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:P}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],E.prototype,"play",null),t([v({parameters:[],returns:P}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],E.prototype,"pause",null),t([v({parameters:[],returns:P}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],E.prototype,"stop",null),t([v({parameters:[],returns:P}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],E.prototype,"restart",null),E=t([r({typeId:b})],E);export{E 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 a}from"../../gameplay/actors/actor.js";import{inject as n}from"../../gameplay/inject.js";import{AssetLoader as o}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 f}from"../../scene/assets-provider.js";import{VfxInputRuntime as g}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 b}from"../../gameplay/event-graph/types.js";import{VFX_ACTOR_TYPE_ID as w}from"../../gameplay/event-graph/asset-actor-types.js";import{GpuVfxWorldService as P}from"./gpu-vfx-world-service.js";import{compileGpuSpriteEmitters as S}from"./gpu-sprite-compiler.js";let j=class extends a{constructor(){super(...arguments),this.onComplete=new y,this._timescale=1,this.paused=!0,this.runtimeBackend="cpu",this.backendFallbackReasons=[],this.profilingEnabled=!1,this.simulationCpuMs=0,this.system=null,this.disposeSystem=null,this.assetLoader=n(o),this.assetManagerService=n(c),this.assetService=n(f),this.world=n(p),this.view=n(l),this.gpuWorld=n(P),this.particleSystemContainer=null,this.gpuPrepared=null,this.gpuHandle=null,this.physics=n(m),this.shaderProvider=n(d),this.inputRuntime=new g,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.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 a=i??this.view.vfxBackendPreference;if("cpu"===a&&(this.backendFallbackReasons=["GPU VFX was not requested; the selected VFX backend preference is CPU."]),"cpu"!==a){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"===a){const t=S(this.sourceAsset,[new s.Vector3]).compatibility;this.backendFallbackReasons=t.issues.filter(t=>"error"===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:n,dispose:o,container:l}=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);this.system=n,this.disposeSystem=o,this.particleSystemContainer=l,this.inputsMaterialized=!0,this.object.visible=!1,this.object.matrixAutoUpdate=!1,this.object.matrixWorldAutoUpdate=!1,this.particleSystemContainer.matrixAutoUpdate=!1,this.particleSystemContainer.matrixWorldAutoUpdate=!1}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.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){if(null==this.system)return;this.prepareForPlayback(),this.restart(),this.paused=!0;const r=Math.max(0,t),a=Math.min(r,Math.max(0,e)),n=r-a,o=Number(a>0)+Number(n>0),l=Math.max(o,Math.min(this.maxPreviewSteps,Math.max(1,Math.ceil(r/this.previewStepSeconds)))),p=a<=0?0:n<=0?l:Math.min(l-1,Math.max(1,Math.round(l*a/r)));this.simulatePreview(a,p),n>0&&(this.applyClipEndBehavior(s,i),this.simulatePreview(n,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.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,a=r.geometry;if(null==a||!1===r.visible)return;const n=r instanceof s.InstancedMesh?r.count:1,o=a.index?.count??a.getAttribute("position")?.count??0;n<=0||o<=0||(t++,e+=Math.floor(o/3)*n)}),{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.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 a=t.childEmitters??[];for(const t of a)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:b}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],j.prototype,"play",null),t([v({parameters:[],returns:b}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],j.prototype,"pause",null),t([v({parameters:[],returns:b}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],j.prototype,"stop",null),t([v({parameters:[],returns:b}),e("design:type",Function),e("design:paramtypes",[]),e("design:returntype",void 0)],j.prototype,"restart",null),j=t([r({typeId:w})],j);export{j as VfxActor};/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import type { LibraryShapeType } from '../../scene/objects/shapes.js';
|
|
2
|
-
import type { AssetId, CustomParamValue } from '../../scene/model.js';
|
|
2
|
+
import type { AssetId, CustomParamValue, VfxInputBinding } from '../../scene/model.js';
|
|
3
3
|
import { SerializedParamType } from '../../scene/model.js';
|
|
4
4
|
import type { LibraryVfxBehaviourType, LibraryVfxInitalizerType } from './vfx-defs.js';
|
|
5
5
|
export type OnceRateData = {
|
|
6
6
|
type: 'once';
|
|
7
7
|
count: number;
|
|
8
8
|
delay: number;
|
|
9
|
+
/** Optional creation-time number input used instead of the authored delay. */
|
|
10
|
+
delayBinding?: VfxInputBinding;
|
|
9
11
|
};
|
|
10
12
|
export type ContinuousRateData = {
|
|
11
13
|
type: 'continuous';
|
|
12
14
|
count: number;
|
|
13
15
|
time: number;
|
|
14
16
|
delay: number;
|
|
17
|
+
/** Optional creation-time number input used instead of the authored delay. */
|
|
18
|
+
delayBinding?: VfxInputBinding;
|
|
15
19
|
duration?: number;
|
|
16
20
|
};
|
|
17
21
|
export type RateData = OnceRateData | ContinuousRateData;
|
|
@@ -60,9 +64,9 @@ export type OutputShaderGraph = {
|
|
|
60
64
|
export type OutputMaterialSourceSettings = {
|
|
61
65
|
materialSource?: OutputMaterialSource;
|
|
62
66
|
} & Partial<OutputShaderGraph>;
|
|
63
|
-
export declare const VfxEffectInputTypes: readonly ["number", "vec3", "color"];
|
|
67
|
+
export declare const VfxEffectInputTypes: readonly ["number", "vec3", "color", "mesh"];
|
|
64
68
|
export type VfxEffectInputType = (typeof VfxEffectInputTypes)[number];
|
|
65
|
-
export type VfxEffectInputValue = number | [number, number, number] | string;
|
|
69
|
+
export type VfxEffectInputValue = number | [number, number, number] | string | null;
|
|
66
70
|
export type VfxEffectInputDefinition = {
|
|
67
71
|
id: string;
|
|
68
72
|
name: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{SerializedParamType as e}from"../../scene/model.js";import*as t from"three";import{randomString as n}from"../../utils/math.js";export const BlendingModes=["additive","subtractive","multiply","normal"];export const ThreeBlendingMode={normal:t.NormalBlending,additive:t.AdditiveBlending,subtractive:t.SubtractiveBlending,multiply:t.MultiplyBlending};export const FlipbookModes=["clamp","loop"];export const VfxEffectInputTypes=["number","vec3","color"];export const ParticleChildSpawnEvents=["collision","start"];export function defaultVfxEffectInputValue(e){switch(e){case"number":return 0;case"vec3":return[0,0,0];case"color":return"#ffffff"}}export function createVfxEffectInputDefinition(e="input",t="number"){return{id:n(),name:e,type:t,defaultValue:defaultVfxEffectInputValue(t)}}export function EmitterDataConstructor(){return{id:n(),name:"Emitter",rate:{type:"continuous",count:1,time:.5,delay:0},position:[0,0,0],rotation:[0,0,0],initializers:[{id:n(),enabled:!0,type:"lifetime",params:{duration:{type:e.Number,value:1}}}],behaviours:[],children:[],output:{type:"sprite",texture:null,opacityChannel:"alpha",color:"#ffffff",blendingMode:"normal",space:"world",renderOrder:0,softness:0,intensity:1,bloom:!1}}}/*
|
|
1
|
+
import{SerializedParamType as e}from"../../scene/model.js";import*as t from"three";import{randomString as n}from"../../utils/math.js";export const BlendingModes=["additive","subtractive","multiply","normal"];export const ThreeBlendingMode={normal:t.NormalBlending,additive:t.AdditiveBlending,subtractive:t.SubtractiveBlending,multiply:t.MultiplyBlending};export const FlipbookModes=["clamp","loop"];export const VfxEffectInputTypes=["number","vec3","color","mesh"];export const ParticleChildSpawnEvents=["collision","start"];export function defaultVfxEffectInputValue(e){switch(e){case"number":return 0;case"vec3":return[0,0,0];case"color":return"#ffffff";case"mesh":return null}}export function createVfxEffectInputDefinition(e="input",t="number"){return{id:n(),name:e,type:t,defaultValue:defaultVfxEffectInputValue(t)}}export function EmitterDataConstructor(){return{id:n(),name:"Emitter",rate:{type:"continuous",count:1,time:.5,delay:0},position:[0,0,0],rotation:[0,0,0],initializers:[{id:n(),enabled:!0,type:"lifetime",params:{duration:{type:e.Number,value:1}}}],behaviours:[],children:[],output:{type:"sprite",texture:null,opacityChannel:"alpha",color:"#ffffff",blendingMode:"normal",space:"world",renderOrder:0,softness:0,intensity:1,bloom:!1}}}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -2,7 +2,7 @@ import { Behaviour, Initializer } from "@hology/nebula";
|
|
|
2
2
|
import type { CustomParamValue } from "../../scene/model.js";
|
|
3
3
|
import type { VfxInputRuntime } from "./vfx-input-runtime.js";
|
|
4
4
|
export type VfxBindingMode = 'spawn' | 'live';
|
|
5
|
-
export type VfxBindableParameterType = 'number' | 'color' | 'string' | 'select' | 'vec2' | 'vec3' | 'curve' | 'animationclip' | 'boolean';
|
|
5
|
+
export type VfxBindableParameterType = 'number' | 'color' | 'string' | 'select' | 'vec2' | 'vec3' | 'curve' | 'animationclip' | 'boolean' | 'mesh';
|
|
6
6
|
export type VfxBindableParameterDefinition = {
|
|
7
7
|
type: VfxBindableParameterType;
|
|
8
8
|
default: unknown;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Behaviour as n,Initializer as e}from"@hology/nebula";import*as t from"three";export function spawnBindingAdapter(n){return{sync(e,t,i){"spawn"===i&&n(e,t)}}}export function liveBindingAdapter(n){return{sync(e,t,i){"live"===i&&n(e,t)}}}export function mixedBindingAdapter(n){return{buildBound:n.buildBound,sync(e,t,i){"spawn"===i?n.spawn?.(e,t):n.live?.(e,t)}}}export function createBoundInitializer(n,e,t,r){const o=new i(n.parameters??{},e,t,r);if(!o.hasBindings())return n.build(t);if(null!=n.bindingAdapter?.buildBound)return n.bindingAdapter.buildBound(o);const a=n.build(t);return null==n.bindingAdapter?.sync?(b(`VFX initializer "${a.type}" has bound params but no binding adapter. Falling back to static values.`),a):new s(a,o,n.bindingAdapter.sync)}export function createBoundBehaviour(n,e,t,s){const o=new i(n.parameters??{},e,t,s);if(!o.hasBindings())return n.build(t);if(null!=n.bindingAdapter?.buildBound)return n.bindingAdapter.buildBound(o);const a=n.build(t);return null==n.bindingAdapter?.sync?(b(`VFX behaviour "${a.type}" has bound params but no binding adapter. Falling back to static values.`),a):new r(a,o,n.bindingAdapter.sync)}class i{constructor(n,e,t,i){this.entries=new Map;for(const[s,r]of Object.entries(n)){const n=p(r.type,t[s]??r.default),u=e[s]?.binding;if("vfxInput"===u?.type&&null!=r.bindable&&null!=i){const e=i.getDefinition(u.inputId);if(null==e){b(`VFX input binding "${u.inputId}" no longer exists. Falling back to the parameter literal value.`),this.entries.set(s,o(s,r.type,n));continue}if(!l(r.type,e.type)){b(`VFX input "${e.name}" does not match the parameter type for "${s}". Falling back to the parameter literal value.`),this.entries.set(s,o(s,r.type,n));continue}this.entries.set(s,{key:s,type:r.type,mode:r.bindable,resolve:()=>a(i,u.inputId,r.type,n)});continue}"vfxInput"===u?.type&&null==r.bindable&&b(`VFX parameter "${s}" is bound to an effect input but the definition is not marked bindable. Falling back to the parameter literal value.`),this.entries.set(s,o(s,r.type,n))}}get(n){return this.entries.get(n)?.resolve()}resolveAll(){const n={};for(const e of this.entries.values())n[e.key]=e.resolve();return n}resolveMode(n){const e={};for(const t of this.entries.values())t.mode===n&&(e[t.key]=t.resolve());return e}hasBindings(n){for(const e of this.entries.values())if(null!=e.mode&&(null==n||e.mode===n))return!0;return!1}}class s extends e{constructor(n,e,t){super(n.type,n.isEnabled),this.inner=n,this.bindings=e,this.sync=t,this.lastSpawnParams=null}reset(){this.inner.reset(),this.lastSpawnParams=null}init(n,e){this.syncMode("spawn"),this.inner.init(n,e)}initialize(n){this.syncMode("spawn"),this.inner.initialize(n)}syncMode(n){const e=this.bindings.resolveMode(n);0!==Object.keys(e).length&&(c(this.lastSpawnParams,e)||(this.sync(this.inner,this.bindings.resolveAll(),n),this.lastSpawnParams=u(e)))}}class r extends n{constructor(n,e,t){super(n.life,n.easing,n.type,n.isEnabled),this.inner=n,this.bindings=e,this.sync=t,this.lastSpawnParams=null,this.lastLiveParams=null,this.id=n.id}reset(n,e){this.inner.reset(n,e),this.lastSpawnParams=null,this.lastLiveParams=null}initialize(n){this.syncMode("live"),this.syncMode("spawn"),this.inner.initialize(n)}applyBehaviour(n,e,t){this.syncMode("live"),this.inner.applyBehaviour(n,e,t)}mutate(n,e,t){this.syncMode("live"),this.inner.mutate(n,e,t)}destroy(){this.inner.destroy()}syncMode(n){const e=this.bindings.resolveMode(n);if(0===Object.keys(e).length)return;c("spawn"===n?this.lastSpawnParams:this.lastLiveParams,e)||(this.sync(this.inner,this.bindings.resolveAll(),n),"spawn"===n?this.lastSpawnParams=u(e):this.lastLiveParams=u(e))}}function o(n,e,t){return{key:n,type:e,mode:null,resolve:()=>p(e,t)}}function a(n,e,i,s){switch(i){case"number":return n.getNumber(e,"number"==typeof s?s:0);case"vec3":return n.getVector3(e,s instanceof t.Vector3?s:new t.Vector3);case"color":return n.getColor(e,s instanceof t.Color?s:new t.Color("#ffffff"));default:return p(i,s)}}function l(n,e){return"number"===n&&"number"===e||"vec3"===n&&"vec3"===e||"color"===n&&"color"===e}function u(n){const e={};for(const[t,i]of Object.entries(n))e[t]=p(h(i),i);return e}function c(n,e){if(null==n)return!1;const t=Object.keys(n),i=Object.keys(e);if(t.length!==i.length)return!1;for(const t of i)if(!d(n[t],e[t]))return!1;return!0}function d(n,e){return n instanceof t.Vector3&&e instanceof t.Vector3||n instanceof t.Color&&e instanceof t.Color?n.equals(e):n===e}function p(n,e){switch(n){case"vec3":return e instanceof t.Vector3?e.clone():e;case"color":return e instanceof t.Color?e.clone():e;default:return e}}function h(n){return n instanceof t.Vector3?"vec3":n instanceof t.Color?"color":"number"==typeof n?"number":"boolean"==typeof n?"boolean":"string"}const f=new Set;function b(n){f.has(n)||(f.add(n),console.warn(n))}/*
|
|
1
|
+
import{Behaviour as n,Initializer as e}from"@hology/nebula";import*as t from"three";export function spawnBindingAdapter(n){return{sync(e,t,i){"spawn"===i&&n(e,t)}}}export function liveBindingAdapter(n){return{sync(e,t,i){"live"===i&&n(e,t)}}}export function mixedBindingAdapter(n){return{buildBound:n.buildBound,sync(e,t,i){"spawn"===i?n.spawn?.(e,t):n.live?.(e,t)}}}export function createBoundInitializer(n,e,t,r){const o=new i(n.parameters??{},e,t,r);if(!o.hasBindings())return n.build(t);if(null!=n.bindingAdapter?.buildBound)return n.bindingAdapter.buildBound(o);const a=n.build(t);return null==n.bindingAdapter?.sync?(b(`VFX initializer "${a.type}" has bound params but no binding adapter. Falling back to static values.`),a):new s(a,o,n.bindingAdapter.sync)}export function createBoundBehaviour(n,e,t,s){const o=new i(n.parameters??{},e,t,s);if(!o.hasBindings())return n.build(t);if(null!=n.bindingAdapter?.buildBound)return n.bindingAdapter.buildBound(o);const a=n.build(t);return null==n.bindingAdapter?.sync?(b(`VFX behaviour "${a.type}" has bound params but no binding adapter. Falling back to static values.`),a):new r(a,o,n.bindingAdapter.sync)}class i{constructor(n,e,t,i){this.entries=new Map;for(const[s,r]of Object.entries(n)){const n=p(r.type,t[s]??r.default),u=e[s]?.binding;if("vfxInput"===u?.type&&null!=r.bindable&&null!=i){const e=i.getDefinition(u.inputId);if(null==e){b(`VFX input binding "${u.inputId}" no longer exists. Falling back to the parameter literal value.`),this.entries.set(s,o(s,r.type,n));continue}if(!l(r.type,e.type)){b(`VFX input "${e.name}" does not match the parameter type for "${s}". Falling back to the parameter literal value.`),this.entries.set(s,o(s,r.type,n));continue}this.entries.set(s,{key:s,type:r.type,mode:r.bindable,resolve:()=>a(i,u.inputId,r.type,n)});continue}"vfxInput"===u?.type&&null==r.bindable&&b(`VFX parameter "${s}" is bound to an effect input but the definition is not marked bindable. Falling back to the parameter literal value.`),this.entries.set(s,o(s,r.type,n))}}get(n){return this.entries.get(n)?.resolve()}resolveAll(){const n={};for(const e of this.entries.values())n[e.key]=e.resolve();return n}resolveMode(n){const e={};for(const t of this.entries.values())t.mode===n&&(e[t.key]=t.resolve());return e}hasBindings(n){for(const e of this.entries.values())if(null!=e.mode&&(null==n||e.mode===n))return!0;return!1}}class s extends e{constructor(n,e,t){super(n.type,n.isEnabled),this.inner=n,this.bindings=e,this.sync=t,this.lastSpawnParams=null}reset(){this.inner.reset(),this.lastSpawnParams=null}init(n,e){this.syncMode("spawn"),this.inner.init(n,e)}initialize(n){this.syncMode("spawn"),this.inner.initialize(n)}syncMode(n){const e=this.bindings.resolveMode(n);0!==Object.keys(e).length&&(c(this.lastSpawnParams,e)||(this.sync(this.inner,this.bindings.resolveAll(),n),this.lastSpawnParams=u(e)))}}class r extends n{constructor(n,e,t){super(n.life,n.easing,n.type,n.isEnabled),this.inner=n,this.bindings=e,this.sync=t,this.lastSpawnParams=null,this.lastLiveParams=null,this.id=n.id}reset(n,e){this.inner.reset(n,e),this.lastSpawnParams=null,this.lastLiveParams=null}initialize(n){this.syncMode("live"),this.syncMode("spawn"),this.inner.initialize(n)}applyBehaviour(n,e,t){this.syncMode("live"),this.inner.applyBehaviour(n,e,t)}mutate(n,e,t){this.syncMode("live"),this.inner.mutate(n,e,t)}destroy(){this.inner.destroy()}syncMode(n){const e=this.bindings.resolveMode(n);if(0===Object.keys(e).length)return;c("spawn"===n?this.lastSpawnParams:this.lastLiveParams,e)||(this.sync(this.inner,this.bindings.resolveAll(),n),"spawn"===n?this.lastSpawnParams=u(e):this.lastLiveParams=u(e))}}function o(n,e,t){return{key:n,type:e,mode:null,resolve:()=>p(e,t)}}function a(n,e,i,s){switch(i){case"number":return n.getNumber(e,"number"==typeof s?s:0);case"vec3":return n.getVector3(e,s instanceof t.Vector3?s:new t.Vector3);case"color":return n.getColor(e,s instanceof t.Color?s:new t.Color("#ffffff"));case"mesh":return n.getMesh(e,s instanceof t.Object3D?s:null)??s;default:return p(i,s)}}function l(n,e){return"number"===n&&"number"===e||"vec3"===n&&"vec3"===e||"color"===n&&"color"===e||"mesh"===n&&"mesh"===e}function u(n){const e={};for(const[t,i]of Object.entries(n))e[t]=p(h(i),i);return e}function c(n,e){if(null==n)return!1;const t=Object.keys(n),i=Object.keys(e);if(t.length!==i.length)return!1;for(const t of i)if(!d(n[t],e[t]))return!1;return!0}function d(n,e){return n instanceof t.Vector3&&e instanceof t.Vector3||n instanceof t.Color&&e instanceof t.Color?n.equals(e):n===e}function p(n,e){switch(n){case"vec3":return e instanceof t.Vector3?e.clone():e;case"color":return e instanceof t.Color?e.clone():e;default:return e}}function h(n){return n instanceof t.Vector3?"vec3":n instanceof t.Color?"color":"number"==typeof n?"number":"boolean"==typeof n?"boolean":"string"}const f=new Set;function b(n){f.has(n)||(f.add(n),console.warn(n))}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnimationClip } from "three";
|
|
1
|
+
import type { AnimationClip, Object3D } from "three";
|
|
2
2
|
import { Vector2, Vector3 } from "three";
|
|
3
3
|
import type { Behaviour, Initializer } from "@hology/nebula";
|
|
4
4
|
import { Alpha, Attraction, Color as ChangeColor, Force, Gravity, Life, Mass, Position, RadialVelocity, RandomDrift, Repulsion, Rotate } from "@hology/nebula";
|
|
@@ -7,8 +7,8 @@ import { AdditiveVelocity, InitialScale, InitialScaleComponents, RandomColor, Ra
|
|
|
7
7
|
import { Curve2 } from "../../utils/curve.js";
|
|
8
8
|
import type { VfxBindingAdapter, VfxBindingMode, VfxBoundBehaviourDefinition, VfxBoundInitializerDefinition } from "./vfx-binding-runtime.js";
|
|
9
9
|
type HexColor = `#${string}`;
|
|
10
|
-
export type ShapeParameterDef<T = boolean | number | string | Vector3 | Vector2 | HexColor | Curve2 | AnimationClip> = {
|
|
11
|
-
type: T extends number ? 'number' : T extends HexColor ? 'color' : T extends string ? 'string' | 'select' : T extends Vector2 ? 'vec2' : T extends Vector3 ? 'vec3' : T extends Curve2 ? 'curve' : T extends AnimationClip ? 'animationclip' : T extends boolean ? 'boolean' : never;
|
|
10
|
+
export type ShapeParameterDef<T = boolean | number | string | Vector3 | Vector2 | HexColor | Curve2 | AnimationClip | Object3D> = {
|
|
11
|
+
type: T extends number ? 'number' : T extends HexColor ? 'color' : T extends string ? 'string' | 'select' : T extends Vector2 ? 'vec2' : T extends Vector3 ? 'vec3' : T extends Curve2 ? 'curve' : T extends AnimationClip ? 'animationclip' : T extends Object3D ? 'mesh' : T extends boolean ? 'boolean' : never;
|
|
12
12
|
default: T;
|
|
13
13
|
requires?: Record<string, unknown>;
|
|
14
14
|
bindable?: VfxBindingMode;
|
|
@@ -137,6 +137,55 @@ declare class PositionRingDef extends BaseIniDef<PositionRingDef> {
|
|
|
137
137
|
bindingAdapter: VfxBindingAdapter<Position>;
|
|
138
138
|
make(params?: ParamsType<this>): Initializer;
|
|
139
139
|
}
|
|
140
|
+
declare class PositionMeshDef extends BaseIniDef<PositionMeshDef> {
|
|
141
|
+
parameters: {
|
|
142
|
+
mesh: ShapeParameterDef<Object3D<import("three").Object3DEventMap, Event>>;
|
|
143
|
+
sampling: SelectParameterDef<string>;
|
|
144
|
+
position: ShapeParameterDef<Vector3>;
|
|
145
|
+
rotation: ShapeParameterDef<Vector3>;
|
|
146
|
+
scale: ShapeParameterDef<Vector3>;
|
|
147
|
+
alignNormal: ShapeParameterDef<boolean>;
|
|
148
|
+
alignmentAxis: SelectParameterDef<string>;
|
|
149
|
+
normalVelocity: ShapeParameterDef<boolean>;
|
|
150
|
+
normalVelocityMin: {
|
|
151
|
+
requires: {
|
|
152
|
+
normalVelocity: boolean;
|
|
153
|
+
};
|
|
154
|
+
type: "number";
|
|
155
|
+
default: number;
|
|
156
|
+
bindable?: VfxBindingMode;
|
|
157
|
+
float: boolean;
|
|
158
|
+
min?: number;
|
|
159
|
+
max?: number;
|
|
160
|
+
};
|
|
161
|
+
normalVelocityMax: {
|
|
162
|
+
requires: {
|
|
163
|
+
normalVelocity: boolean;
|
|
164
|
+
};
|
|
165
|
+
type: "number";
|
|
166
|
+
default: number;
|
|
167
|
+
bindable?: VfxBindingMode;
|
|
168
|
+
float: boolean;
|
|
169
|
+
min?: number;
|
|
170
|
+
max?: number;
|
|
171
|
+
};
|
|
172
|
+
normalVelocitySpread: {
|
|
173
|
+
requires: {
|
|
174
|
+
normalVelocity: boolean;
|
|
175
|
+
};
|
|
176
|
+
type: "number";
|
|
177
|
+
default: number;
|
|
178
|
+
bindable?: VfxBindingMode;
|
|
179
|
+
float: boolean;
|
|
180
|
+
min?: number;
|
|
181
|
+
max?: number;
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
bindingAdapter: {
|
|
185
|
+
buildBound: (context: any) => Initializer;
|
|
186
|
+
};
|
|
187
|
+
make(params?: ParamsType<this>): Initializer;
|
|
188
|
+
}
|
|
140
189
|
declare class RadialVelocityDef extends BaseIniDef<RadialVelocityDef> {
|
|
141
190
|
parameters: {
|
|
142
191
|
speed: FloatParameterDef;
|
|
@@ -381,6 +430,7 @@ declare const vfxInitializsers: {
|
|
|
381
430
|
positionLine: PositionLineDef;
|
|
382
431
|
positionCircle: PositionCircleDef;
|
|
383
432
|
positionRing: PositionRingDef;
|
|
433
|
+
positionMesh: PositionMeshDef;
|
|
384
434
|
rotation: RotationDef;
|
|
385
435
|
randomRotation: RandomRotationDef;
|
|
386
436
|
scale: InitialScaleDef;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{LoopOnce as e,LoopPingPong as t,LoopRepeat as n,MathUtils as s,Vector2 as r,Vector3 as a}from"three";import{Alpha as i,Attraction as o,BoxZone as c,CircleZone as p,Color as d,ease as u,Force as l,Gravity as m,Life as w,LineZone as f,Mass as h,Position as x,RadialVelocity as g,RandomDrift as b,Repulsion as y,Rotate as k,Vector3D as v}from"@hology/nebula";import{AnimationBehavior as A,AxisDirection as z,AxisPlane as R,Disperse as L,FollowParent as C,LinearDamping as D,MoveOverLife as S,OrientAlongVelocity as T,RotatePosition as V,Scale as j,ScaleComponents as Y}from"./behaviours.js";import{AdditiveVelocity as B,AlignRotation as F,alignRotationAxes as O,alignRotationModes as P,InitialScale as X,InitialScaleComponents as Z,RandomColor as I,RandomDirection as M,RandomRotation as K,randomRotationAxis as W,RandomScale as q,Rotation as E}from"./initializsers.js";import{PointZone as G,SphereZone as H}from"./zones.js";import{WorldCollisionBehaviour as J}from"./vfx-collision-behaviour.js";import{Curve2 as N}from"../../utils/curve.js";import{liveBindingAdapter as Q,spawnBindingAdapter as U}from"./vfx-binding-runtime.js";class ${build(e={}){const t=pe(e,this.parameters);return this.make(t)}}class _{build(e={}){const t=pe(e,this.parameters);return this.make(t)}}function ee(e,t,n){return{type:"number",default:e,float:!0,min:t,max:n}}function te(e,t,n,s){return{...ee(e,n,s),bindable:t}}function ne(e){return{type:"boolean",default:e}}function se(e){return{type:"vec3",default:(new a).fromArray(e)}}function re(e,t){return{...se(e),bindable:t}}function ae(e){return{type:"color",default:e}}function ie(e,t){return{...ae(e),bindable:t}}function oe(e,t){return{type:"select",opts:e,default:t}}function ce(){return{type:"curve",default:N.linear()}}function pe(e,t){const n={};for(let s in t)null==e[s]?n[s]=t[s].default:n[s]=e[s];return n}function de(e){return new v(e.x,e.y,e.z)}function ue(e){return new a(s.degToRad(e.x),s.degToRad(e.y),s.degToRad(e.z))}function le(e){const{x:t,y:n,z:s}=e;return new G(t,n,s)}function me(e,t){const{x:n,y:s,z:r}=e,{x:a,y:i,z:o}=t;return new c(n,s,r,a,i,o)}function we(e,t,n=!1){const{x:s,y:r,z:a}=e;return new H(s,r,a,t,n)}function fe(e,t){return new f(e.x,e.y,e.z,t.x,t.y,t.z)}function he(e,t,n,r=0,a=360){return new p(e.x,e.y,e.z,n,r,s.degToRad(t.x),s.degToRad(t.y),s.degToRad(t.z),s.degToRad(a))}function xe(e){return t=>e.getY(t)}const ge={lifetime:new class extends _{constructor(){super(...arguments),this.parameters={duration:te(1,"spawn",0,1e5)},this.bindingAdapter=U((e,t)=>{e.resetLife(t.duration)})}make(e={}){return new w(e.duration)}},randomLifetime:new class extends _{constructor(){super(...arguments),this.parameters={minDuration:te(1,"spawn",0,1e5),maxDuration:te(2,"spawn",0,1e5)},this.bindingAdapter=U((e,t)=>{e.resetLife(t.minDuration,t.maxDuration)})}make(e={}){return new w(e.minDuration,e.maxDuration)}},positionPoint:new class extends _{constructor(){super(...arguments),this.parameters={position:re([0,0,0],"spawn")},this.bindingAdapter=U((e,t)=>{e.reset(le(t.position))})}make(e={}){return new x(le(e.position))}},positionBox:new class extends _{constructor(){super(...arguments),this.parameters={position:re([0,0,0],"spawn"),dimensions:re([0,0,0],"spawn")},this.bindingAdapter=U((e,t)=>{e.reset(me(t.position,t.dimensions))})}make(e={}){return new x(me(e.position,e.dimensions))}},positionSphere:new class extends _{constructor(){super(...arguments),this.parameters={position:re([0,0,0],"spawn"),radius:te(1,"spawn"),surface:ne(!1)},this.bindingAdapter=U((e,t)=>{e.reset(we(t.position,t.radius,t.surface))})}make(e={}){return new x(we(e.position,e.radius,e.surface))}},positionLine:new class extends _{constructor(){super(...arguments),this.parameters={a:re([0,0,0],"spawn"),b:re([0,0,1],"spawn")},this.bindingAdapter=U((e,t)=>{e.reset(fe(t.a,t.b))})}make(e={}){return new x(fe(e.a,e.b))}},positionCircle:new class extends _{constructor(){super(...arguments),this.parameters={position:re([0,0,0],"spawn"),rotation:re([0,0,0],"spawn"),radius:te(1,"spawn",0,1e4),angle:te(360,"spawn",0,360)},this.bindingAdapter=U((e,t)=>{e.reset(he(t.position,t.rotation,t.radius,0,t.angle))})}make(e={}){return new x(he(e.position,e.rotation,e.radius,0,e.angle))}},positionRing:new class extends _{constructor(){super(...arguments),this.parameters={position:re([0,0,0],"spawn"),rotation:re([0,0,0],"spawn"),radius:te(1,"spawn",0,1e4),thickness:te(.2,"spawn",0,1e4),angle:te(360,"spawn",0,360)},this.bindingAdapter=U((e,t)=>{e.reset(he(t.position,t.rotation,t.radius,Math.max(t.thickness,1e-4),t.angle))})}make(e={}){return new x(he(e.position,e.rotation,e.radius,Math.max(e.thickness,1e-4),e.angle))}},rotation:new class extends _{constructor(){super(...arguments),this.parameters={rotation:re([0,0,0],"spawn")},this.bindingAdapter=U((e,t)=>{e.setRotation(ue(t.rotation))})}make(e={}){return new E(ue(e.rotation))}},randomRotation:new class extends _{constructor(){super(...arguments),this.parameters={axis:oe(W,"XYZ")}}make(e={}){return new K(e.axis)}},scale:new class extends _{constructor(){super(...arguments),this.parameters={scale:te(1,"spawn")},this.bindingAdapter=U((e,t)=>{e.setScale(t.scale)})}make(e={}){return new X(e.scale)}},scaleComponents:new class extends _{constructor(){super(...arguments),this.parameters={scale:re([1,1,1],"spawn")},this.bindingAdapter=U((e,t)=>{e.setScaleComponents(t.scale)})}make(e={}){return new Z(e.scale)}},randomScale:new class extends _{constructor(){super(...arguments),this.parameters={min:te(1,"spawn"),max:te(1,"spawn")},this.bindingAdapter=U((e,t)=>{e.setRange(t.min,t.max)})}make(e={}){return new q(e.min,e.max)}},randomColor:new class extends _{constructor(){super(...arguments),this.parameters={a:ie("#ffffff","spawn"),b:ie("#ffffff","spawn")},this.bindingAdapter=U((e,t)=>{e.setRange(t.a,t.b)})}make(e={}){return new I(e.a,e.b)}},mass:new class extends _{constructor(){super(...arguments),this.parameters={min:te(1,"spawn")},this.bindingAdapter=U((e,t)=>{e.resetMass(t.min)})}make(e={}){return new h(e.min)}},velocity:new class extends _{constructor(){super(...arguments),this.parameters={velocity:re([0,0,0],"spawn")},this.bindingAdapter={buildBound:e=>new B(()=>e.get("velocity"))}}make(e={}){const{x:t,y:n,z:s}=e.velocity;return new B(new a(t,n,s))}},randomVelocity:new class extends _{constructor(){super(...arguments),this.parameters={min:re([0,0,0],"spawn"),max:re([0,0,0],"spawn")},this.bindingAdapter={buildBound:e=>new B(()=>e.get("min"),()=>e.get("max"))}}make(e={}){return new B(e.min,e.max)}},radialVelocity:new class extends _{constructor(){super(...arguments),this.parameters={speed:te(1,"spawn"),direction:re([0,1,0],"spawn"),spread:te(0,"spawn")},this.bindingAdapter=U((e,t)=>{e.resetRadialVelocity(t.speed,de(t.direction),t.spread)})}make(e={}){return new g(e.speed,de(e.direction),e.spread)}},randomDirection:new class extends _{constructor(){super(...arguments),this.parameters={speed:te(1,"spawn")},this.bindingAdapter=U((e,t)=>{e.setSpeed(t.speed)})}make(e={}){return new M(e.speed)}},alignRotation:new class extends _{constructor(){super(...arguments),this.parameters={mode:oe(P,"AwayFromCenter"),axis:oe(O,"+Y"),offset:se([0,0,0]),center:se([0,0,0])}}make(e={}){return new F(e.axis,e.mode,ue(e.offset),e.center)}}},be={force:new class extends ${constructor(){super(...arguments),this.parameters={force:re([0,0,0],"live"),ease:ce()},this.bindingAdapter=Q((e,t)=>{const n=t.force;e.setForce(n.x,n.y,n.z)})}make(e={}){return new l(e.force.x,e.force.y,e.force.z,void 0,xe(e.ease))}},gravity:new class extends ${constructor(){super(...arguments),this.parameters={gravity:te(8,"live"),easing:ce()},this.bindingAdapter=Q((e,t)=>{e.setForce(0,-t.gravity,0)})}make(e={}){return new m(e.gravity,void 0,xe(e.easing))}},repulsion:new class extends ${constructor(){super(...arguments),this.parameters={target:re([0,0,0],"live"),force:te(1,"live"),radius:te(1,"live"),easing:ce()},this.bindingAdapter=Q((e,t)=>{e.resetAttraction(de(t.target),t.force,t.radius)})}make(e={}){const t=de(e.target);return new y(t,e.force,e.radius,void 0,xe(e.easing))}},attraction:new class extends ${constructor(){super(...arguments),this.parameters={target:re([0,0,0],"live"),force:te(1,"live"),radius:te(1,"live"),easing:ce()},this.bindingAdapter=Q((e,t)=>{e.resetAttraction(de(t.target),t.force,t.radius)})}make(e={}){const t=de(e.target);return new o(t,e.force,e.radius,void 0,xe(e.easing))}},scale:new class extends ${constructor(){super(...arguments),this.parameters={a:te(1,"spawn"),b:te(1,"spawn"),ease:ce()},this.bindingAdapter=U((e,t)=>{e.setScaleRange(t.a,t.b)})}make(e={}){return new j(e.a,e.b,xe(e.ease))}},scaleComponents:new class extends ${constructor(){super(...arguments),this.parameters={a:re([1,1,1],"spawn"),b:re([1,1,1],"spawn"),ease:ce()},this.bindingAdapter=U((e,t)=>{e.setScaleRange(t.a,t.b)})}make(e={}){return new Y(e.a,e.b,xe(e.ease))}},randomDrift:new class extends ${constructor(){super(...arguments),this.parameters={drift:re([1,1,1],"live"),delay:te(0,"live"),ease:ce()},this.bindingAdapter=Q((e,t)=>{const n=t.drift;e.setDrift(n.x,n.y,n.z,t.delay)})}make(e={}){return new b(e.drift.x,e.drift.y,e.drift.z,e.delay,void 0,xe(e.ease))}},disperse:new class extends ${constructor(){super(...arguments),this.parameters={distance:te(1,"spawn"),axis:oe(["XYZ","XZ","XY","YZ"],"XYZ"),ease:ce()},this.bindingAdapter=U((e,t)=>{e.setDistance(t.distance)})}make(e={}){return new L(e.distance,R[e.axis.toLowerCase()],xe(e.ease))}},rotate:new class extends ${constructor(){super(...arguments),this.parameters={rotation:re([0,0,0],"spawn"),ease:ce()},this.bindingAdapter=U((e,t)=>{const n=ue(t.rotation);e.resetRotation(n.x,n.y,n.z)})}make(e={}){const t=ue(e.rotation);return new k(t.x,t.y,t.z,void 0,xe(e.ease))}},rotateAdd:new class extends ${constructor(){super(...arguments),this.parameters={rotation:re([0,0,0],"spawn")},this.bindingAdapter=U((e,t)=>{const n=ue(t.rotation);e.resetRotation(n.x,n.y,n.z)})}make(e={}){const t=ue(e.rotation),n=new k(t.x,t.y,t.z,void 0);return n.rotationType="add",n}},changeColor:new class extends ${constructor(){super(...arguments),this.parameters={a:ie("#ffffff","spawn"),b:ie("#ffffff","spawn"),ease:ce()},this.bindingAdapter=U((e,t)=>{e.resetColor(t.a,t.b)})}make(e={}){return new d(e.a,e.b,void 0,xe(e.ease))}},changeOpacity:new class extends ${constructor(){super(...arguments),this.parameters={a:te(1,"spawn",0,1),b:te(0,"spawn",0,1),ease:ce()},this.bindingAdapter=U((e,t)=>{e.resetAlpha(t.a,t.b)})}make(e={}){return new i(e.a,e.b,void 0,xe(e.ease))}},vortex:new class extends ${constructor(){super(...arguments),this.parameters={axis:re([0,1,0],"live"),amount:te(1,"live")},this.bindingAdapter=Q((e,t)=>{e.setAxisAngle(t.axis,t.amount)})}make(e={}){return new V(e.axis,e.amount)}},moveTo:new class extends ${constructor(){super(...arguments),this.parameters={target:re([0,1,0],"live"),ease:ce()},this.bindingAdapter=Q((e,t)=>{const n=t.target;e.setTarget(n.x,n.y,n.z)})}make(e={}){return new S(e.target.x,e.target.y,e.target.z,xe(e.ease))}},linearDamping:new class extends ${constructor(){super(...arguments),this.parameters={factor:te(.1,"live",0,1e3)},this.bindingAdapter=Q((e,t)=>{e.setFactor(t.factor)})}make(e={}){return new D(e.factor)}},followParent:new class extends ${constructor(){super(...arguments),this.parameters={speed:te(0,"live")},this.bindingAdapter=Q((e,t)=>{e.setSpeed(t.speed)})}make(e={}){return new C(e.speed)}},worldCollision:new class extends ${constructor(){super(...arguments),this.parameters={friction:ee(1,0,1),bounce:ee(1,0,1),lifeLoss:ee(0,0)}}make(e={}){return new J(null,e.bounce,e.friction,e.lifeLoss)}},animation:new class extends ${constructor(){super(...arguments),this.parameters={clip:{type:"animationclip",default:null},timeScale:ee(1,0,10),weight:ee(1,0,1),loop:oe(["Once","Repeat","PingPong"],"Once"),clampWhenFinished:{...ne(!0),requires:{loop:"Once"}}}}make(s={}){let r=e;return"Repeat"===s.loop?r=n:"PingPong"===s.loop&&(r=t),new A(s.clip,s.timeScale,s.weight,r,s.clampWhenFinished,u.easeLinear)}}};export const VfxInitializserLibrary=ge;export const VfxInitializserLibraryKeys=Object.keys(VfxInitializserLibrary);export const VfxBehaviourLibrary=be;export const VfxBehaviourLibraryKeys=Object.keys(VfxBehaviourLibrary);/*
|
|
1
|
+
import{LoopOnce as e,LoopPingPong as t,LoopRepeat as n,MathUtils as s,Vector2 as r,Vector3 as a}from"three";import{Alpha as i,Attraction as o,BoxZone as c,CircleZone as p,Color as u,ease as d,Force as l,Gravity as m,Life as w,LineZone as f,Mass as h,Position as x,RadialVelocity as g,RandomDrift as b,Repulsion as y,Rotate as v,Vector3D as k}from"@hology/nebula";import{AnimationBehavior as A,AxisDirection as z,AxisPlane as R,Disperse as V,FollowParent as L,LinearDamping as D,MoveOverLife as S,OrientAlongVelocity as C,RotatePosition as j,Scale as T,ScaleComponents as M}from"./behaviours.js";import{AdditiveVelocity as Y,AlignRotation as B,alignRotationAxes as O,alignRotationModes as F,InitialScale as P,InitialScaleComponents as X,MeshPositionInitializer as Z,RandomColor as q,RandomDirection as I,RandomRotation as K,randomRotationAxis as N,RandomScale as W,Rotation as E}from"./initializsers.js";import{VfxMeshSampler as G}from"./vfx-mesh-sampler.js";import{PointZone as H,SphereZone as J}from"./zones.js";import{WorldCollisionBehaviour as Q}from"./vfx-collision-behaviour.js";import{Curve2 as U}from"../../utils/curve.js";import{liveBindingAdapter as $,spawnBindingAdapter as _}from"./vfx-binding-runtime.js";class ee{build(e={}){const t=de(e,this.parameters);return this.make(t)}}class te{build(e={}){const t=de(e,this.parameters);return this.make(t)}}function ne(e,t,n){return{type:"number",default:e,float:!0,min:t,max:n}}function se(e,t,n,s){return{...ne(e,n,s),bindable:t}}function re(e){return{type:"boolean",default:e}}function ae(e){return{type:"vec3",default:(new a).fromArray(e)}}function ie(e,t){return{...ae(e),bindable:t}}function oe(e){return{type:"color",default:e}}function ce(e,t){return{...oe(e),bindable:t}}function pe(e,t){return{type:"select",opts:e,default:t}}function ue(){return{type:"curve",default:U.linear()}}function de(e,t){const n={};for(let s in t)null==e[s]?n[s]=t[s].default:n[s]=e[s];return n}function le(e){return new k(e.x,e.y,e.z)}function me(e){return new a(s.degToRad(e.x),s.degToRad(e.y),s.degToRad(e.z))}function we(e){const{x:t,y:n,z:s}=e;return new H(t,n,s)}function fe(e,t){const{x:n,y:s,z:r}=e,{x:a,y:i,z:o}=t;return new c(n,s,r,a,i,o)}function he(e,t,n=!1){const{x:s,y:r,z:a}=e;return new J(s,r,a,t,n)}function xe(e,t){return new f(e.x,e.y,e.z,t.x,t.y,t.z)}function ge(e,t,n,r=0,a=360){return new p(e.x,e.y,e.z,n,r,s.degToRad(t.x),s.degToRad(t.y),s.degToRad(t.z),s.degToRad(a))}function be(e){const t=e.mesh,n=!0===t?.isObject3D?new G(t):null;return new Z(n?.valid?n:null,e.sampling,e.position,e.rotation,e.scale,e.alignNormal,e.alignmentAxis,e.normalVelocity,e.normalVelocityMin,e.normalVelocityMax,e.normalVelocitySpread)}function ye(e){return t=>e.getY(t)}const ve={lifetime:new class extends te{constructor(){super(...arguments),this.parameters={duration:se(1,"spawn",0,1e5)},this.bindingAdapter=_((e,t)=>{e.resetLife(t.duration)})}make(e={}){return new w(e.duration)}},randomLifetime:new class extends te{constructor(){super(...arguments),this.parameters={minDuration:se(1,"spawn",0,1e5),maxDuration:se(2,"spawn",0,1e5)},this.bindingAdapter=_((e,t)=>{e.resetLife(t.minDuration,t.maxDuration)})}make(e={}){return new w(e.minDuration,e.maxDuration)}},positionPoint:new class extends te{constructor(){super(...arguments),this.parameters={position:ie([0,0,0],"spawn")},this.bindingAdapter=_((e,t)=>{e.reset(we(t.position))})}make(e={}){return new x(we(e.position))}},positionBox:new class extends te{constructor(){super(...arguments),this.parameters={position:ie([0,0,0],"spawn"),dimensions:ie([0,0,0],"spawn")},this.bindingAdapter=_((e,t)=>{e.reset(fe(t.position,t.dimensions))})}make(e={}){return new x(fe(e.position,e.dimensions))}},positionSphere:new class extends te{constructor(){super(...arguments),this.parameters={position:ie([0,0,0],"spawn"),radius:se(1,"spawn"),surface:re(!1)},this.bindingAdapter=_((e,t)=>{e.reset(he(t.position,t.radius,t.surface))})}make(e={}){return new x(he(e.position,e.radius,e.surface))}},positionLine:new class extends te{constructor(){super(...arguments),this.parameters={a:ie([0,0,0],"spawn"),b:ie([0,0,1],"spawn")},this.bindingAdapter=_((e,t)=>{e.reset(xe(t.a,t.b))})}make(e={}){return new x(xe(e.a,e.b))}},positionCircle:new class extends te{constructor(){super(...arguments),this.parameters={position:ie([0,0,0],"spawn"),rotation:ie([0,0,0],"spawn"),radius:se(1,"spawn",0,1e4),angle:se(360,"spawn",0,360)},this.bindingAdapter=_((e,t)=>{e.reset(ge(t.position,t.rotation,t.radius,0,t.angle))})}make(e={}){return new x(ge(e.position,e.rotation,e.radius,0,e.angle))}},positionRing:new class extends te{constructor(){super(...arguments),this.parameters={position:ie([0,0,0],"spawn"),rotation:ie([0,0,0],"spawn"),radius:se(1,"spawn",0,1e4),thickness:se(.2,"spawn",0,1e4),angle:se(360,"spawn",0,360)},this.bindingAdapter=_((e,t)=>{e.reset(ge(t.position,t.rotation,t.radius,Math.max(t.thickness,1e-4),t.angle))})}make(e={}){return new x(ge(e.position,e.rotation,e.radius,Math.max(e.thickness,1e-4),e.angle))}},positionMesh:new class extends te{constructor(){var e;super(...arguments),this.parameters={mesh:(e="spawn",{type:"mesh",default:null,bindable:e}),sampling:pe(["surface","vertices"],"surface"),position:ae([0,0,0]),rotation:ae([0,0,0]),scale:ae([1,1,1]),alignNormal:re(!1),alignmentAxis:pe(O,"+Y"),normalVelocity:re(!1),normalVelocityMin:{...ne(1),requires:{normalVelocity:!0}},normalVelocityMax:{...ne(1),requires:{normalVelocity:!0}},normalVelocitySpread:{...ne(0,0,180),requires:{normalVelocity:!0}}},this.bindingAdapter={buildBound:e=>be(e.resolveAll())}}make(e={}){return be(e)}},rotation:new class extends te{constructor(){super(...arguments),this.parameters={rotation:ie([0,0,0],"spawn")},this.bindingAdapter=_((e,t)=>{e.setRotation(me(t.rotation))})}make(e={}){return new E(me(e.rotation))}},randomRotation:new class extends te{constructor(){super(...arguments),this.parameters={axis:pe(N,"XYZ")}}make(e={}){return new K(e.axis)}},scale:new class extends te{constructor(){super(...arguments),this.parameters={scale:se(1,"spawn")},this.bindingAdapter=_((e,t)=>{e.setScale(t.scale)})}make(e={}){return new P(e.scale)}},scaleComponents:new class extends te{constructor(){super(...arguments),this.parameters={scale:ie([1,1,1],"spawn")},this.bindingAdapter=_((e,t)=>{e.setScaleComponents(t.scale)})}make(e={}){return new X(e.scale)}},randomScale:new class extends te{constructor(){super(...arguments),this.parameters={min:se(1,"spawn"),max:se(1,"spawn")},this.bindingAdapter=_((e,t)=>{e.setRange(t.min,t.max)})}make(e={}){return new W(e.min,e.max)}},randomColor:new class extends te{constructor(){super(...arguments),this.parameters={a:ce("#ffffff","spawn"),b:ce("#ffffff","spawn")},this.bindingAdapter=_((e,t)=>{e.setRange(t.a,t.b)})}make(e={}){return new q(e.a,e.b)}},mass:new class extends te{constructor(){super(...arguments),this.parameters={min:se(1,"spawn")},this.bindingAdapter=_((e,t)=>{e.resetMass(t.min)})}make(e={}){return new h(e.min)}},velocity:new class extends te{constructor(){super(...arguments),this.parameters={velocity:ie([0,0,0],"spawn")},this.bindingAdapter={buildBound:e=>new Y(()=>e.get("velocity"))}}make(e={}){const{x:t,y:n,z:s}=e.velocity;return new Y(new a(t,n,s))}},randomVelocity:new class extends te{constructor(){super(...arguments),this.parameters={min:ie([0,0,0],"spawn"),max:ie([0,0,0],"spawn")},this.bindingAdapter={buildBound:e=>new Y(()=>e.get("min"),()=>e.get("max"))}}make(e={}){return new Y(e.min,e.max)}},radialVelocity:new class extends te{constructor(){super(...arguments),this.parameters={speed:se(1,"spawn"),direction:ie([0,1,0],"spawn"),spread:se(0,"spawn")},this.bindingAdapter=_((e,t)=>{e.resetRadialVelocity(t.speed,le(t.direction),t.spread)})}make(e={}){return new g(e.speed,le(e.direction),e.spread)}},randomDirection:new class extends te{constructor(){super(...arguments),this.parameters={speed:se(1,"spawn")},this.bindingAdapter=_((e,t)=>{e.setSpeed(t.speed)})}make(e={}){return new I(e.speed)}},alignRotation:new class extends te{constructor(){super(...arguments),this.parameters={mode:pe(F,"AwayFromCenter"),axis:pe(O,"+Y"),offset:ae([0,0,0]),center:ae([0,0,0])}}make(e={}){return new B(e.axis,e.mode,me(e.offset),e.center)}}},ke={force:new class extends ee{constructor(){super(...arguments),this.parameters={force:ie([0,0,0],"live"),ease:ue()},this.bindingAdapter=$((e,t)=>{const n=t.force;e.setForce(n.x,n.y,n.z)})}make(e={}){return new l(e.force.x,e.force.y,e.force.z,void 0,ye(e.ease))}},gravity:new class extends ee{constructor(){super(...arguments),this.parameters={gravity:se(8,"live"),easing:ue()},this.bindingAdapter=$((e,t)=>{e.setForce(0,-t.gravity,0)})}make(e={}){return new m(e.gravity,void 0,ye(e.easing))}},repulsion:new class extends ee{constructor(){super(...arguments),this.parameters={target:ie([0,0,0],"live"),force:se(1,"live"),radius:se(1,"live"),easing:ue()},this.bindingAdapter=$((e,t)=>{e.resetAttraction(le(t.target),t.force,t.radius)})}make(e={}){const t=le(e.target);return new y(t,e.force,e.radius,void 0,ye(e.easing))}},attraction:new class extends ee{constructor(){super(...arguments),this.parameters={target:ie([0,0,0],"live"),force:se(1,"live"),radius:se(1,"live"),easing:ue()},this.bindingAdapter=$((e,t)=>{e.resetAttraction(le(t.target),t.force,t.radius)})}make(e={}){const t=le(e.target);return new o(t,e.force,e.radius,void 0,ye(e.easing))}},scale:new class extends ee{constructor(){super(...arguments),this.parameters={a:se(1,"spawn"),b:se(1,"spawn"),ease:ue()},this.bindingAdapter=_((e,t)=>{e.setScaleRange(t.a,t.b)})}make(e={}){return new T(e.a,e.b,ye(e.ease))}},scaleComponents:new class extends ee{constructor(){super(...arguments),this.parameters={a:ie([1,1,1],"spawn"),b:ie([1,1,1],"spawn"),ease:ue()},this.bindingAdapter=_((e,t)=>{e.setScaleRange(t.a,t.b)})}make(e={}){return new M(e.a,e.b,ye(e.ease))}},randomDrift:new class extends ee{constructor(){super(...arguments),this.parameters={drift:ie([1,1,1],"live"),delay:se(0,"live"),ease:ue()},this.bindingAdapter=$((e,t)=>{const n=t.drift;e.setDrift(n.x,n.y,n.z,t.delay)})}make(e={}){return new b(e.drift.x,e.drift.y,e.drift.z,e.delay,void 0,ye(e.ease))}},disperse:new class extends ee{constructor(){super(...arguments),this.parameters={distance:se(1,"spawn"),axis:pe(["XYZ","XZ","XY","YZ"],"XYZ"),ease:ue()},this.bindingAdapter=_((e,t)=>{e.setDistance(t.distance)})}make(e={}){return new V(e.distance,R[e.axis.toLowerCase()],ye(e.ease))}},rotate:new class extends ee{constructor(){super(...arguments),this.parameters={rotation:ie([0,0,0],"spawn"),ease:ue()},this.bindingAdapter=_((e,t)=>{const n=me(t.rotation);e.resetRotation(n.x,n.y,n.z)})}make(e={}){const t=me(e.rotation);return new v(t.x,t.y,t.z,void 0,ye(e.ease))}},rotateAdd:new class extends ee{constructor(){super(...arguments),this.parameters={rotation:ie([0,0,0],"spawn")},this.bindingAdapter=_((e,t)=>{const n=me(t.rotation);e.resetRotation(n.x,n.y,n.z)})}make(e={}){const t=me(e.rotation),n=new v(t.x,t.y,t.z,void 0);return n.rotationType="add",n}},changeColor:new class extends ee{constructor(){super(...arguments),this.parameters={a:ce("#ffffff","spawn"),b:ce("#ffffff","spawn"),ease:ue()},this.bindingAdapter=_((e,t)=>{e.resetColor(t.a,t.b)})}make(e={}){return new u(e.a,e.b,void 0,ye(e.ease))}},changeOpacity:new class extends ee{constructor(){super(...arguments),this.parameters={a:se(1,"spawn",0,1),b:se(0,"spawn",0,1),ease:ue()},this.bindingAdapter=_((e,t)=>{e.resetAlpha(t.a,t.b)})}make(e={}){return new i(e.a,e.b,void 0,ye(e.ease))}},vortex:new class extends ee{constructor(){super(...arguments),this.parameters={axis:ie([0,1,0],"live"),amount:se(1,"live")},this.bindingAdapter=$((e,t)=>{e.setAxisAngle(t.axis,t.amount)})}make(e={}){return new j(e.axis,e.amount)}},moveTo:new class extends ee{constructor(){super(...arguments),this.parameters={target:ie([0,1,0],"live"),ease:ue()},this.bindingAdapter=$((e,t)=>{const n=t.target;e.setTarget(n.x,n.y,n.z)})}make(e={}){return new S(e.target.x,e.target.y,e.target.z,ye(e.ease))}},linearDamping:new class extends ee{constructor(){super(...arguments),this.parameters={factor:se(.1,"live",0,1e3)},this.bindingAdapter=$((e,t)=>{e.setFactor(t.factor)})}make(e={}){return new D(e.factor)}},followParent:new class extends ee{constructor(){super(...arguments),this.parameters={speed:se(0,"live")},this.bindingAdapter=$((e,t)=>{e.setSpeed(t.speed)})}make(e={}){return new L(e.speed)}},worldCollision:new class extends ee{constructor(){super(...arguments),this.parameters={friction:ne(1,0,1),bounce:ne(1,0,1),lifeLoss:ne(0,0)}}make(e={}){return new Q(null,e.bounce,e.friction,e.lifeLoss)}},animation:new class extends ee{constructor(){super(...arguments),this.parameters={clip:{type:"animationclip",default:null},timeScale:ne(1,0,10),weight:ne(1,0,1),loop:pe(["Once","Repeat","PingPong"],"Once"),clampWhenFinished:{...re(!0),requires:{loop:"Once"}}}}make(s={}){let r=e;return"Repeat"===s.loop?r=n:"PingPong"===s.loop&&(r=t),new A(s.clip,s.timeScale,s.weight,r,s.clampWhenFinished,d.easeLinear)}}};export const VfxInitializserLibrary=ve;export const VfxInitializserLibraryKeys=Object.keys(VfxInitializserLibrary);export const VfxBehaviourLibrary=ke;export const VfxBehaviourLibraryKeys=Object.keys(VfxBehaviourLibrary);/*
|
|
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 * as THREE from 'three';
|
|
2
2
|
import type { VfxEffectInputDefinition } from './vfx-asset.js';
|
|
3
|
-
type VfxRuntimeInputValue = number | THREE.Vector3 | THREE.Color;
|
|
3
|
+
export type VfxRuntimeInputValue = number | THREE.Vector3 | THREE.Color | THREE.Object3D | null;
|
|
4
4
|
export declare class VfxInputRuntime {
|
|
5
5
|
private readonly definitions;
|
|
6
6
|
private readonly inputIdsByName;
|
|
@@ -13,7 +13,7 @@ export declare class VfxInputRuntime {
|
|
|
13
13
|
getNumber(inputNameOrId: string, fallback?: number): number;
|
|
14
14
|
getVector3(inputNameOrId: string, fallback?: THREE.Vector3): THREE.Vector3;
|
|
15
15
|
getColor(inputNameOrId: string, fallback?: THREE.Color): THREE.Color;
|
|
16
|
+
getMesh(inputNameOrId: string, fallback?: THREE.Object3D | null): THREE.Object3D | null;
|
|
16
17
|
getValue(inputNameOrId: string): VfxRuntimeInputValue | undefined;
|
|
17
18
|
}
|
|
18
|
-
export {};
|
|
19
19
|
//# sourceMappingURL=vfx-input-runtime.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import*as e from"three";export class VfxInputRuntime{constructor(e=[]){this.definitions=new Map,this.inputIdsByName=new Map,this.values=new Map,this.setDefinitions(e)}setDefinitions(e=[]){this.definitions.clear(),this.inputIdsByName.clear(),this.values.clear();for(const n of e)this.definitions.set(n.id,n),this.inputIdsByName.set(n.name,n.id),this.values.set(n.id,t(n.type,n.defaultValue,n.defaultValue))}getDefinitions(){return Array.from(this.definitions.values())}getDefinition(e){const t=this.definitions.has(e)?e:this.inputIdsByName.get(e);return null!=t?this.definitions.get(t):void 0}setInput(e,n){const i=this.getDefinition(e);null!=i&&this.values.set(i.id,t(i.type,n,i.defaultValue))}getNumber(e,t=0){const n=this.getValue(e);return"number"==typeof n?n:t}getVector3(t,n=new e.Vector3){const i=this.getValue(t);return i instanceof e.Vector3?i:n}getColor(t,n=new e.Color("#ffffff")){const i=this.getValue(t);return i instanceof e.Color?i:n}getValue(e){const t=this.getDefinition(e);return null!=t?this.values.get(t.id):void 0}}function t(t,
|
|
1
|
+
import*as e from"three";export class VfxInputRuntime{constructor(e=[]){this.definitions=new Map,this.inputIdsByName=new Map,this.values=new Map,this.setDefinitions(e)}setDefinitions(e=[]){this.definitions.clear(),this.inputIdsByName.clear(),this.values.clear();for(const n of e)this.definitions.set(n.id,n),this.inputIdsByName.set(n.name,n.id),this.values.set(n.id,t(n.type,n.defaultValue,n.defaultValue))}getDefinitions(){return Array.from(this.definitions.values())}getDefinition(e){const t=this.definitions.has(e)?e:this.inputIdsByName.get(e);return null!=t?this.definitions.get(t):void 0}setInput(e,n){const i=this.getDefinition(e);null!=i&&this.values.set(i.id,t(i.type,n,i.defaultValue))}getNumber(e,t=0){const n=this.getValue(e);return"number"==typeof n?n:t}getVector3(t,n=new e.Vector3){const i=this.getValue(t);return i instanceof e.Vector3?i:n}getColor(t,n=new e.Color("#ffffff")){const i=this.getValue(t);return i instanceof e.Color?i:n}getMesh(e,t=null){const i=this.getValue(e);return n(i)?i:t}getValue(e){const t=this.getDefinition(e);return null!=t?this.values.get(t.id):void 0}}function t(t,r,s){switch(t){case"number":return i(r,s);case"vec3":return function(t,n){if(t instanceof e.Vector3)return t.clone();if(Array.isArray(t)&&t.length>=3)return new e.Vector3(i(t[0]),i(t[1]),i(t[2]));if(n instanceof e.Vector3)return n.clone();if(Array.isArray(n)&&n.length>=3)return new e.Vector3(i(n[0]),i(n[1]),i(n[2]));return new e.Vector3}(r,s);case"color":return function(t,n){if(t instanceof e.Color)return t.clone();if("string"==typeof t||"number"==typeof t)return new e.Color(t);if(n instanceof e.Color)return n.clone();if("string"==typeof n||"number"==typeof n)return new e.Color(n);return new e.Color("#ffffff")}(r,s);case"mesh":return n(r)?r:n(s)?s:null}}function n(e){return!0===e?.isObject3D}function i(e,t){if("number"==typeof e&&Number.isFinite(e))return e;if("string"==typeof e){const t=parseFloat(e);if(Number.isFinite(t))return t}return"number"==typeof t?t:0}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|