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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/dist/behaviorModules/FXBehaviorDirectionalGravity.d.ts +21 -0
  3. package/dist/behaviorModules/FXBehaviorDirectionalGravity.js +47 -0
  4. package/dist/behaviorModules/FXBehaviorModule.d.ts +14 -0
  5. package/dist/behaviorModules/FXBehaviorModule.js +9 -0
  6. package/dist/behaviorModules/FXBehaviorPointGravity.d.ts +39 -0
  7. package/dist/behaviorModules/FXBehaviorPointGravity.js +110 -0
  8. package/dist/behaviorModules/FXBehaviorScaleOverLife.d.ts +22 -0
  9. package/dist/behaviorModules/FXBehaviorScaleOverLife.js +66 -0
  10. package/dist/behaviorModules/FXBehaviorTorqueDamping.d.ts +20 -0
  11. package/dist/behaviorModules/FXBehaviorTorqueDamping.js +48 -0
  12. package/dist/behaviorModules/FXBehaviorTorqueNoise.d.ts +26 -0
  13. package/dist/behaviorModules/FXBehaviorTorqueNoise.js +66 -0
  14. package/dist/behaviorModules/FXBehaviorVelocityDamping.d.ts +20 -0
  15. package/dist/behaviorModules/FXBehaviorVelocityDamping.js +49 -0
  16. package/dist/behaviorModules/FXBehaviorVelocityNoise.d.ts +26 -0
  17. package/dist/behaviorModules/FXBehaviorVelocityNoise.js +73 -0
  18. package/dist/color/FXColor.Internal.d.ts +7 -0
  19. package/dist/color/FXColor.Internal.js +42 -0
  20. package/dist/color/FXColor.d.ts +234 -0
  21. package/dist/color/FXColor.js +636 -0
  22. package/dist/elements/FXEmitter.Internal.d.ts +10 -0
  23. package/dist/elements/FXEmitter.Internal.js +5 -0
  24. package/dist/elements/FXEmitter.d.ts +29 -0
  25. package/dist/elements/FXEmitter.js +154 -0
  26. package/dist/index.d.ts +18 -0
  27. package/dist/index.js +18 -0
  28. package/dist/instancedParticle/FXInstancedParticle.Internal.d.ts +13 -0
  29. package/dist/instancedParticle/FXInstancedParticle.Internal.js +216 -0
  30. package/dist/instancedParticle/FXInstancedParticle.d.ts +17 -0
  31. package/dist/instancedParticle/FXInstancedParticle.js +93 -0
  32. package/dist/instancedParticle/shared.d.ts +27 -0
  33. package/dist/instancedParticle/shared.js +123 -0
  34. package/dist/miscellaneous/FXTransparencyMode.d.ts +7 -0
  35. package/dist/miscellaneous/asserts.d.ts +21 -0
  36. package/dist/miscellaneous/asserts.js +48 -0
  37. package/dist/miscellaneous/math.d.ts +8 -0
  38. package/dist/miscellaneous/math.js +11 -0
  39. package/dist/miscellaneous/miscellaneous.d.ts +33 -0
  40. package/dist/miscellaneous/miscellaneous.js +66 -0
  41. package/dist/miscellaneous/texture/FXTextureView.Internal.d.ts +49 -0
  42. package/dist/miscellaneous/texture/FXTextureView.Internal.js +6 -0
  43. package/dist/miscellaneous/texture/FXTextureView.d.ts +72 -0
  44. package/dist/miscellaneous/texture/FXTextureView.js +234 -0
  45. package/dist/miscellaneous/webglCapabilities.d.ts +1 -0
  46. package/dist/miscellaneous/webglCapabilities.js +45 -0
  47. package/dist/renderingModules/FXRenderingColorOverLife.d.ts +14 -0
  48. package/dist/renderingModules/FXRenderingColorOverLife.js +32 -0
  49. package/dist/renderingModules/FXRenderingColorOverVelocity.d.ts +15 -0
  50. package/dist/renderingModules/FXRenderingColorOverVelocity.js +37 -0
  51. package/dist/renderingModules/FXRenderingModule.d.ts +8 -0
  52. package/dist/renderingModules/FXRenderingModule.js +9 -0
  53. package/dist/renderingModules/FXRenderingTexture.d.ts +14 -0
  54. package/dist/renderingModules/FXRenderingTexture.js +26 -0
  55. package/dist/shaders/FXRenderingColorOverLife.glsl.js +3 -0
  56. package/dist/shaders/FXRenderingColorOverVelocity.glsl.js +3 -0
  57. package/dist/shaders/FXRenderingTexture.glsl.js +3 -0
  58. package/dist/spawnModules/FXSpawnBox.d.ts +27 -0
  59. package/dist/spawnModules/FXSpawnBox.js +67 -0
  60. package/dist/spawnModules/FXSpawnModule.d.ts +9 -0
  61. package/dist/spawnModules/FXSpawnModule.js +9 -0
  62. package/dist/spawnModules/FXSpawnOffset.d.ts +21 -0
  63. package/dist/spawnModules/FXSpawnOffset.js +49 -0
  64. package/dist/spawnModules/FXSpawnRandomLifetime.d.ts +20 -0
  65. package/dist/spawnModules/FXSpawnRandomLifetime.js +47 -0
  66. package/dist/spawnModules/FXSpawnRandomRotation.d.ts +20 -0
  67. package/dist/spawnModules/FXSpawnRandomRotation.js +45 -0
  68. package/dist/spawnModules/FXSpawnRandomScale.d.ts +26 -0
  69. package/dist/spawnModules/FXSpawnRandomScale.js +60 -0
  70. package/dist/spawnModules/FXSpawnRandomTorque.d.ts +20 -0
  71. package/dist/spawnModules/FXSpawnRandomTorque.js +45 -0
  72. package/dist/spawnModules/FXSpawnRandomVelocity.d.ts +26 -0
  73. package/dist/spawnModules/FXSpawnRandomVelocity.js +64 -0
  74. 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 };
@@ -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>;