@codexo/exojs-particles 0.12.0
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/README.md +129 -0
- package/dist/esm/ParticleSystem.d.ts +212 -0
- package/dist/esm/ParticleSystem.js +570 -0
- package/dist/esm/ParticleSystem.js.map +1 -0
- package/dist/esm/distributions/BoxArea.d.ts +17 -0
- package/dist/esm/distributions/BoxArea.js +48 -0
- package/dist/esm/distributions/BoxArea.js.map +1 -0
- package/dist/esm/distributions/CircleArea.d.ts +19 -0
- package/dist/esm/distributions/CircleArea.js +33 -0
- package/dist/esm/distributions/CircleArea.js.map +1 -0
- package/dist/esm/distributions/ColorGradient.d.ts +40 -0
- package/dist/esm/distributions/ColorGradient.js +72 -0
- package/dist/esm/distributions/ColorGradient.js.map +1 -0
- package/dist/esm/distributions/ConeDirection.d.ts +28 -0
- package/dist/esm/distributions/ConeDirection.js +44 -0
- package/dist/esm/distributions/ConeDirection.js.map +1 -0
- package/dist/esm/distributions/Constant.d.ts +17 -0
- package/dist/esm/distributions/Constant.js +35 -0
- package/dist/esm/distributions/Constant.js.map +1 -0
- package/dist/esm/distributions/Curve.d.ts +30 -0
- package/dist/esm/distributions/Curve.js +53 -0
- package/dist/esm/distributions/Curve.js.map +1 -0
- package/dist/esm/distributions/Distribution.d.ts +45 -0
- package/dist/esm/distributions/LineSegment.d.ts +15 -0
- package/dist/esm/distributions/LineSegment.js +27 -0
- package/dist/esm/distributions/LineSegment.js.map +1 -0
- package/dist/esm/distributions/Range.d.ts +12 -0
- package/dist/esm/distributions/Range.js +19 -0
- package/dist/esm/distributions/Range.js.map +1 -0
- package/dist/esm/distributions/VectorRange.d.ts +20 -0
- package/dist/esm/distributions/VectorRange.js +31 -0
- package/dist/esm/distributions/VectorRange.js.map +1 -0
- package/dist/esm/distributions/index.d.ts +12 -0
- package/dist/esm/gpu/ParticleGpuState.d.ts +57 -0
- package/dist/esm/gpu/ParticleGpuState.js +535 -0
- package/dist/esm/gpu/ParticleGpuState.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +32 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/modules/AlphaFadeOverLifetime.d.ts +24 -0
- package/dist/esm/modules/AlphaFadeOverLifetime.js +64 -0
- package/dist/esm/modules/AlphaFadeOverLifetime.js.map +1 -0
- package/dist/esm/modules/ApplyForce.d.ts +20 -0
- package/dist/esm/modules/ApplyForce.js +48 -0
- package/dist/esm/modules/ApplyForce.js.map +1 -0
- package/dist/esm/modules/AttractToPoint.d.ts +27 -0
- package/dist/esm/modules/AttractToPoint.js +73 -0
- package/dist/esm/modules/AttractToPoint.js.map +1 -0
- package/dist/esm/modules/BurstSpawn.d.ts +53 -0
- package/dist/esm/modules/BurstSpawn.js +93 -0
- package/dist/esm/modules/BurstSpawn.js.map +1 -0
- package/dist/esm/modules/ColorOverLifetime.d.ts +22 -0
- package/dist/esm/modules/ColorOverLifetime.js +65 -0
- package/dist/esm/modules/ColorOverLifetime.js.map +1 -0
- package/dist/esm/modules/ColorOverSpeed.d.ts +27 -0
- package/dist/esm/modules/ColorOverSpeed.js +86 -0
- package/dist/esm/modules/ColorOverSpeed.js.map +1 -0
- package/dist/esm/modules/DeathModule.d.ts +24 -0
- package/dist/esm/modules/DeathModule.js +25 -0
- package/dist/esm/modules/DeathModule.js.map +1 -0
- package/dist/esm/modules/Drag.d.ts +20 -0
- package/dist/esm/modules/Drag.js +43 -0
- package/dist/esm/modules/Drag.js.map +1 -0
- package/dist/esm/modules/OrbitalForce.d.ts +28 -0
- package/dist/esm/modules/OrbitalForce.js +65 -0
- package/dist/esm/modules/OrbitalForce.js.map +1 -0
- package/dist/esm/modules/RateSpawn.d.ts +41 -0
- package/dist/esm/modules/RateSpawn.js +75 -0
- package/dist/esm/modules/RateSpawn.js.map +1 -0
- package/dist/esm/modules/RepelFromPoint.d.ts +24 -0
- package/dist/esm/modules/RepelFromPoint.js +76 -0
- package/dist/esm/modules/RepelFromPoint.js.map +1 -0
- package/dist/esm/modules/RotateOverLifetime.d.ts +20 -0
- package/dist/esm/modules/RotateOverLifetime.js +41 -0
- package/dist/esm/modules/RotateOverLifetime.js.map +1 -0
- package/dist/esm/modules/ScaleOverLifetime.d.ts +26 -0
- package/dist/esm/modules/ScaleOverLifetime.js +59 -0
- package/dist/esm/modules/ScaleOverLifetime.js.map +1 -0
- package/dist/esm/modules/SpawnModule.d.ts +30 -0
- package/dist/esm/modules/SpawnModule.js +31 -0
- package/dist/esm/modules/SpawnModule.js.map +1 -0
- package/dist/esm/modules/SpawnOnDeath.d.ts +24 -0
- package/dist/esm/modules/SpawnOnDeath.js +47 -0
- package/dist/esm/modules/SpawnOnDeath.js.map +1 -0
- package/dist/esm/modules/Turbulence.d.ts +30 -0
- package/dist/esm/modules/Turbulence.js +122 -0
- package/dist/esm/modules/Turbulence.js.map +1 -0
- package/dist/esm/modules/UpdateModule.d.ts +95 -0
- package/dist/esm/modules/UpdateModule.js +66 -0
- package/dist/esm/modules/UpdateModule.js.map +1 -0
- package/dist/esm/modules/VelocityOverLifetime.d.ts +30 -0
- package/dist/esm/modules/VelocityOverLifetime.js +84 -0
- package/dist/esm/modules/VelocityOverLifetime.js.map +1 -0
- package/dist/esm/modules/WgslContribution.d.ts +81 -0
- package/dist/esm/modules/WgslContribution.js +34 -0
- package/dist/esm/modules/WgslContribution.js.map +1 -0
- package/dist/esm/modules/index.d.ts +22 -0
- package/dist/esm/particlesBuildInfo.d.ts +15 -0
- package/dist/esm/particlesBuildInfo.js +8 -0
- package/dist/esm/particlesBuildInfo.js.map +1 -0
- package/dist/esm/particlesExtension.d.ts +24 -0
- package/dist/esm/particlesExtension.js +49 -0
- package/dist/esm/particlesExtension.js.map +1 -0
- package/dist/esm/public.d.ts +9 -0
- package/dist/esm/register.d.ts +1 -0
- package/dist/esm/register.js +43 -0
- package/dist/esm/register.js.map +1 -0
- package/dist/esm/renderers/WebGl2ParticleRenderer.d.ts +45 -0
- package/dist/esm/renderers/WebGl2ParticleRenderer.js +325 -0
- package/dist/esm/renderers/WebGl2ParticleRenderer.js.map +1 -0
- package/dist/esm/renderers/WebGpuParticleRenderer.d.ts +48 -0
- package/dist/esm/renderers/WebGpuParticleRenderer.js +553 -0
- package/dist/esm/renderers/WebGpuParticleRenderer.js.map +1 -0
- package/dist/esm/renderers/glsl/particle.frag.js +4 -0
- package/dist/esm/renderers/glsl/particle.frag.js.map +1 -0
- package/dist/esm/renderers/glsl/particle.vert.js +4 -0
- package/dist/esm/renderers/glsl/particle.vert.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Curve } from "#distributions/Curve";
|
|
2
|
+
import type { ParticleSystem } from "#ParticleSystem";
|
|
3
|
+
import { UpdateModule } from './UpdateModule';
|
|
4
|
+
import type { WgslContribution } from './WgslContribution';
|
|
5
|
+
/**
|
|
6
|
+
* Fades only the alpha channel over a particle's lifetime, leaving RGB
|
|
7
|
+
* untouched. Pair with a spawn-time tint or a separate `ColorOverLifetime`
|
|
8
|
+
* to keep the color layer stable while controlling opacity from a single
|
|
9
|
+
* curve.
|
|
10
|
+
*
|
|
11
|
+
* The default curve `1 → 0` produces a linear fade-out. For a fade-in then
|
|
12
|
+
* fade-out, pass a curve like `[0,0]→[0.5,1]→[1,0]`.
|
|
13
|
+
*
|
|
14
|
+
* GPU-eligible: uploads the curve as a 256-tap 1D R32F texture; alpha is
|
|
15
|
+
* resampled per-particle in the compute shader and stitched into the
|
|
16
|
+
* existing color word with a single mask + shift.
|
|
17
|
+
*/
|
|
18
|
+
export declare class AlphaFadeOverLifetime extends UpdateModule {
|
|
19
|
+
curve: Curve;
|
|
20
|
+
constructor(curve?: Curve);
|
|
21
|
+
apply(system: ParticleSystem, _dt: number): void;
|
|
22
|
+
wgsl(): WgslContribution;
|
|
23
|
+
uploadTextures(device: GPUDevice, textures: ReadonlyMap<string, GPUTexture>): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Curve } from '../distributions/Curve.js';
|
|
2
|
+
import { UpdateModule } from './UpdateModule.js';
|
|
3
|
+
|
|
4
|
+
/// <reference types="@webgpu/types" />
|
|
5
|
+
const lookupSize = 256;
|
|
6
|
+
/**
|
|
7
|
+
* Fades only the alpha channel over a particle's lifetime, leaving RGB
|
|
8
|
+
* untouched. Pair with a spawn-time tint or a separate `ColorOverLifetime`
|
|
9
|
+
* to keep the color layer stable while controlling opacity from a single
|
|
10
|
+
* curve.
|
|
11
|
+
*
|
|
12
|
+
* The default curve `1 → 0` produces a linear fade-out. For a fade-in then
|
|
13
|
+
* fade-out, pass a curve like `[0,0]→[0.5,1]→[1,0]`.
|
|
14
|
+
*
|
|
15
|
+
* GPU-eligible: uploads the curve as a 256-tap 1D R32F texture; alpha is
|
|
16
|
+
* resampled per-particle in the compute shader and stitched into the
|
|
17
|
+
* existing color word with a single mask + shift.
|
|
18
|
+
*/
|
|
19
|
+
class AlphaFadeOverLifetime extends UpdateModule {
|
|
20
|
+
curve;
|
|
21
|
+
constructor(curve = new Curve([
|
|
22
|
+
{ t: 0, v: 1 },
|
|
23
|
+
{ t: 1, v: 0 },
|
|
24
|
+
])) {
|
|
25
|
+
super();
|
|
26
|
+
this.curve = curve;
|
|
27
|
+
}
|
|
28
|
+
apply(system, _dt) {
|
|
29
|
+
const { color, elapsed, lifetime, liveCount } = system;
|
|
30
|
+
const curve = this.curve;
|
|
31
|
+
for (let i = 0; i < liveCount; i++) {
|
|
32
|
+
const t = elapsed[i] / lifetime[i];
|
|
33
|
+
const a = curve.evaluate(t);
|
|
34
|
+
const alphaByte = (Math.max(0, Math.min(1, a)) * 255) & 255;
|
|
35
|
+
color[i] = (color[i] & 0x00ffffff) | (alphaByte << 24);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
wgsl() {
|
|
39
|
+
return {
|
|
40
|
+
key: 'AlphaFadeOverLifetime',
|
|
41
|
+
textures: [{ name: 'curve', format: 'r32float' }],
|
|
42
|
+
body: `
|
|
43
|
+
let alphaT = clamp(timing[idx].x / max(timing[idx].y, 0.000001), 0.0, 1.0);
|
|
44
|
+
let alphaSample = textureSampleLevel(u_AlphaFadeOverLifetime_curve, u_AlphaFadeOverLifetime_curve_sampler, alphaT, 0.0).r;
|
|
45
|
+
let alphaByte = u32(clamp(alphaSample, 0.0, 1.0) * 255.0) & 255u;
|
|
46
|
+
color[idx] = (color[idx] & 0x00ffffffu) | (alphaByte << 24u);
|
|
47
|
+
`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
uploadTextures(device, textures) {
|
|
51
|
+
const texture = textures.get('curve');
|
|
52
|
+
if (texture === undefined) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const data = new Float32Array(lookupSize);
|
|
56
|
+
for (let i = 0; i < lookupSize; i++) {
|
|
57
|
+
data[i] = this.curve.evaluate(i / (lookupSize - 1));
|
|
58
|
+
}
|
|
59
|
+
device.queue.writeTexture({ texture }, data.buffer, { offset: 0, bytesPerRow: lookupSize * 4, rowsPerImage: 1 }, { width: lookupSize, height: 1, depthOrArrayLayers: 1 });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { AlphaFadeOverLifetime };
|
|
64
|
+
//# sourceMappingURL=AlphaFadeOverLifetime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AlphaFadeOverLifetime.js","sources":["../../../../src/modules/AlphaFadeOverLifetime.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAAA;AAQA,MAAM,UAAU,GAAG,GAAG;AAEtB;;;;;;;;;;;;AAYG;AACG,MAAO,qBAAsB,SAAQ,YAAY,CAAA;AAC9C,IAAA,KAAK;IAEZ,WAAA,CACE,KAAA,GAAe,IAAI,KAAK,CAAC;AACvB,QAAA,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACd,QAAA,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;KACf,CAAC,EAAA;AAEF,QAAA,KAAK,EAAE;AACP,QAAA,IAAI,CAAC,KAAK,GAAG,KAAK;IACpB;IAEgB,KAAK,CAAC,MAAsB,EAAE,GAAW,EAAA;QACvD,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,MAAM;AACtD,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK;AAExB,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE;YAClC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;YAClC,MAAM,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC3B,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,GAAG;AAE3D,YAAA,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,UAAU,KAAK,SAAS,IAAI,EAAE,CAAC;QACxD;IACF;IAEgB,IAAI,GAAA;QAClB,OAAO;AACL,YAAA,GAAG,EAAE,uBAAuB;YAC5B,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AACjD,YAAA,IAAI,EAAE;;;;;AAKC,YAAA,CAAA;SACR;IACH;IAEgB,cAAc,CAAC,MAAiB,EAAE,QAAyC,EAAA;QACzF,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC;AAErC,QAAA,IAAI,OAAO,KAAK,SAAS,EAAE;YACzB;QACF;AAEA,QAAA,MAAM,IAAI,GAAG,IAAI,YAAY,CAAC,UAAU,CAAC;AAEzC,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE;AACnC,YAAA,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC;QACrD;AAEA,QAAA,MAAM,CAAC,KAAK,CAAC,YAAY,CACvB,EAAE,OAAO,EAAE,EACX,IAAI,CAAC,MAAM,EACX,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,EAAE,UAAU,GAAG,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,EAC3D,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,CACxD;IACH;AACD;;;;"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ParticleSystem } from "#ParticleSystem";
|
|
2
|
+
import { UpdateModule } from './UpdateModule';
|
|
3
|
+
import type { WgslContribution } from './WgslContribution';
|
|
4
|
+
/**
|
|
5
|
+
* Adds a constant 2D acceleration to every live particle's velocity each
|
|
6
|
+
* frame. Use for gravity (`new ApplyForce(0, 980)`), wind, or any uniform
|
|
7
|
+
* force field. Force is applied to all particles equally — for per-particle
|
|
8
|
+
* variation, layer multiple ApplyForce modules with different gates.
|
|
9
|
+
*
|
|
10
|
+
* GPU-eligible: implements {@link UpdateModule.wgsl} so this module runs in
|
|
11
|
+
* the system's compute shader on WebGPU backends with no CPU readback.
|
|
12
|
+
*/
|
|
13
|
+
export declare class ApplyForce extends UpdateModule {
|
|
14
|
+
accelerationX: number;
|
|
15
|
+
accelerationY: number;
|
|
16
|
+
constructor(accelerationX: number, accelerationY: number);
|
|
17
|
+
apply(system: ParticleSystem, dt: number): void;
|
|
18
|
+
wgsl(): WgslContribution;
|
|
19
|
+
writeUniforms(view: DataView, offset: number): void;
|
|
20
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { UpdateModule } from './UpdateModule.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Adds a constant 2D acceleration to every live particle's velocity each
|
|
5
|
+
* frame. Use for gravity (`new ApplyForce(0, 980)`), wind, or any uniform
|
|
6
|
+
* force field. Force is applied to all particles equally — for per-particle
|
|
7
|
+
* variation, layer multiple ApplyForce modules with different gates.
|
|
8
|
+
*
|
|
9
|
+
* GPU-eligible: implements {@link UpdateModule.wgsl} so this module runs in
|
|
10
|
+
* the system's compute shader on WebGPU backends with no CPU readback.
|
|
11
|
+
*/
|
|
12
|
+
class ApplyForce extends UpdateModule {
|
|
13
|
+
accelerationX;
|
|
14
|
+
accelerationY;
|
|
15
|
+
constructor(accelerationX, accelerationY) {
|
|
16
|
+
super();
|
|
17
|
+
this.accelerationX = accelerationX;
|
|
18
|
+
this.accelerationY = accelerationY;
|
|
19
|
+
}
|
|
20
|
+
apply(system, dt) {
|
|
21
|
+
const { velX, velY, liveCount } = system;
|
|
22
|
+
const ax = this.accelerationX * dt;
|
|
23
|
+
const ay = this.accelerationY * dt;
|
|
24
|
+
for (let i = 0; i < liveCount; i++) {
|
|
25
|
+
velX[i] += ax;
|
|
26
|
+
velY[i] += ay;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
wgsl() {
|
|
30
|
+
return {
|
|
31
|
+
key: 'ApplyForce',
|
|
32
|
+
uniforms: [
|
|
33
|
+
{ name: 'ax', type: 'f32' },
|
|
34
|
+
{ name: 'ay', type: 'f32' },
|
|
35
|
+
],
|
|
36
|
+
body: `
|
|
37
|
+
velocities[idx] = velocities[idx] + vec2<f32>(modules.u_ApplyForce.ax, modules.u_ApplyForce.ay) * dt;
|
|
38
|
+
`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
writeUniforms(view, offset) {
|
|
42
|
+
view.setFloat32(offset + 0, this.accelerationX, true);
|
|
43
|
+
view.setFloat32(offset + 4, this.accelerationY, true);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { ApplyForce };
|
|
48
|
+
//# sourceMappingURL=ApplyForce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApplyForce.js","sources":["../../../../src/modules/ApplyForce.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAKA;;;;;;;;AAQG;AACG,MAAO,UAAW,SAAQ,YAAY,CAAA;AACnC,IAAA,aAAa;AACb,IAAA,aAAa;IAEpB,WAAA,CAAmB,aAAqB,EAAE,aAAqB,EAAA;AAC7D,QAAA,KAAK,EAAE;AACP,QAAA,IAAI,CAAC,aAAa,GAAG,aAAa;AAClC,QAAA,IAAI,CAAC,aAAa,GAAG,aAAa;IACpC;IAEgB,KAAK,CAAC,MAAsB,EAAE,EAAU,EAAA;QACtD,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,MAAM;AACxC,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,aAAa,GAAG,EAAE;AAClC,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,aAAa,GAAG,EAAE;AAElC,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE;AAClC,YAAA,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE;AACb,YAAA,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE;QACf;IACF;IAEgB,IAAI,GAAA;QAClB,OAAO;AACL,YAAA,GAAG,EAAE,YAAY;AACjB,YAAA,QAAQ,EAAE;AACR,gBAAA,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE;AAC3B,gBAAA,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE;AAC5B,aAAA;AACD,YAAA,IAAI,EAAE;;AAEC,YAAA,CAAA;SACR;IACH;IAEgB,aAAa,CAAC,IAAc,EAAE,MAAc,EAAA;AAC1D,QAAA,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC;AACrD,QAAA,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC;IACvD;AACD;;;;"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ParticleSystem } from "#ParticleSystem";
|
|
2
|
+
import { UpdateModule } from './UpdateModule';
|
|
3
|
+
import type { WgslContribution } from './WgslContribution';
|
|
4
|
+
/**
|
|
5
|
+
* Pulls every live particle toward a fixed point in the system's local
|
|
6
|
+
* coordinate space. Acceleration magnitude is `strength` (units / s²),
|
|
7
|
+
* applied along the direction `(point − particle)`. The optional `falloff`
|
|
8
|
+
* radius softens the pull near the center — particles within `falloff`
|
|
9
|
+
* units lerp the strength linearly to zero, preventing the singularity at
|
|
10
|
+
* `r = 0` from yielding infinite acceleration.
|
|
11
|
+
*
|
|
12
|
+
* Use cases: orbit anchors, pin emitters to a moving target, simulate a
|
|
13
|
+
* "black hole" pickup. For repulsion, use {@link RepelFromPoint}; for
|
|
14
|
+
* tangential motion, layer with {@link OrbitalForce}.
|
|
15
|
+
*
|
|
16
|
+
* GPU-eligible.
|
|
17
|
+
*/
|
|
18
|
+
export declare class AttractToPoint extends UpdateModule {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
strength: number;
|
|
22
|
+
falloff: number;
|
|
23
|
+
constructor(x: number, y: number, strength: number, falloff?: number);
|
|
24
|
+
apply(system: ParticleSystem, dt: number): void;
|
|
25
|
+
wgsl(): WgslContribution;
|
|
26
|
+
writeUniforms(view: DataView, offset: number): void;
|
|
27
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { UpdateModule } from './UpdateModule.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pulls every live particle toward a fixed point in the system's local
|
|
5
|
+
* coordinate space. Acceleration magnitude is `strength` (units / s²),
|
|
6
|
+
* applied along the direction `(point − particle)`. The optional `falloff`
|
|
7
|
+
* radius softens the pull near the center — particles within `falloff`
|
|
8
|
+
* units lerp the strength linearly to zero, preventing the singularity at
|
|
9
|
+
* `r = 0` from yielding infinite acceleration.
|
|
10
|
+
*
|
|
11
|
+
* Use cases: orbit anchors, pin emitters to a moving target, simulate a
|
|
12
|
+
* "black hole" pickup. For repulsion, use {@link RepelFromPoint}; for
|
|
13
|
+
* tangential motion, layer with {@link OrbitalForce}.
|
|
14
|
+
*
|
|
15
|
+
* GPU-eligible.
|
|
16
|
+
*/
|
|
17
|
+
class AttractToPoint extends UpdateModule {
|
|
18
|
+
x;
|
|
19
|
+
y;
|
|
20
|
+
strength;
|
|
21
|
+
falloff;
|
|
22
|
+
constructor(x, y, strength, falloff = 0) {
|
|
23
|
+
super();
|
|
24
|
+
this.x = x;
|
|
25
|
+
this.y = y;
|
|
26
|
+
this.strength = strength;
|
|
27
|
+
this.falloff = falloff;
|
|
28
|
+
}
|
|
29
|
+
apply(system, dt) {
|
|
30
|
+
const { posX, posY, velX, velY, liveCount } = system;
|
|
31
|
+
const { x, y, strength, falloff } = this;
|
|
32
|
+
for (let i = 0; i < liveCount; i++) {
|
|
33
|
+
const dx = x - posX[i];
|
|
34
|
+
const dy = y - posY[i];
|
|
35
|
+
const distSq = dx * dx + dy * dy;
|
|
36
|
+
const dist = Math.sqrt(distSq);
|
|
37
|
+
if (dist < 1e-5)
|
|
38
|
+
continue;
|
|
39
|
+
const k = falloff > 0 ? Math.min(1, dist / falloff) : 1;
|
|
40
|
+
const a = (strength * k * dt) / dist;
|
|
41
|
+
velX[i] += dx * a;
|
|
42
|
+
velY[i] += dy * a;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
wgsl() {
|
|
46
|
+
return {
|
|
47
|
+
key: 'AttractToPoint',
|
|
48
|
+
uniforms: [
|
|
49
|
+
{ name: 'point', type: 'vec2<f32>' },
|
|
50
|
+
{ name: 'strength', type: 'f32' },
|
|
51
|
+
{ name: 'falloff', type: 'f32' },
|
|
52
|
+
],
|
|
53
|
+
body: `
|
|
54
|
+
let attractDelta = modules.u_AttractToPoint.point - positions[idx];
|
|
55
|
+
let attractDist = length(attractDelta);
|
|
56
|
+
if (attractDist > 0.00001) {
|
|
57
|
+
let attractK = select(1.0, min(1.0, attractDist / max(modules.u_AttractToPoint.falloff, 0.000001)), modules.u_AttractToPoint.falloff > 0.0);
|
|
58
|
+
let attractAccel = (modules.u_AttractToPoint.strength * attractK * dt) / attractDist;
|
|
59
|
+
velocities[idx] = velocities[idx] + attractDelta * attractAccel;
|
|
60
|
+
}
|
|
61
|
+
`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
writeUniforms(view, offset) {
|
|
65
|
+
view.setFloat32(offset + 0, this.x, true);
|
|
66
|
+
view.setFloat32(offset + 4, this.y, true);
|
|
67
|
+
view.setFloat32(offset + 8, this.strength, true);
|
|
68
|
+
view.setFloat32(offset + 12, this.falloff, true);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { AttractToPoint };
|
|
73
|
+
//# sourceMappingURL=AttractToPoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AttractToPoint.js","sources":["../../../../src/modules/AttractToPoint.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAKA;;;;;;;;;;;;;AAaG;AACG,MAAO,cAAe,SAAQ,YAAY,CAAA;AACvC,IAAA,CAAC;AACD,IAAA,CAAC;AACD,IAAA,QAAQ;AACR,IAAA,OAAO;IAEd,WAAA,CAAmB,CAAS,EAAE,CAAS,EAAE,QAAgB,EAAE,OAAO,GAAG,CAAC,EAAA;AACpE,QAAA,KAAK,EAAE;AACP,QAAA,IAAI,CAAC,CAAC,GAAG,CAAC;AACV,QAAA,IAAI,CAAC,CAAC,GAAG,CAAC;AACV,QAAA,IAAI,CAAC,QAAQ,GAAG,QAAQ;AACxB,QAAA,IAAI,CAAC,OAAO,GAAG,OAAO;IACxB;IAEgB,KAAK,CAAC,MAAsB,EAAE,EAAU,EAAA;AACtD,QAAA,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,MAAM;QACpD,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI;AAExC,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE;YAClC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;YACtB,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;YACtB,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE;YAChC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;YAE9B,IAAI,IAAI,GAAG,IAAI;gBAAE;YAEjB,MAAM,CAAC,GAAG,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC;YACvD,MAAM,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC,GAAG,EAAE,IAAI,IAAI;AAEpC,YAAA,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC;AACjB,YAAA,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC;QACnB;IACF;IAEgB,IAAI,GAAA;QAClB,OAAO;AACL,YAAA,GAAG,EAAE,gBAAgB;AACrB,YAAA,QAAQ,EAAE;AACR,gBAAA,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE;AACpC,gBAAA,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE;AACjC,gBAAA,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE;AACjC,aAAA;AACD,YAAA,IAAI,EAAE;;;;;;;;AAQC,YAAA,CAAA;SACR;IACH;IAEgB,aAAa,CAAC,IAAc,EAAE,MAAc,EAAA;AAC1D,QAAA,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC;AACzC,QAAA,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC;AACzC,QAAA,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC;AAChD,QAAA,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC;IAClD;AACD;;;;"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Color } from '@codexo/exojs';
|
|
2
|
+
import { Vector } from '@codexo/exojs';
|
|
3
|
+
import type { Distribution } from "#distributions/Distribution";
|
|
4
|
+
import type { ParticleSystem } from "#ParticleSystem";
|
|
5
|
+
import { SpawnModule } from './SpawnModule';
|
|
6
|
+
/**
|
|
7
|
+
* Burst trigger schedule. The module fires at `time` seconds (since
|
|
8
|
+
* registration), spawning `count` particles in one frame.
|
|
9
|
+
*/
|
|
10
|
+
export interface BurstSchedule {
|
|
11
|
+
time: number;
|
|
12
|
+
count: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Per-property spawn configuration for {@link BurstSpawn}. Same fields as
|
|
16
|
+
* {@link RateSpawnConfig} except no rate — the schedule drives counts.
|
|
17
|
+
*/
|
|
18
|
+
export interface BurstSpawnConfig {
|
|
19
|
+
schedule: readonly BurstSchedule[];
|
|
20
|
+
/** Whether to repeat the schedule from t=0 once exhausted. Default `false`. */
|
|
21
|
+
loop?: boolean;
|
|
22
|
+
lifetime?: Distribution<number>;
|
|
23
|
+
position?: Distribution<Vector>;
|
|
24
|
+
velocity?: Distribution<Vector>;
|
|
25
|
+
scale?: Distribution<Vector>;
|
|
26
|
+
rotation?: Distribution<number>;
|
|
27
|
+
rotationSpeed?: Distribution<number>;
|
|
28
|
+
tint?: Distribution<Color>;
|
|
29
|
+
textureIndex?: Distribution<number>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Discrete-burst spawner. Fires at scheduled times with a fixed count per
|
|
33
|
+
* burst. Useful for explosions, hit-impacts, level-up effects.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* new BurstSpawn({
|
|
37
|
+
* schedule: [{ time: 0, count: 50 }, { time: 0.2, count: 25 }],
|
|
38
|
+
* velocity: ConeDirection.omni(150, 350),
|
|
39
|
+
* lifetime: new Range(0.4, 0.9),
|
|
40
|
+
* });
|
|
41
|
+
*/
|
|
42
|
+
export declare class BurstSpawn extends SpawnModule {
|
|
43
|
+
config: BurstSpawnConfig;
|
|
44
|
+
private _elapsed;
|
|
45
|
+
private _nextIndex;
|
|
46
|
+
private readonly _vec;
|
|
47
|
+
private readonly _color;
|
|
48
|
+
constructor(config: BurstSpawnConfig);
|
|
49
|
+
apply(system: ParticleSystem, dt: number): void;
|
|
50
|
+
/** Restart the schedule from t=0. */
|
|
51
|
+
reset(): this;
|
|
52
|
+
private _spawnBurst;
|
|
53
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Vector, Color } from '@codexo/exojs';
|
|
2
|
+
import { SpawnModule } from './SpawnModule.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Discrete-burst spawner. Fires at scheduled times with a fixed count per
|
|
6
|
+
* burst. Useful for explosions, hit-impacts, level-up effects.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* new BurstSpawn({
|
|
10
|
+
* schedule: [{ time: 0, count: 50 }, { time: 0.2, count: 25 }],
|
|
11
|
+
* velocity: ConeDirection.omni(150, 350),
|
|
12
|
+
* lifetime: new Range(0.4, 0.9),
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
class BurstSpawn extends SpawnModule {
|
|
16
|
+
config;
|
|
17
|
+
_elapsed = 0;
|
|
18
|
+
_nextIndex = 0;
|
|
19
|
+
_vec = new Vector();
|
|
20
|
+
_color = new Color();
|
|
21
|
+
constructor(config) {
|
|
22
|
+
super();
|
|
23
|
+
this.config = config;
|
|
24
|
+
}
|
|
25
|
+
apply(system, dt) {
|
|
26
|
+
const cfg = this.config;
|
|
27
|
+
const schedule = cfg.schedule;
|
|
28
|
+
if (schedule.length === 0) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this._elapsed += dt;
|
|
32
|
+
while (this._nextIndex < schedule.length && this._elapsed >= schedule[this._nextIndex].time) {
|
|
33
|
+
this._spawnBurst(system, schedule[this._nextIndex].count);
|
|
34
|
+
this._nextIndex++;
|
|
35
|
+
}
|
|
36
|
+
if (cfg.loop && this._nextIndex >= schedule.length) {
|
|
37
|
+
this._elapsed = 0;
|
|
38
|
+
this._nextIndex = 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Restart the schedule from t=0. */
|
|
42
|
+
reset() {
|
|
43
|
+
this._elapsed = 0;
|
|
44
|
+
this._nextIndex = 0;
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
_spawnBurst(system, count) {
|
|
48
|
+
const cfg = this.config;
|
|
49
|
+
const v = this._vec;
|
|
50
|
+
const c = this._color;
|
|
51
|
+
for (let i = 0; i < count; i++) {
|
|
52
|
+
const slot = system.spawn();
|
|
53
|
+
if (slot < 0) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
system.lifetime[slot] = cfg.lifetime ? cfg.lifetime.sample() : 1;
|
|
57
|
+
if (cfg.position) {
|
|
58
|
+
cfg.position.sample(v);
|
|
59
|
+
system.posX[slot] = v.x;
|
|
60
|
+
system.posY[slot] = v.y;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
system.posX[slot] = 0;
|
|
64
|
+
system.posY[slot] = 0;
|
|
65
|
+
}
|
|
66
|
+
if (cfg.velocity) {
|
|
67
|
+
cfg.velocity.sample(v);
|
|
68
|
+
system.velX[slot] = v.x;
|
|
69
|
+
system.velY[slot] = v.y;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
system.velX[slot] = 0;
|
|
73
|
+
system.velY[slot] = 0;
|
|
74
|
+
}
|
|
75
|
+
if (cfg.scale) {
|
|
76
|
+
cfg.scale.sample(v);
|
|
77
|
+
system.scaleX[slot] = v.x;
|
|
78
|
+
system.scaleY[slot] = v.y;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
system.scaleX[slot] = 1;
|
|
82
|
+
system.scaleY[slot] = 1;
|
|
83
|
+
}
|
|
84
|
+
system.rotations[slot] = cfg.rotation ? cfg.rotation.sample() : 0;
|
|
85
|
+
system.rotationSpeeds[slot] = cfg.rotationSpeed ? cfg.rotationSpeed.sample() : 0;
|
|
86
|
+
system.color[slot] = cfg.tint ? cfg.tint.sample(c).toRgba() : 0xffffffff;
|
|
87
|
+
system.textureIndex[slot] = cfg.textureIndex ? cfg.textureIndex.sample() | 0 : 0;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export { BurstSpawn };
|
|
93
|
+
//# sourceMappingURL=BurstSpawn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BurstSpawn.js","sources":["../../../../src/modules/BurstSpawn.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ColorGradient } from "#distributions/ColorGradient";
|
|
2
|
+
import type { ParticleSystem } from "#ParticleSystem";
|
|
3
|
+
import { UpdateModule } from './UpdateModule';
|
|
4
|
+
import type { WgslContribution } from './WgslContribution';
|
|
5
|
+
/**
|
|
6
|
+
* Per-frame, per-particle color sampler. Each live particle's tint is set
|
|
7
|
+
* to the gradient evaluated at the particle's current `elapsed / lifetime`
|
|
8
|
+
* ratio, packed RGBA. Replaces the per-particle blend of `ColorAffector`
|
|
9
|
+
* (legacy) with a multi-keyframe gradient.
|
|
10
|
+
*
|
|
11
|
+
* GPU-eligible: the gradient is uploaded once as a 256-tap 1D RGBA8 texture
|
|
12
|
+
* and sampled with linear filtering on the GPU.
|
|
13
|
+
*/
|
|
14
|
+
export declare class ColorOverLifetime extends UpdateModule {
|
|
15
|
+
gradient: ColorGradient;
|
|
16
|
+
constructor(gradient: ColorGradient);
|
|
17
|
+
apply(system: ParticleSystem, _dt: number): void;
|
|
18
|
+
wgsl(): WgslContribution;
|
|
19
|
+
uploadTextures(device: GPUDevice, textures: ReadonlyMap<string, GPUTexture>): void;
|
|
20
|
+
}
|
|
21
|
+
/** Texture width for the gradient lookup table. Exposed for ParticleGpuState. */
|
|
22
|
+
export declare const colorLookupSize = 256;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Color } from '@codexo/exojs';
|
|
2
|
+
import { UpdateModule } from './UpdateModule.js';
|
|
3
|
+
|
|
4
|
+
/// <reference types="@webgpu/types" />
|
|
5
|
+
const lookupSize = 256;
|
|
6
|
+
/**
|
|
7
|
+
* Per-frame, per-particle color sampler. Each live particle's tint is set
|
|
8
|
+
* to the gradient evaluated at the particle's current `elapsed / lifetime`
|
|
9
|
+
* ratio, packed RGBA. Replaces the per-particle blend of `ColorAffector`
|
|
10
|
+
* (legacy) with a multi-keyframe gradient.
|
|
11
|
+
*
|
|
12
|
+
* GPU-eligible: the gradient is uploaded once as a 256-tap 1D RGBA8 texture
|
|
13
|
+
* and sampled with linear filtering on the GPU.
|
|
14
|
+
*/
|
|
15
|
+
class ColorOverLifetime extends UpdateModule {
|
|
16
|
+
gradient;
|
|
17
|
+
constructor(gradient) {
|
|
18
|
+
super();
|
|
19
|
+
this.gradient = gradient;
|
|
20
|
+
}
|
|
21
|
+
apply(system, _dt) {
|
|
22
|
+
const { color, elapsed, lifetime, liveCount } = system;
|
|
23
|
+
const gradient = this.gradient;
|
|
24
|
+
for (let i = 0; i < liveCount; i++) {
|
|
25
|
+
const t = elapsed[i] / lifetime[i];
|
|
26
|
+
color[i] = gradient.evaluateRgba(t);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
wgsl() {
|
|
30
|
+
return {
|
|
31
|
+
key: 'ColorOverLifetime',
|
|
32
|
+
textures: [{ name: 'gradient', format: 'rgba8unorm' }],
|
|
33
|
+
body: `
|
|
34
|
+
let colorT = clamp(timing[idx].x / max(timing[idx].y, 0.000001), 0.0, 1.0);
|
|
35
|
+
let colorSample = textureSampleLevel(u_ColorOverLifetime_gradient, u_ColorOverLifetime_gradient_sampler, colorT, 0.0);
|
|
36
|
+
let r = u32(colorSample.r * 255.0) & 255u;
|
|
37
|
+
let g = u32(colorSample.g * 255.0) & 255u;
|
|
38
|
+
let b = u32(colorSample.b * 255.0) & 255u;
|
|
39
|
+
let a = u32(colorSample.a * 255.0) & 255u;
|
|
40
|
+
color[idx] = (a << 24u) | (b << 16u) | (g << 8u) | r;
|
|
41
|
+
`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
uploadTextures(device, textures) {
|
|
45
|
+
const texture = textures.get('gradient');
|
|
46
|
+
if (texture === undefined) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const data = new Uint8Array(lookupSize * 4);
|
|
50
|
+
const scratch = new Color();
|
|
51
|
+
for (let i = 0; i < lookupSize; i++) {
|
|
52
|
+
const t = i / (lookupSize - 1);
|
|
53
|
+
this.gradient.evaluate(t, scratch);
|
|
54
|
+
const o = i * 4;
|
|
55
|
+
data[o + 0] = scratch.r & 255;
|
|
56
|
+
data[o + 1] = scratch.g & 255;
|
|
57
|
+
data[o + 2] = scratch.b & 255;
|
|
58
|
+
data[o + 3] = ((scratch.a * 255) | 0) & 255;
|
|
59
|
+
}
|
|
60
|
+
device.queue.writeTexture({ texture }, data.buffer, { offset: 0, bytesPerRow: lookupSize * 4, rowsPerImage: 1 }, { width: lookupSize, height: 1, depthOrArrayLayers: 1 });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { ColorOverLifetime };
|
|
65
|
+
//# sourceMappingURL=ColorOverLifetime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ColorOverLifetime.js","sources":["../../../../src/modules/ColorOverLifetime.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAAA;AASA,MAAM,UAAU,GAAG,GAAG;AAEtB;;;;;;;;AAQG;AACG,MAAO,iBAAkB,SAAQ,YAAY,CAAA;AAC1C,IAAA,QAAQ;AAEf,IAAA,WAAA,CAAmB,QAAuB,EAAA;AACxC,QAAA,KAAK,EAAE;AACP,QAAA,IAAI,CAAC,QAAQ,GAAG,QAAQ;IAC1B;IAEgB,KAAK,CAAC,MAAsB,EAAE,GAAW,EAAA;QACvD,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,MAAM;AACtD,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ;AAE9B,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE;YAClC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;YAElC,KAAK,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC;QACrC;IACF;IAEgB,IAAI,GAAA;QAClB,OAAO;AACL,YAAA,GAAG,EAAE,mBAAmB;YACxB,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;AACtD,YAAA,IAAI,EAAE;;;;;;;;AAQC,YAAA,CAAA;SACR;IACH;IAEgB,cAAc,CAAC,MAAiB,EAAE,QAAyC,EAAA;QACzF,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC;AAExC,QAAA,IAAI,OAAO,KAAK,SAAS,EAAE;YACzB;QACF;QAEA,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,UAAU,GAAG,CAAC,CAAC;AAC3C,QAAA,MAAM,OAAO,GAAG,IAAI,KAAK,EAAE;AAE3B,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE;YACnC,MAAM,CAAC,GAAG,CAAC,IAAI,UAAU,GAAG,CAAC,CAAC;YAE9B,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;AAElC,YAAA,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;YAEf,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,GAAG;YAC7B,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,GAAG;YAC7B,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,GAAG;AAC7B,YAAA,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,GAAG;QAC7C;AAEA,QAAA,MAAM,CAAC,KAAK,CAAC,YAAY,CACvB,EAAE,OAAO,EAAE,EACX,IAAI,CAAC,MAAM,EACX,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,EAAE,UAAU,GAAG,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,EAC3D,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,CACxD;IACH;AACD;;;;"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ColorGradient } from "#distributions/ColorGradient";
|
|
2
|
+
import type { ParticleSystem } from "#ParticleSystem";
|
|
3
|
+
import { UpdateModule } from './UpdateModule';
|
|
4
|
+
import type { WgslContribution } from './WgslContribution';
|
|
5
|
+
/**
|
|
6
|
+
* Per-frame, per-particle color sampler driven by velocity magnitude rather
|
|
7
|
+
* than lifetime ratio. Each live particle's tint is set to the gradient
|
|
8
|
+
* evaluated at `clamp((|velocity| - minSpeed) / (maxSpeed - minSpeed), 0, 1)`.
|
|
9
|
+
*
|
|
10
|
+
* Use cases: heat-mapping (slow=blue, fast=red), velocity-tinted trails,
|
|
11
|
+
* speed-gated highlights.
|
|
12
|
+
*
|
|
13
|
+
* GPU-eligible: gradient uploaded as a 256-tap 1D RGBA8 texture, sampled
|
|
14
|
+
* with linear filtering. Replaces the full color word — pair with a
|
|
15
|
+
* separate {@link AlphaFadeOverLifetime} after this module if you want to
|
|
16
|
+
* keep alpha controlled by lifetime.
|
|
17
|
+
*/
|
|
18
|
+
export declare class ColorOverSpeed extends UpdateModule {
|
|
19
|
+
gradient: ColorGradient;
|
|
20
|
+
minSpeed: number;
|
|
21
|
+
maxSpeed: number;
|
|
22
|
+
constructor(gradient: ColorGradient, minSpeed: number, maxSpeed: number);
|
|
23
|
+
apply(system: ParticleSystem, _dt: number): void;
|
|
24
|
+
wgsl(): WgslContribution;
|
|
25
|
+
writeUniforms(view: DataView, offset: number): void;
|
|
26
|
+
uploadTextures(device: GPUDevice, textures: ReadonlyMap<string, GPUTexture>): void;
|
|
27
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Color } from '@codexo/exojs';
|
|
2
|
+
import { UpdateModule } from './UpdateModule.js';
|
|
3
|
+
|
|
4
|
+
/// <reference types="@webgpu/types" />
|
|
5
|
+
const lookupSize = 256;
|
|
6
|
+
/**
|
|
7
|
+
* Per-frame, per-particle color sampler driven by velocity magnitude rather
|
|
8
|
+
* than lifetime ratio. Each live particle's tint is set to the gradient
|
|
9
|
+
* evaluated at `clamp((|velocity| - minSpeed) / (maxSpeed - minSpeed), 0, 1)`.
|
|
10
|
+
*
|
|
11
|
+
* Use cases: heat-mapping (slow=blue, fast=red), velocity-tinted trails,
|
|
12
|
+
* speed-gated highlights.
|
|
13
|
+
*
|
|
14
|
+
* GPU-eligible: gradient uploaded as a 256-tap 1D RGBA8 texture, sampled
|
|
15
|
+
* with linear filtering. Replaces the full color word — pair with a
|
|
16
|
+
* separate {@link AlphaFadeOverLifetime} after this module if you want to
|
|
17
|
+
* keep alpha controlled by lifetime.
|
|
18
|
+
*/
|
|
19
|
+
class ColorOverSpeed extends UpdateModule {
|
|
20
|
+
gradient;
|
|
21
|
+
minSpeed;
|
|
22
|
+
maxSpeed;
|
|
23
|
+
constructor(gradient, minSpeed, maxSpeed) {
|
|
24
|
+
super();
|
|
25
|
+
this.gradient = gradient;
|
|
26
|
+
this.minSpeed = minSpeed;
|
|
27
|
+
this.maxSpeed = maxSpeed;
|
|
28
|
+
}
|
|
29
|
+
apply(system, _dt) {
|
|
30
|
+
const { velX, velY, color, liveCount } = system;
|
|
31
|
+
const gradient = this.gradient;
|
|
32
|
+
const min = this.minSpeed;
|
|
33
|
+
const span = Math.max(1e-5, this.maxSpeed - this.minSpeed);
|
|
34
|
+
for (let i = 0; i < liveCount; i++) {
|
|
35
|
+
const speed = Math.sqrt(velX[i] * velX[i] + velY[i] * velY[i]);
|
|
36
|
+
const t = Math.max(0, Math.min(1, (speed - min) / span));
|
|
37
|
+
color[i] = gradient.evaluateRgba(t);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
wgsl() {
|
|
41
|
+
return {
|
|
42
|
+
key: 'ColorOverSpeed',
|
|
43
|
+
uniforms: [
|
|
44
|
+
{ name: 'minSpeed', type: 'f32' },
|
|
45
|
+
{ name: 'invSpan', type: 'f32' },
|
|
46
|
+
],
|
|
47
|
+
textures: [{ name: 'gradient', format: 'rgba8unorm' }],
|
|
48
|
+
body: `
|
|
49
|
+
let speedMag = length(velocities[idx]);
|
|
50
|
+
let speedT = clamp((speedMag - modules.u_ColorOverSpeed.minSpeed) * modules.u_ColorOverSpeed.invSpan, 0.0, 1.0);
|
|
51
|
+
let speedSample = textureSampleLevel(u_ColorOverSpeed_gradient, u_ColorOverSpeed_gradient_sampler, speedT, 0.0);
|
|
52
|
+
let speedR = u32(speedSample.r * 255.0) & 255u;
|
|
53
|
+
let speedG = u32(speedSample.g * 255.0) & 255u;
|
|
54
|
+
let speedB = u32(speedSample.b * 255.0) & 255u;
|
|
55
|
+
let speedA = u32(speedSample.a * 255.0) & 255u;
|
|
56
|
+
color[idx] = (speedA << 24u) | (speedB << 16u) | (speedG << 8u) | speedR;
|
|
57
|
+
`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
writeUniforms(view, offset) {
|
|
61
|
+
const span = Math.max(1e-5, this.maxSpeed - this.minSpeed);
|
|
62
|
+
view.setFloat32(offset + 0, this.minSpeed, true);
|
|
63
|
+
view.setFloat32(offset + 4, 1 / span, true);
|
|
64
|
+
}
|
|
65
|
+
uploadTextures(device, textures) {
|
|
66
|
+
const texture = textures.get('gradient');
|
|
67
|
+
if (texture === undefined) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const data = new Uint8Array(lookupSize * 4);
|
|
71
|
+
const scratch = new Color();
|
|
72
|
+
for (let i = 0; i < lookupSize; i++) {
|
|
73
|
+
const t = i / (lookupSize - 1);
|
|
74
|
+
this.gradient.evaluate(t, scratch);
|
|
75
|
+
const o = i * 4;
|
|
76
|
+
data[o + 0] = scratch.r & 255;
|
|
77
|
+
data[o + 1] = scratch.g & 255;
|
|
78
|
+
data[o + 2] = scratch.b & 255;
|
|
79
|
+
data[o + 3] = ((scratch.a * 255) | 0) & 255;
|
|
80
|
+
}
|
|
81
|
+
device.queue.writeTexture({ texture }, data.buffer, { offset: 0, bytesPerRow: lookupSize * 4, rowsPerImage: 1 }, { width: lookupSize, height: 1, depthOrArrayLayers: 1 });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { ColorOverSpeed };
|
|
86
|
+
//# sourceMappingURL=ColorOverSpeed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ColorOverSpeed.js","sources":["../../../../src/modules/ColorOverSpeed.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAAA;AASA,MAAM,UAAU,GAAG,GAAG;AAEtB;;;;;;;;;;;;AAYG;AACG,MAAO,cAAe,SAAQ,YAAY,CAAA;AACvC,IAAA,QAAQ;AACR,IAAA,QAAQ;AACR,IAAA,QAAQ;AAEf,IAAA,WAAA,CAAmB,QAAuB,EAAE,QAAgB,EAAE,QAAgB,EAAA;AAC5E,QAAA,KAAK,EAAE;AACP,QAAA,IAAI,CAAC,QAAQ,GAAG,QAAQ;AACxB,QAAA,IAAI,CAAC,QAAQ,GAAG,QAAQ;AACxB,QAAA,IAAI,CAAC,QAAQ,GAAG,QAAQ;IAC1B;IAEgB,KAAK,CAAC,MAAsB,EAAE,GAAW,EAAA;QACvD,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM;AAC/C,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ;AAC9B,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ;AACzB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;AAE1D,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE;YAClC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YAC9D,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC;YAExD,KAAK,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC;QACrC;IACF;IAEgB,IAAI,GAAA;QAClB,OAAO;AACL,YAAA,GAAG,EAAE,gBAAgB;AACrB,YAAA,QAAQ,EAAE;AACR,gBAAA,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE;AACjC,gBAAA,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE;AACjC,aAAA;YACD,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;AACtD,YAAA,IAAI,EAAE;;;;;;;;;AASC,YAAA,CAAA;SACR;IACH;IAEgB,aAAa,CAAC,IAAc,EAAE,MAAc,EAAA;AAC1D,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;AAE1D,QAAA,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC;AAChD,QAAA,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,CAAC;IAC7C;IAEgB,cAAc,CAAC,MAAiB,EAAE,QAAyC,EAAA;QACzF,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC;AAExC,QAAA,IAAI,OAAO,KAAK,SAAS,EAAE;YACzB;QACF;QAEA,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,UAAU,GAAG,CAAC,CAAC;AAC3C,QAAA,MAAM,OAAO,GAAG,IAAI,KAAK,EAAE;AAE3B,QAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE;YACnC,MAAM,CAAC,GAAG,CAAC,IAAI,UAAU,GAAG,CAAC,CAAC;YAE9B,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;AAElC,YAAA,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;YAEf,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,GAAG;YAC7B,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,GAAG;YAC7B,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,GAAG;AAC7B,YAAA,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,GAAG;QAC7C;AAEA,QAAA,MAAM,CAAC,KAAK,CAAC,YAAY,CACvB,EAAE,OAAO,EAAE,EACX,IAAI,CAAC,MAAM,EACX,EAAE,MAAM,EAAE,CAAC,EAAE,WAAW,EAAE,UAAU,GAAG,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,EAC3D,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,CACxD;IACH;AACD;;;;"}
|