sparcoon 0.0.1
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/LICENSE +21 -0
- package/dist/behaviorModules/FXBehaviorDirectionalGravity.d.ts +21 -0
- package/dist/behaviorModules/FXBehaviorDirectionalGravity.js +47 -0
- package/dist/behaviorModules/FXBehaviorModule.d.ts +14 -0
- package/dist/behaviorModules/FXBehaviorModule.js +9 -0
- package/dist/behaviorModules/FXBehaviorPointGravity.d.ts +39 -0
- package/dist/behaviorModules/FXBehaviorPointGravity.js +110 -0
- package/dist/behaviorModules/FXBehaviorScaleOverLife.d.ts +22 -0
- package/dist/behaviorModules/FXBehaviorScaleOverLife.js +66 -0
- package/dist/behaviorModules/FXBehaviorTorqueDamping.d.ts +20 -0
- package/dist/behaviorModules/FXBehaviorTorqueDamping.js +48 -0
- package/dist/behaviorModules/FXBehaviorTorqueNoise.d.ts +26 -0
- package/dist/behaviorModules/FXBehaviorTorqueNoise.js +66 -0
- package/dist/behaviorModules/FXBehaviorVelocityDamping.d.ts +20 -0
- package/dist/behaviorModules/FXBehaviorVelocityDamping.js +49 -0
- package/dist/behaviorModules/FXBehaviorVelocityNoise.d.ts +26 -0
- package/dist/behaviorModules/FXBehaviorVelocityNoise.js +73 -0
- package/dist/color/FXColor.Internal.d.ts +7 -0
- package/dist/color/FXColor.Internal.js +42 -0
- package/dist/color/FXColor.d.ts +234 -0
- package/dist/color/FXColor.js +636 -0
- package/dist/elements/FXEmitter.Internal.d.ts +10 -0
- package/dist/elements/FXEmitter.Internal.js +5 -0
- package/dist/elements/FXEmitter.d.ts +29 -0
- package/dist/elements/FXEmitter.js +154 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +18 -0
- package/dist/instancedParticle/FXInstancedParticle.Internal.d.ts +13 -0
- package/dist/instancedParticle/FXInstancedParticle.Internal.js +216 -0
- package/dist/instancedParticle/FXInstancedParticle.d.ts +17 -0
- package/dist/instancedParticle/FXInstancedParticle.js +93 -0
- package/dist/instancedParticle/shared.d.ts +27 -0
- package/dist/instancedParticle/shared.js +123 -0
- package/dist/miscellaneous/FXTransparencyMode.d.ts +7 -0
- package/dist/miscellaneous/asserts.d.ts +21 -0
- package/dist/miscellaneous/asserts.js +48 -0
- package/dist/miscellaneous/math.d.ts +8 -0
- package/dist/miscellaneous/math.js +11 -0
- package/dist/miscellaneous/miscellaneous.d.ts +33 -0
- package/dist/miscellaneous/miscellaneous.js +66 -0
- package/dist/miscellaneous/texture/FXTextureView.Internal.d.ts +49 -0
- package/dist/miscellaneous/texture/FXTextureView.Internal.js +6 -0
- package/dist/miscellaneous/texture/FXTextureView.d.ts +72 -0
- package/dist/miscellaneous/texture/FXTextureView.js +234 -0
- package/dist/miscellaneous/webglCapabilities.d.ts +1 -0
- package/dist/miscellaneous/webglCapabilities.js +45 -0
- package/dist/renderingModules/FXRenderingColorOverLife.d.ts +14 -0
- package/dist/renderingModules/FXRenderingColorOverLife.js +32 -0
- package/dist/renderingModules/FXRenderingColorOverVelocity.d.ts +15 -0
- package/dist/renderingModules/FXRenderingColorOverVelocity.js +37 -0
- package/dist/renderingModules/FXRenderingModule.d.ts +8 -0
- package/dist/renderingModules/FXRenderingModule.js +9 -0
- package/dist/renderingModules/FXRenderingTexture.d.ts +14 -0
- package/dist/renderingModules/FXRenderingTexture.js +26 -0
- package/dist/shaders/FXRenderingColorOverLife.glsl.js +3 -0
- package/dist/shaders/FXRenderingColorOverVelocity.glsl.js +3 -0
- package/dist/shaders/FXRenderingTexture.glsl.js +3 -0
- package/dist/spawnModules/FXSpawnBox.d.ts +27 -0
- package/dist/spawnModules/FXSpawnBox.js +67 -0
- package/dist/spawnModules/FXSpawnModule.d.ts +9 -0
- package/dist/spawnModules/FXSpawnModule.js +9 -0
- package/dist/spawnModules/FXSpawnOffset.d.ts +21 -0
- package/dist/spawnModules/FXSpawnOffset.js +49 -0
- package/dist/spawnModules/FXSpawnRandomLifetime.d.ts +20 -0
- package/dist/spawnModules/FXSpawnRandomLifetime.js +47 -0
- package/dist/spawnModules/FXSpawnRandomRotation.d.ts +20 -0
- package/dist/spawnModules/FXSpawnRandomRotation.js +45 -0
- package/dist/spawnModules/FXSpawnRandomScale.d.ts +26 -0
- package/dist/spawnModules/FXSpawnRandomScale.js +60 -0
- package/dist/spawnModules/FXSpawnRandomTorque.d.ts +20 -0
- package/dist/spawnModules/FXSpawnRandomTorque.js +45 -0
- package/dist/spawnModules/FXSpawnRandomVelocity.d.ts +26 -0
- package/dist/spawnModules/FXSpawnRandomVelocity.js +64 -0
- package/package.json +55 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Object3D, Vector2 } from 'three';
|
|
2
|
+
import { FXInstancedParticle } from '../instancedParticle/FXInstancedParticle.js';
|
|
3
|
+
import { collectProperties, collectUniforms } from '../instancedParticle/FXInstancedParticle.Internal.js';
|
|
4
|
+
import { resolveGLSLTypeInfo } from '../instancedParticle/shared.js';
|
|
5
|
+
import { BUILTIN_OFFSET_AGE, BUILTIN_OFFSET_POSITION_X, BUILTIN_OFFSET_VELOCITY_X, BUILTIN_OFFSET_POSITION_Y, BUILTIN_OFFSET_VELOCITY_Y, BUILTIN_OFFSET_ROTATION, BUILTIN_OFFSET_TORQUE, BUILTIN_OFFSET_RANDOM_A, BUILTIN_OFFSET_RANDOM_B, BUILTIN_OFFSET_RANDOM_C } from '../miscellaneous/miscellaneous.js';
|
|
6
|
+
import { EMITTER_DEFAULT_EXPECTED_CAPACITY, EMITTER_DEFAULT_CAPACITY_STEP, EMITTER_DEFAULT_AUTOMATICALLY_DESTROY_MODULES } from './FXEmitter.Internal.js';
|
|
7
|
+
|
|
8
|
+
const INSTANCES = new Array();
|
|
9
|
+
class FXEmitter extends Object3D {
|
|
10
|
+
constructor(_private_spawnSequence, _private_behaviorSequence, _private_renderingSequence, options = {}) {
|
|
11
|
+
super();
|
|
12
|
+
this._private_spawnSequence = _private_spawnSequence;
|
|
13
|
+
this._private_behaviorSequence = _private_behaviorSequence;
|
|
14
|
+
this._private_renderingSequence = _private_renderingSequence;
|
|
15
|
+
this._private_emissionRate = 0;
|
|
16
|
+
this._private_emissionAccumulator = 0;
|
|
17
|
+
this._private_emissionDuration = Infinity;
|
|
18
|
+
this._private_emissionElapsed = 0;
|
|
19
|
+
this._private_onRendering = (deltaTime) => {
|
|
20
|
+
{
|
|
21
|
+
const { builtin } = this._private_mesh.propertyBuffers;
|
|
22
|
+
const { array, itemSize } = builtin;
|
|
23
|
+
for (let i = 0; i < this._private_mesh.instanceCount; i++) {
|
|
24
|
+
array[i * itemSize + BUILTIN_OFFSET_AGE] += deltaTime;
|
|
25
|
+
}
|
|
26
|
+
builtin.needsUpdate = true;
|
|
27
|
+
this._private_mesh.removeDeadParticles();
|
|
28
|
+
}
|
|
29
|
+
if (this._private_emissionRate > 0) {
|
|
30
|
+
this._private_emissionElapsed += deltaTime;
|
|
31
|
+
if (this._private_emissionElapsed >= this._private_emissionDuration) {
|
|
32
|
+
this.stop();
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
this._private_emissionAccumulator += this._private_emissionRate * deltaTime;
|
|
36
|
+
const particlesToSpawn = Math.floor(this._private_emissionAccumulator);
|
|
37
|
+
if (particlesToSpawn > 0) {
|
|
38
|
+
this.burst(particlesToSpawn);
|
|
39
|
+
this._private_emissionAccumulator -= particlesToSpawn;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (this._private_mesh.instanceCount === 0) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
{
|
|
47
|
+
const { propertyBuffers, instanceCount } = this._private_mesh;
|
|
48
|
+
for (const module of this._private_behaviorSequence) {
|
|
49
|
+
module.update(propertyBuffers, instanceCount, deltaTime);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const { builtin } = this._private_mesh.propertyBuffers;
|
|
53
|
+
const { array, itemSize } = builtin;
|
|
54
|
+
{
|
|
55
|
+
for (let i = 0; i < this._private_mesh.instanceCount; i++) {
|
|
56
|
+
const itemOffset = i * itemSize;
|
|
57
|
+
array[itemOffset + BUILTIN_OFFSET_POSITION_X] +=
|
|
58
|
+
array[itemOffset + BUILTIN_OFFSET_VELOCITY_X] * deltaTime;
|
|
59
|
+
array[itemOffset + BUILTIN_OFFSET_POSITION_Y] +=
|
|
60
|
+
array[itemOffset + BUILTIN_OFFSET_VELOCITY_Y] * deltaTime;
|
|
61
|
+
}
|
|
62
|
+
builtin.needsUpdate = true;
|
|
63
|
+
}
|
|
64
|
+
{
|
|
65
|
+
for (let i = 0; i < this._private_mesh.instanceCount; i++) {
|
|
66
|
+
const itemOffset = i * itemSize;
|
|
67
|
+
array[itemOffset + BUILTIN_OFFSET_ROTATION] +=
|
|
68
|
+
array[itemOffset + BUILTIN_OFFSET_TORQUE] * deltaTime;
|
|
69
|
+
}
|
|
70
|
+
builtin.needsUpdate = true;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const collectedProperties = {
|
|
74
|
+
builtin: resolveGLSLTypeInfo("Matrix4"),
|
|
75
|
+
// 0, 1 positionX, positionY
|
|
76
|
+
// 2, 3 velocityX, velocityY
|
|
77
|
+
// 4, 5 scaleX, scaleY
|
|
78
|
+
// 6, 7 rotation, torque
|
|
79
|
+
// 8, 9 lifetime, age
|
|
80
|
+
// 10, 11, 12 randomX, randomY, randomZ
|
|
81
|
+
// 13, 14, 16 reservedX, reservedY, reservedZ
|
|
82
|
+
};
|
|
83
|
+
collectProperties(collectedProperties, _private_spawnSequence, "UIEmitter.constructor.spawnSequence:");
|
|
84
|
+
collectProperties(collectedProperties, _private_behaviorSequence, "UIEmitter.constructor.behaviorSequence");
|
|
85
|
+
collectProperties(collectedProperties, _private_renderingSequence, "UIEmitter.constructor.renderingSequence");
|
|
86
|
+
const collectedUniforms = {
|
|
87
|
+
origin: { value: new Vector2(), glslTypeInfo: resolveGLSLTypeInfo("Vector2") },
|
|
88
|
+
};
|
|
89
|
+
collectUniforms(collectedUniforms, _private_renderingSequence, "UIEmitter.constructor.renderingSequence");
|
|
90
|
+
this._private_mesh = new FXInstancedParticle(this._private_renderingSequence.map((module) => module.source), collectedUniforms, collectedProperties, options.expectedCapacity ?? EMITTER_DEFAULT_EXPECTED_CAPACITY, options.capacityStep ?? EMITTER_DEFAULT_CAPACITY_STEP);
|
|
91
|
+
this.automaticallyDestroyModules =
|
|
92
|
+
options.automaticallyDestroyModules ?? EMITTER_DEFAULT_AUTOMATICALLY_DESTROY_MODULES;
|
|
93
|
+
this.add(this._private_mesh);
|
|
94
|
+
INSTANCES.push(this);
|
|
95
|
+
}
|
|
96
|
+
destroy() {
|
|
97
|
+
const index = INSTANCES.indexOf(this);
|
|
98
|
+
if (index !== -1) {
|
|
99
|
+
INSTANCES.splice(index, 1);
|
|
100
|
+
}
|
|
101
|
+
if (this.automaticallyDestroyModules) {
|
|
102
|
+
for (const module of this._private_spawnSequence) {
|
|
103
|
+
module.destroy?.();
|
|
104
|
+
}
|
|
105
|
+
for (const module of this._private_behaviorSequence) {
|
|
106
|
+
module.destroy?.();
|
|
107
|
+
}
|
|
108
|
+
for (const module of this._private_renderingSequence) {
|
|
109
|
+
module.destroy?.();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
burst(count) {
|
|
114
|
+
const instanceBegin = this._private_mesh.instanceCount;
|
|
115
|
+
this._private_mesh.createInstances(count);
|
|
116
|
+
const instanceEnd = this._private_mesh.instanceCount;
|
|
117
|
+
{
|
|
118
|
+
const { builtin } = this._private_mesh.propertyBuffers;
|
|
119
|
+
const { array, itemSize } = builtin;
|
|
120
|
+
for (let i = instanceBegin; i < instanceEnd; i++) {
|
|
121
|
+
const itemOffset = i * itemSize;
|
|
122
|
+
array[itemOffset + BUILTIN_OFFSET_RANDOM_A] = Math.random();
|
|
123
|
+
array[itemOffset + BUILTIN_OFFSET_RANDOM_B] = Math.random();
|
|
124
|
+
array[itemOffset + BUILTIN_OFFSET_RANDOM_C] = Math.random();
|
|
125
|
+
}
|
|
126
|
+
builtin.needsUpdate = true;
|
|
127
|
+
}
|
|
128
|
+
for (const spawnModule of this._private_spawnSequence) {
|
|
129
|
+
spawnModule.spawn(this._private_mesh.propertyBuffers, instanceBegin, instanceEnd);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
reset() {
|
|
133
|
+
this._private_mesh.drop();
|
|
134
|
+
}
|
|
135
|
+
play(rate, options = {}) {
|
|
136
|
+
this._private_emissionRate = rate;
|
|
137
|
+
this._private_emissionAccumulator = 0;
|
|
138
|
+
this._private_emissionDuration = options.duration ?? Infinity;
|
|
139
|
+
this._private_emissionElapsed = 0;
|
|
140
|
+
}
|
|
141
|
+
stop() {
|
|
142
|
+
this._private_emissionRate = 0;
|
|
143
|
+
this._private_emissionAccumulator = 0;
|
|
144
|
+
this._private_emissionDuration = Infinity;
|
|
145
|
+
this._private_emissionElapsed = 0;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
FXEmitter.onWillRender = (deltaTime) => {
|
|
149
|
+
for (const instance of INSTANCES) {
|
|
150
|
+
instance._private_onRendering(deltaTime);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export { FXEmitter };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { FXBehaviorDirectionalGravity } from "./behaviorModules/FXBehaviorDirectionalGravity";
|
|
2
|
+
export { FXBehaviorPointGravity } from "./behaviorModules/FXBehaviorPointGravity";
|
|
3
|
+
export { FXBehaviorScaleOverLife } from "./behaviorModules/FXBehaviorScaleOverLife";
|
|
4
|
+
export { FXBehaviorTorqueDamping } from "./behaviorModules/FXBehaviorTorqueDamping";
|
|
5
|
+
export { FXBehaviorTorqueNoise } from "./behaviorModules/FXBehaviorTorqueNoise";
|
|
6
|
+
export { FXBehaviorVelocityDamping } from "./behaviorModules/FXBehaviorVelocityDamping";
|
|
7
|
+
export { FXBehaviorVelocityNoise } from "./behaviorModules/FXBehaviorVelocityNoise";
|
|
8
|
+
export { FXEmitter } from "./elements/FXEmitter";
|
|
9
|
+
export { FXRenderingColorOverLife } from "./renderingModules/FXRenderingColorOverLife";
|
|
10
|
+
export { FXRenderingColorOverVelocity } from "./renderingModules/FXRenderingColorOverVelocity";
|
|
11
|
+
export { FXTextureRenderingModule } from "./renderingModules/FXRenderingTexture";
|
|
12
|
+
export { FXSpawnBox } from "./spawnModules/FXSpawnBox";
|
|
13
|
+
export { FXSpawnOffset } from "./spawnModules/FXSpawnOffset";
|
|
14
|
+
export { FXSpawnRandomLifetime } from "./spawnModules/FXSpawnRandomLifetime";
|
|
15
|
+
export { FXSpawnRandomRotation } from "./spawnModules/FXSpawnRandomRotation";
|
|
16
|
+
export { FXSpawnRandomScale } from "./spawnModules/FXSpawnRandomScale";
|
|
17
|
+
export { FXSpawnRandomTorque } from "./spawnModules/FXSpawnRandomTorque";
|
|
18
|
+
export { FXSpawnRandomVelocity } from "./spawnModules/FXSpawnRandomVelocity";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { FXBehaviorDirectionalGravity } from './behaviorModules/FXBehaviorDirectionalGravity.js';
|
|
2
|
+
export { FXBehaviorPointGravity } from './behaviorModules/FXBehaviorPointGravity.js';
|
|
3
|
+
export { FXBehaviorScaleOverLife } from './behaviorModules/FXBehaviorScaleOverLife.js';
|
|
4
|
+
export { FXBehaviorTorqueDamping } from './behaviorModules/FXBehaviorTorqueDamping.js';
|
|
5
|
+
export { FXBehaviorTorqueNoise } from './behaviorModules/FXBehaviorTorqueNoise.js';
|
|
6
|
+
export { FXBehaviorVelocityDamping } from './behaviorModules/FXBehaviorVelocityDamping.js';
|
|
7
|
+
export { FXBehaviorVelocityNoise } from './behaviorModules/FXBehaviorVelocityNoise.js';
|
|
8
|
+
export { FXEmitter } from './elements/FXEmitter.js';
|
|
9
|
+
export { FXRenderingColorOverLife } from './renderingModules/FXRenderingColorOverLife.js';
|
|
10
|
+
export { FXRenderingColorOverVelocity } from './renderingModules/FXRenderingColorOverVelocity.js';
|
|
11
|
+
export { FXTextureRenderingModule } from './renderingModules/FXRenderingTexture.js';
|
|
12
|
+
export { FXSpawnBox } from './spawnModules/FXSpawnBox.js';
|
|
13
|
+
export { FXSpawnOffset } from './spawnModules/FXSpawnOffset.js';
|
|
14
|
+
export { FXSpawnRandomLifetime } from './spawnModules/FXSpawnRandomLifetime.js';
|
|
15
|
+
export { FXSpawnRandomRotation } from './spawnModules/FXSpawnRandomRotation.js';
|
|
16
|
+
export { FXSpawnRandomScale } from './spawnModules/FXSpawnRandomScale.js';
|
|
17
|
+
export { FXSpawnRandomTorque } from './spawnModules/FXSpawnRandomTorque.js';
|
|
18
|
+
export { FXSpawnRandomVelocity } from './spawnModules/FXSpawnRandomVelocity.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { InstancedBufferGeometry, ShaderMaterial } from "three";
|
|
2
|
+
import type { GLProperty, GLTypeInfo } from "./shared";
|
|
3
|
+
import { type FXProperty, type FXPropertyName } from "./shared";
|
|
4
|
+
export declare const INSTANCED_PARTICLE_GEOMETRY: InstancedBufferGeometry;
|
|
5
|
+
export declare function collectProperties(keeper: Record<string, GLTypeInfo>, modules: readonly {
|
|
6
|
+
requiredProperties?: Record<string, FXPropertyName>;
|
|
7
|
+
}[], debugContext: string): void;
|
|
8
|
+
export declare function collectUniforms(keeper: Record<string, GLProperty>, modules: readonly {
|
|
9
|
+
requiredUniforms: Record<string, FXProperty>;
|
|
10
|
+
}[], debugContext: string): void;
|
|
11
|
+
export declare function buildParticleVertexShader(uniformDeclarations: string[], attributeDeclarations: string[], varyingDeclarations: string[], vertexAssignments: string[]): string;
|
|
12
|
+
export declare function buildParticleFragmentShader(uniformDeclarations: string[], varyingDeclarations: string[], sources: string[]): string;
|
|
13
|
+
export declare function buildParticleMaterial(sources: string[], uniformProperties: Record<string, GLProperty>, varyingProperties: Record<string, GLTypeInfo>): ShaderMaterial;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { InstancedBufferGeometry, BufferAttribute, Float32BufferAttribute, ShaderMaterial } from 'three';
|
|
2
|
+
import { FXColor } from '../color/FXColor.js';
|
|
3
|
+
import { checkSRGBSupport } from '../miscellaneous/webglCapabilities.js';
|
|
4
|
+
import { resolveGLSLTypeInfo } from './shared.js';
|
|
5
|
+
|
|
6
|
+
const INSTANCED_PARTICLE_GEOMETRY = (() => {
|
|
7
|
+
const geometry = new InstancedBufferGeometry();
|
|
8
|
+
const indices = new Uint16Array([0, 2, 1, 2, 3, 1]);
|
|
9
|
+
geometry.setIndex(new BufferAttribute(indices, 1));
|
|
10
|
+
const positions = new Float32Array([-0.5, 0.5, 0, 0.5, 0.5, 0, -0.5, -0.5, 0, 0.5, -0.5, 0]);
|
|
11
|
+
geometry.setAttribute("position", new Float32BufferAttribute(positions, 3));
|
|
12
|
+
const uvs = new Float32Array([0, 1, 1, 1, 0, 0, 1, 0]);
|
|
13
|
+
geometry.setAttribute("uv", new Float32BufferAttribute(uvs, 2));
|
|
14
|
+
return geometry;
|
|
15
|
+
})();
|
|
16
|
+
function collectProperties(keeper, modules, debugContext) {
|
|
17
|
+
for (const module of modules) {
|
|
18
|
+
if (module.requiredProperties === undefined) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
for (const key in module.requiredProperties) {
|
|
22
|
+
const existingTypeInfo = keeper[key];
|
|
23
|
+
const newTypeInfo = resolveGLSLTypeInfo(module.requiredProperties[key]);
|
|
24
|
+
if (existingTypeInfo === undefined) {
|
|
25
|
+
keeper[key] = newTypeInfo;
|
|
26
|
+
}
|
|
27
|
+
else if (existingTypeInfo.glslTypeName !== newTypeInfo.glslTypeName) {
|
|
28
|
+
throw new Error(`${debugContext}: property conflict for "${key}"`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function collectUniforms(keeper, modules, debugContext) {
|
|
34
|
+
for (const module of modules) {
|
|
35
|
+
for (const key in module.requiredUniforms) {
|
|
36
|
+
const value = module.requiredUniforms[key];
|
|
37
|
+
const existingGLProperty = keeper[key];
|
|
38
|
+
const newPropertyTypeInfo = resolveGLSLTypeInfo(value);
|
|
39
|
+
if (existingGLProperty === undefined) {
|
|
40
|
+
keeper[key] = { value, glslTypeInfo: newPropertyTypeInfo };
|
|
41
|
+
}
|
|
42
|
+
else if (existingGLProperty.glslTypeInfo.glslTypeName !== newPropertyTypeInfo.glslTypeName) {
|
|
43
|
+
throw new Error(`${debugContext}: property conflict for "${key}"`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function buildParticleVertexShader(uniformDeclarations, attributeDeclarations, varyingDeclarations, vertexAssignments) {
|
|
49
|
+
return `
|
|
50
|
+
#define PARTICLE_POSITION_X p_builtin[0][0]
|
|
51
|
+
#define PARTICLE_POSITION_Y p_builtin[0][1]
|
|
52
|
+
#define PARTICLE_POSITION_Z p_builtin[0][2]
|
|
53
|
+
|
|
54
|
+
#define PARTICLE_VELOCITY_X p_builtin[0][3]
|
|
55
|
+
#define PARTICLE_VELOCITY_Y p_builtin[1][0]
|
|
56
|
+
#define PARTICLE_VELOCITY_Z p_builtin[1][1]
|
|
57
|
+
|
|
58
|
+
#define PARTICLE_SCALE_X p_builtin[1][2]
|
|
59
|
+
#define PARTICLE_SCALE_Y p_builtin[1][3]
|
|
60
|
+
#define PARTICLE_SCALE_Z p_builtin[2][0]
|
|
61
|
+
|
|
62
|
+
#define PARTICLE_ROTATION p_builtin[2][1]
|
|
63
|
+
#define PARTICLE_TORQUE p_builtin[2][2]
|
|
64
|
+
|
|
65
|
+
#define PARTICLE_LIFETIME p_builtin[2][3]
|
|
66
|
+
#define PARTICLE_AGE p_builtin[3][0]
|
|
67
|
+
|
|
68
|
+
#define PARTICLE_RANDOM_A p_builtin[3][1]
|
|
69
|
+
#define PARTICLE_RANDOM_B p_builtin[3][2]
|
|
70
|
+
#define PARTICLE_RANDOM_C p_builtin[3][3]
|
|
71
|
+
|
|
72
|
+
// User attributes
|
|
73
|
+
${attributeDeclarations.join("\n")}
|
|
74
|
+
|
|
75
|
+
// Uniforms
|
|
76
|
+
${uniformDeclarations.join("\n")}
|
|
77
|
+
|
|
78
|
+
// Builtin varyings
|
|
79
|
+
varying vec2 p_uv;
|
|
80
|
+
|
|
81
|
+
// User varyings
|
|
82
|
+
${varyingDeclarations.join("\n")}
|
|
83
|
+
|
|
84
|
+
void main() {
|
|
85
|
+
${vertexAssignments.join("\n")}
|
|
86
|
+
|
|
87
|
+
p_uv = uv;
|
|
88
|
+
|
|
89
|
+
vec2 transformedPosition = position.xy;
|
|
90
|
+
transformedPosition.x *= PARTICLE_SCALE_X;
|
|
91
|
+
transformedPosition.y *= PARTICLE_SCALE_Y;
|
|
92
|
+
|
|
93
|
+
float cosR = cos(PARTICLE_ROTATION);
|
|
94
|
+
float sinR = sin(PARTICLE_ROTATION);
|
|
95
|
+
|
|
96
|
+
transformedPosition = vec2(
|
|
97
|
+
transformedPosition.x * cosR - transformedPosition.y * sinR,
|
|
98
|
+
transformedPosition.x * sinR + transformedPosition.y * cosR
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
vec3 center = vec3(
|
|
102
|
+
PARTICLE_POSITION_X,
|
|
103
|
+
PARTICLE_POSITION_Y,
|
|
104
|
+
PARTICLE_POSITION_Z
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
vec4 mvCenter = modelViewMatrix * vec4(center, 1.0);
|
|
108
|
+
|
|
109
|
+
mvCenter.xy += transformedPosition;
|
|
110
|
+
|
|
111
|
+
gl_Position = projectionMatrix * mvCenter;
|
|
112
|
+
}
|
|
113
|
+
`;
|
|
114
|
+
}
|
|
115
|
+
function buildParticleFragmentShader(uniformDeclarations, varyingDeclarations, sources) {
|
|
116
|
+
const renamedSources = sources.map((source, index) => {
|
|
117
|
+
return source.replace(/vec4\s+draw\s*\(\s*\)/g, `vec4 draw${index}()`);
|
|
118
|
+
});
|
|
119
|
+
let drawCalls = "";
|
|
120
|
+
for (let i = 0; i < sources.length; i++) {
|
|
121
|
+
drawCalls += i !== 0 ? " * " : "";
|
|
122
|
+
drawCalls += `draw${i}()`;
|
|
123
|
+
}
|
|
124
|
+
return `
|
|
125
|
+
// Defines
|
|
126
|
+
#define PI 3.14159265359
|
|
127
|
+
#define SRGB_SUPPORTED ${checkSRGBSupport() ? "1" : "0"}
|
|
128
|
+
|
|
129
|
+
#define PARTICLE_POSITION_X p_builtin[0][0]
|
|
130
|
+
#define PARTICLE_POSITION_Y p_builtin[0][1]
|
|
131
|
+
#define PARTICLE_POSITION_Z p_builtin[0][2]
|
|
132
|
+
|
|
133
|
+
#define PARTICLE_VELOCITY_X p_builtin[0][3]
|
|
134
|
+
#define PARTICLE_VELOCITY_Y p_builtin[1][0]
|
|
135
|
+
#define PARTICLE_VELOCITY_Z p_builtin[1][1]
|
|
136
|
+
|
|
137
|
+
#define PARTICLE_SCALE_X p_builtin[1][2]
|
|
138
|
+
#define PARTICLE_SCALE_Y p_builtin[1][3]
|
|
139
|
+
#define PARTICLE_SCALE_Z p_builtin[2][0]
|
|
140
|
+
|
|
141
|
+
#define PARTICLE_ROTATION p_builtin[2][1]
|
|
142
|
+
#define PARTICLE_TORQUE p_builtin[2][2]
|
|
143
|
+
|
|
144
|
+
#define PARTICLE_LIFETIME p_builtin[2][3]
|
|
145
|
+
#define PARTICLE_AGE p_builtin[3][0]
|
|
146
|
+
|
|
147
|
+
#define PARTICLE_RANDOM_A p_builtin[3][1]
|
|
148
|
+
#define PARTICLE_RANDOM_B p_builtin[3][2]
|
|
149
|
+
#define PARTICLE_RANDOM_C p_builtin[3][3]
|
|
150
|
+
|
|
151
|
+
// Uniforms
|
|
152
|
+
${uniformDeclarations.join("\n")}
|
|
153
|
+
|
|
154
|
+
// Builtin varyings
|
|
155
|
+
varying vec2 p_uv;
|
|
156
|
+
|
|
157
|
+
// User varyings
|
|
158
|
+
${varyingDeclarations.join("\n")}
|
|
159
|
+
|
|
160
|
+
#include <alphahash_pars_fragment>
|
|
161
|
+
|
|
162
|
+
// sRGB decode helper
|
|
163
|
+
vec4 srgbTexture2D(sampler2D textureSampler, vec2 uv) {
|
|
164
|
+
#if SRGB_SUPPORTED
|
|
165
|
+
return texture2D(textureSampler, uv);
|
|
166
|
+
#else
|
|
167
|
+
vec4 textureValue = texture2D(textureSampler, uv);
|
|
168
|
+
return vec4(pow(textureValue.rgb, vec3(2.2)), textureValue.a);
|
|
169
|
+
#endif
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Source must define vec4 draw() function
|
|
173
|
+
${renamedSources.join("\n")}
|
|
174
|
+
|
|
175
|
+
void main() {
|
|
176
|
+
vec4 diffuseColor = ${drawCalls};
|
|
177
|
+
if (diffuseColor.a < 0.0035) {
|
|
178
|
+
discard;
|
|
179
|
+
}
|
|
180
|
+
#ifdef USE_ALPHAHASH
|
|
181
|
+
if (diffuseColor.a < getAlphaHashThreshold(p_position)) {
|
|
182
|
+
discard;
|
|
183
|
+
}
|
|
184
|
+
#endif
|
|
185
|
+
gl_FragColor = linearToOutputTexel(diffuseColor);
|
|
186
|
+
}
|
|
187
|
+
`;
|
|
188
|
+
}
|
|
189
|
+
function buildParticleMaterial(sources, uniformProperties, varyingProperties) {
|
|
190
|
+
const uniforms = {};
|
|
191
|
+
const uniformDeclarations = [];
|
|
192
|
+
const attributeDeclarations = [];
|
|
193
|
+
const varyingDeclarations = [];
|
|
194
|
+
const vertexAssignments = [];
|
|
195
|
+
for (const name in uniformProperties) {
|
|
196
|
+
const { value, glslTypeInfo } = uniformProperties[name];
|
|
197
|
+
uniforms[`p_${name}`] = { value: value instanceof FXColor ? value.toGLSLColor() : value };
|
|
198
|
+
uniformDeclarations.push(`uniform ${glslTypeInfo.glslTypeName} p_${name};`);
|
|
199
|
+
}
|
|
200
|
+
for (const name in varyingProperties) {
|
|
201
|
+
const { glslTypeName } = varyingProperties[name];
|
|
202
|
+
attributeDeclarations.push(`attribute ${glslTypeName} a_${name};`);
|
|
203
|
+
varyingDeclarations.push(`varying ${glslTypeName} p_${name};`);
|
|
204
|
+
vertexAssignments.push(`p_${name} = a_${name};`);
|
|
205
|
+
}
|
|
206
|
+
return new ShaderMaterial({
|
|
207
|
+
uniforms,
|
|
208
|
+
vertexShader: buildParticleVertexShader(uniformDeclarations, attributeDeclarations, varyingDeclarations, vertexAssignments),
|
|
209
|
+
fragmentShader: buildParticleFragmentShader(uniformDeclarations, varyingDeclarations, sources),
|
|
210
|
+
transparent: true,
|
|
211
|
+
depthWrite: false,
|
|
212
|
+
depthTest: true,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export { INSTANCED_PARTICLE_GEOMETRY, buildParticleFragmentShader, buildParticleMaterial, buildParticleVertexShader, collectProperties, collectUniforms };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { InstancedBufferAttribute, Mesh } from "three";
|
|
2
|
+
import { type GLProperty, type GLTypeInfo } from "./shared";
|
|
3
|
+
export declare class FXInstancedParticle extends Mesh {
|
|
4
|
+
private readonly capacityStep;
|
|
5
|
+
readonly propertyBuffers: Record<string, InstancedBufferAttribute>;
|
|
6
|
+
private readonly instancedGeometry;
|
|
7
|
+
private readonly shaderMaterial;
|
|
8
|
+
private capacity;
|
|
9
|
+
constructor(sources: string[], uniforms: Record<string, GLProperty>, varyings: Record<string, GLTypeInfo>, expectedCapacity: number, capacityStep: number);
|
|
10
|
+
get instanceCount(): number;
|
|
11
|
+
createInstances(count: number): void;
|
|
12
|
+
setOrigin(x: number, y: number): void;
|
|
13
|
+
removeDeadParticles(): void;
|
|
14
|
+
drop(): void;
|
|
15
|
+
destroy(): void;
|
|
16
|
+
private ensureCapacity;
|
|
17
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Mesh, InstancedBufferAttribute, StreamDrawUsage } from 'three';
|
|
2
|
+
import { BUILTIN_OFFSET_AGE, BUILTIN_OFFSET_LIFETIME } from '../miscellaneous/miscellaneous.js';
|
|
3
|
+
import { INSTANCED_PARTICLE_GEOMETRY, buildParticleMaterial } from './FXInstancedParticle.Internal.js';
|
|
4
|
+
import { resolvePropertyUniform } from './shared.js';
|
|
5
|
+
|
|
6
|
+
class FXInstancedParticle extends Mesh {
|
|
7
|
+
constructor(sources, uniforms, varyings, expectedCapacity, _private_capacityStep) {
|
|
8
|
+
const instancedGeometry = INSTANCED_PARTICLE_GEOMETRY.clone();
|
|
9
|
+
const shaderMaterial = buildParticleMaterial(sources, uniforms, varyings);
|
|
10
|
+
super(instancedGeometry, shaderMaterial);
|
|
11
|
+
this._private_capacityStep = _private_capacityStep;
|
|
12
|
+
this.propertyBuffers = {};
|
|
13
|
+
this.frustumCulled = false;
|
|
14
|
+
this.matrixAutoUpdate = false;
|
|
15
|
+
this.matrixWorldAutoUpdate = false;
|
|
16
|
+
this._private_instancedGeometry = instancedGeometry;
|
|
17
|
+
this._private_instancedGeometry.instanceCount = 0;
|
|
18
|
+
this._private_shaderMaterial = shaderMaterial;
|
|
19
|
+
this._private_capacity = Math.max(Math.ceil(expectedCapacity / this._private_capacityStep) * this._private_capacityStep, this._private_capacityStep);
|
|
20
|
+
for (const name in varyings) {
|
|
21
|
+
const { bufferSize } = varyings[name];
|
|
22
|
+
const attribute = new InstancedBufferAttribute(new Float32Array(this._private_capacity * bufferSize), bufferSize);
|
|
23
|
+
attribute.setUsage(StreamDrawUsage);
|
|
24
|
+
this._private_instancedGeometry.setAttribute(`a_${name}`, attribute);
|
|
25
|
+
this.propertyBuffers[name] = attribute;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
get instanceCount() {
|
|
29
|
+
return this._private_instancedGeometry.instanceCount;
|
|
30
|
+
}
|
|
31
|
+
createInstances(count) {
|
|
32
|
+
const currentInstanceCount = this._private_instancedGeometry.instanceCount;
|
|
33
|
+
this._private_ensureCapacity(currentInstanceCount + count);
|
|
34
|
+
this._private_instancedGeometry.instanceCount += count;
|
|
35
|
+
}
|
|
36
|
+
setOrigin(x, y) {
|
|
37
|
+
const uniform = resolvePropertyUniform("origin", this._private_shaderMaterial);
|
|
38
|
+
uniform.value.set(x, y);
|
|
39
|
+
}
|
|
40
|
+
removeDeadParticles() {
|
|
41
|
+
const builtinBuffer = this.propertyBuffers.builtin;
|
|
42
|
+
if (builtinBuffer === undefined) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const { array: builtinArray, itemSize: builtinItemSize } = builtinBuffer;
|
|
46
|
+
const { instanceCount } = this._private_instancedGeometry;
|
|
47
|
+
let writeIndex = 0;
|
|
48
|
+
for (let readIndex = 0; readIndex < instanceCount; readIndex++) {
|
|
49
|
+
const offset = readIndex * builtinItemSize;
|
|
50
|
+
if (builtinArray[offset + BUILTIN_OFFSET_AGE] < builtinArray[offset + BUILTIN_OFFSET_LIFETIME]) {
|
|
51
|
+
if (writeIndex !== readIndex) {
|
|
52
|
+
for (const name in this.propertyBuffers) {
|
|
53
|
+
const attribute = this.propertyBuffers[name];
|
|
54
|
+
const { itemSize: dataItemSize, array: dataArray } = attribute;
|
|
55
|
+
const srcOffset = readIndex * dataItemSize;
|
|
56
|
+
const dstOffset = writeIndex * dataItemSize;
|
|
57
|
+
dataArray.copyWithin(dstOffset, srcOffset, srcOffset + dataItemSize);
|
|
58
|
+
attribute.needsUpdate = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
writeIndex++;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
this._private_instancedGeometry.instanceCount = writeIndex;
|
|
65
|
+
}
|
|
66
|
+
drop() {
|
|
67
|
+
this._private_instancedGeometry.instanceCount = 0;
|
|
68
|
+
}
|
|
69
|
+
destroy() {
|
|
70
|
+
this._private_instancedGeometry.dispose();
|
|
71
|
+
this._private_shaderMaterial.dispose();
|
|
72
|
+
}
|
|
73
|
+
_private_ensureCapacity(requiredCapacity) {
|
|
74
|
+
if (requiredCapacity <= this._private_capacity) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const newCapacity = Math.ceil(requiredCapacity / this._private_capacityStep) * this._private_capacityStep;
|
|
78
|
+
for (const name in this.propertyBuffers) {
|
|
79
|
+
const { itemSize, array, usage } = this.propertyBuffers[name];
|
|
80
|
+
const newArray = new Float32Array(newCapacity * itemSize);
|
|
81
|
+
newArray.set(array);
|
|
82
|
+
const newAttribute = new InstancedBufferAttribute(newArray, itemSize);
|
|
83
|
+
newAttribute.setUsage(usage);
|
|
84
|
+
this._private_instancedGeometry.setAttribute(`a_${name}`, newAttribute);
|
|
85
|
+
this.propertyBuffers[name] = newAttribute;
|
|
86
|
+
}
|
|
87
|
+
this._private_capacity = newCapacity;
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- old three.js compatibility fix
|
|
89
|
+
this._private_instancedGeometry._maxInstanceCount = newCapacity;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { FXInstancedParticle };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { IUniform, ShaderMaterial } from "three";
|
|
2
|
+
import { Matrix3, Matrix4, Texture, Vector2, Vector3, Vector4 } from "three";
|
|
3
|
+
import { FXColor } from "../color/FXColor";
|
|
4
|
+
import type { FXTransparencyMode } from "../miscellaneous/FXTransparencyMode";
|
|
5
|
+
export type FXProperty = Texture | FXColor | Vector2 | Vector3 | Vector4 | Matrix3 | Matrix4 | number;
|
|
6
|
+
export type FXPropertyName = "Texture" | "UIColor" | "Vector2" | "Vector3" | "Vector4" | "Matrix3" | "Matrix4" | "number";
|
|
7
|
+
export type FXPropertyConstructor = typeof Texture | typeof FXColor | typeof Vector2 | typeof Vector3 | typeof Vector4 | typeof Matrix3 | typeof Matrix4 | NumberConstructor;
|
|
8
|
+
export type FXPropertyCopyTo = FXColor | Vector2 | Vector3 | Vector4 | Matrix3 | Matrix4;
|
|
9
|
+
export type FXPropertyCopyFrom = FXColor & Vector2 & Vector3 & Vector4 & Matrix3 & Matrix4;
|
|
10
|
+
export interface GLTypeInfo {
|
|
11
|
+
glslTypeName: string;
|
|
12
|
+
bufferSize: number;
|
|
13
|
+
instantiable: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface GLProperty {
|
|
16
|
+
value: FXProperty;
|
|
17
|
+
glslTypeInfo: GLTypeInfo;
|
|
18
|
+
}
|
|
19
|
+
export interface PlaneData {
|
|
20
|
+
source: string;
|
|
21
|
+
properties: Record<string, GLProperty>;
|
|
22
|
+
transform: Matrix4;
|
|
23
|
+
visibility: boolean;
|
|
24
|
+
transparency: FXTransparencyMode;
|
|
25
|
+
}
|
|
26
|
+
export declare function resolveGLSLTypeInfo(value: FXProperty | FXPropertyName | FXPropertyConstructor): GLTypeInfo;
|
|
27
|
+
export declare function resolvePropertyUniform(name: string, material: ShaderMaterial): IUniform<FXProperty>;
|