@triforge/particle-core 0.1.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/dist/core/BaseEmitter.d.ts +50 -0
- package/dist/core/BaseEmitter.d.ts.map +1 -0
- package/dist/core/BaseEmitter.js +53 -0
- package/dist/core/BaseEmitter.js.map +1 -0
- package/dist/core/BaseForce.d.ts +21 -0
- package/dist/core/BaseForce.d.ts.map +1 -0
- package/dist/core/BaseForce.js +15 -0
- package/dist/core/BaseForce.js.map +1 -0
- package/dist/core/BaseRenderer.d.ts +96 -0
- package/dist/core/BaseRenderer.d.ts.map +1 -0
- package/dist/core/BaseRenderer.js +190 -0
- package/dist/core/BaseRenderer.js.map +1 -0
- package/dist/core/DeflectorCollider.d.ts +46 -0
- package/dist/core/DeflectorCollider.d.ts.map +1 -0
- package/dist/core/DeflectorCollider.js +113 -0
- package/dist/core/DeflectorCollider.js.map +1 -0
- package/dist/core/Particle.d.ts +29 -0
- package/dist/core/Particle.d.ts.map +1 -0
- package/dist/core/Particle.js +41 -0
- package/dist/core/Particle.js.map +1 -0
- package/dist/core/ParticleCache.d.ts +73 -0
- package/dist/core/ParticleCache.d.ts.map +1 -0
- package/dist/core/ParticleCache.js +206 -0
- package/dist/core/ParticleCache.js.map +1 -0
- package/dist/core/ParticleSystem.d.ts +192 -0
- package/dist/core/ParticleSystem.d.ts.map +1 -0
- package/dist/core/ParticleSystem.js +484 -0
- package/dist/core/ParticleSystem.js.map +1 -0
- package/dist/core/SeededRandom.d.ts +17 -0
- package/dist/core/SeededRandom.d.ts.map +1 -0
- package/dist/core/SeededRandom.js +27 -0
- package/dist/core/SeededRandom.js.map +1 -0
- package/dist/emitters/EdgeEmitter.d.ts +43 -0
- package/dist/emitters/EdgeEmitter.d.ts.map +1 -0
- package/dist/emitters/EdgeEmitter.js +78 -0
- package/dist/emitters/EdgeEmitter.js.map +1 -0
- package/dist/emitters/MeshEmitter.d.ts +83 -0
- package/dist/emitters/MeshEmitter.d.ts.map +1 -0
- package/dist/emitters/MeshEmitter.js +240 -0
- package/dist/emitters/MeshEmitter.js.map +1 -0
- package/dist/emitters/PointEmitter.d.ts +22 -0
- package/dist/emitters/PointEmitter.d.ts.map +1 -0
- package/dist/emitters/PointEmitter.js +26 -0
- package/dist/emitters/PointEmitter.js.map +1 -0
- package/dist/forces/BoidField.d.ts +58 -0
- package/dist/forces/BoidField.d.ts.map +1 -0
- package/dist/forces/BoidField.js +82 -0
- package/dist/forces/BoidField.js.map +1 -0
- package/dist/forces/BoidForce.d.ts +54 -0
- package/dist/forces/BoidForce.d.ts.map +1 -0
- package/dist/forces/BoidForce.js +233 -0
- package/dist/forces/BoidForce.js.map +1 -0
- package/dist/forces/ChargeForce.d.ts +22 -0
- package/dist/forces/ChargeForce.d.ts.map +1 -0
- package/dist/forces/ChargeForce.js +35 -0
- package/dist/forces/ChargeForce.js.map +1 -0
- package/dist/forces/CurveGuideForce.d.ts +43 -0
- package/dist/forces/CurveGuideForce.d.ts.map +1 -0
- package/dist/forces/CurveGuideForce.js +118 -0
- package/dist/forces/CurveGuideForce.js.map +1 -0
- package/dist/forces/DragForce.d.ts +19 -0
- package/dist/forces/DragForce.d.ts.map +1 -0
- package/dist/forces/DragForce.js +31 -0
- package/dist/forces/DragForce.js.map +1 -0
- package/dist/forces/FlowFieldForce.d.ts +72 -0
- package/dist/forces/FlowFieldForce.d.ts.map +1 -0
- package/dist/forces/FlowFieldForce.js +68 -0
- package/dist/forces/FlowFieldForce.js.map +1 -0
- package/dist/forces/ForceField.d.ts +26 -0
- package/dist/forces/ForceField.d.ts.map +1 -0
- package/dist/forces/ForceField.js +41 -0
- package/dist/forces/ForceField.js.map +1 -0
- package/dist/forces/GravityForce.d.ts +9 -0
- package/dist/forces/GravityForce.d.ts.map +1 -0
- package/dist/forces/GravityForce.js +12 -0
- package/dist/forces/GravityForce.js.map +1 -0
- package/dist/forces/HarmonicForce.d.ts +22 -0
- package/dist/forces/HarmonicForce.d.ts.map +1 -0
- package/dist/forces/HarmonicForce.js +31 -0
- package/dist/forces/HarmonicForce.js.map +1 -0
- package/dist/forces/LennardJonesForce.d.ts +23 -0
- package/dist/forces/LennardJonesForce.d.ts.map +1 -0
- package/dist/forces/LennardJonesForce.js +38 -0
- package/dist/forces/LennardJonesForce.js.map +1 -0
- package/dist/forces/MagneticForce.d.ts +21 -0
- package/dist/forces/MagneticForce.d.ts.map +1 -0
- package/dist/forces/MagneticForce.js +30 -0
- package/dist/forces/MagneticForce.js.map +1 -0
- package/dist/forces/TextureForce.d.ts +37 -0
- package/dist/forces/TextureForce.d.ts.map +1 -0
- package/dist/forces/TextureForce.js +56 -0
- package/dist/forces/TextureForce.js.map +1 -0
- package/dist/forces/TurbulenceForce.d.ts +21 -0
- package/dist/forces/TurbulenceForce.d.ts.map +1 -0
- package/dist/forces/TurbulenceForce.js +39 -0
- package/dist/forces/TurbulenceForce.js.map +1 -0
- package/dist/forces/VortexForce.d.ts +21 -0
- package/dist/forces/VortexForce.d.ts.map +1 -0
- package/dist/forces/VortexForce.js +29 -0
- package/dist/forces/VortexForce.js.map +1 -0
- package/dist/forces/WindForce.d.ts +21 -0
- package/dist/forces/WindForce.d.ts.map +1 -0
- package/dist/forces/WindForce.js +29 -0
- package/dist/forces/WindForce.js.map +1 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/physics/KeyedPhysics.d.ts +43 -0
- package/dist/physics/KeyedPhysics.d.ts.map +1 -0
- package/dist/physics/KeyedPhysics.js +73 -0
- package/dist/physics/KeyedPhysics.js.map +1 -0
- package/dist/physics/SPHPhysics.d.ts +27 -0
- package/dist/physics/SPHPhysics.d.ts.map +1 -0
- package/dist/physics/SPHPhysics.js +119 -0
- package/dist/physics/SPHPhysics.js.map +1 -0
- package/dist/renderers/BillboardRenderer.d.ts +75 -0
- package/dist/renderers/BillboardRenderer.d.ts.map +1 -0
- package/dist/renderers/BillboardRenderer.js +106 -0
- package/dist/renderers/BillboardRenderer.js.map +1 -0
- package/dist/renderers/CollectionRenderer.d.ts +83 -0
- package/dist/renderers/CollectionRenderer.d.ts.map +1 -0
- package/dist/renderers/CollectionRenderer.js +161 -0
- package/dist/renderers/CollectionRenderer.js.map +1 -0
- package/dist/renderers/HaloRenderer.d.ts +3 -0
- package/dist/renderers/HaloRenderer.d.ts.map +1 -0
- package/dist/renderers/HaloRenderer.js +4 -0
- package/dist/renderers/HaloRenderer.js.map +1 -0
- package/dist/renderers/InstanceRenderer.d.ts +70 -0
- package/dist/renderers/InstanceRenderer.d.ts.map +1 -0
- package/dist/renderers/InstanceRenderer.js +86 -0
- package/dist/renderers/InstanceRenderer.js.map +1 -0
- package/dist/renderers/LineRenderer.d.ts +52 -0
- package/dist/renderers/LineRenderer.d.ts.map +1 -0
- package/dist/renderers/LineRenderer.js +96 -0
- package/dist/renderers/LineRenderer.js.map +1 -0
- package/dist/renderers/NoneRenderer.d.ts +19 -0
- package/dist/renderers/NoneRenderer.d.ts.map +1 -0
- package/dist/renderers/NoneRenderer.js +24 -0
- package/dist/renderers/NoneRenderer.js.map +1 -0
- package/dist/renderers/ObjectRenderer.d.ts +3 -0
- package/dist/renderers/ObjectRenderer.d.ts.map +1 -0
- package/dist/renderers/ObjectRenderer.js +4 -0
- package/dist/renderers/ObjectRenderer.js.map +1 -0
- package/dist/renderers/StrandRenderer.d.ts +169 -0
- package/dist/renderers/StrandRenderer.d.ts.map +1 -0
- package/dist/renderers/StrandRenderer.js +235 -0
- package/dist/renderers/StrandRenderer.js.map +1 -0
- package/dist/renderers/TrailRenderer.d.ts +54 -0
- package/dist/renderers/TrailRenderer.d.ts.map +1 -0
- package/dist/renderers/TrailRenderer.js +114 -0
- package/dist/renderers/TrailRenderer.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Vector3 } from 'three';
|
|
2
|
+
import { BaseForce } from '../core/BaseForce.js';
|
|
3
|
+
import type { Particle } from '../core/Particle.js';
|
|
4
|
+
/**
|
|
5
|
+
* Callback that returns a velocity vector at a world-space position.
|
|
6
|
+
* Use this to connect FlowFieldForce to a fluid simulation:
|
|
7
|
+
*
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Outside st-particle-core — no import restriction applies
|
|
10
|
+
* import { FLIPSimulator } from '@st-fluid-core'
|
|
11
|
+
* const fluid = new FLIPSimulator({ ... })
|
|
12
|
+
*
|
|
13
|
+
* const force = new FlowFieldForce({
|
|
14
|
+
* velocityFn: (x, y, z) => {
|
|
15
|
+
* const u = fluid.grid.interpU(x, y, z)
|
|
16
|
+
* const v = fluid.grid.interpV(x, y, z)
|
|
17
|
+
* const w = fluid.grid.interpW(x, y, z)
|
|
18
|
+
* return { x: u, y: v, z: w }
|
|
19
|
+
* }
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* When `velocityFn` is provided the built-in noise field is bypassed entirely.
|
|
24
|
+
*/
|
|
25
|
+
export type FlowVelocityFn = (x: number, y: number, z: number) => {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
z: number;
|
|
29
|
+
};
|
|
30
|
+
export interface FlowFieldForceOptions {
|
|
31
|
+
/** Field strength multiplier. Default 1. */
|
|
32
|
+
strength?: number;
|
|
33
|
+
/** Noise scale — spatial frequency of the flow field. Default 1. */
|
|
34
|
+
scale?: number;
|
|
35
|
+
/** Animation speed (scrolls the field over time). Default 0.3. */
|
|
36
|
+
speed?: number;
|
|
37
|
+
/** How strongly particles align to the field vs keep inertia [0–1]. Default 0.5. */
|
|
38
|
+
influence?: number;
|
|
39
|
+
/** Maximum distance from `position` to apply force. 0 = unlimited. Default 0. */
|
|
40
|
+
maxDistance?: number;
|
|
41
|
+
/** World-space field origin. Default [0,0,0]. */
|
|
42
|
+
px?: number;
|
|
43
|
+
py?: number;
|
|
44
|
+
pz?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Optional external velocity field callback.
|
|
47
|
+
* When provided, replaces the built-in curl-noise field.
|
|
48
|
+
* Receives world-space (x,y,z), returns velocity vector.
|
|
49
|
+
* Use to connect to st-fluid-core FLIPSimulator or any other velocity source.
|
|
50
|
+
*/
|
|
51
|
+
velocityFn?: FlowVelocityFn;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* FlowFieldForce — steers particles along a smooth 3-D noise velocity field.
|
|
55
|
+
* Blender: Force Field → Texture (with animated noise).
|
|
56
|
+
*
|
|
57
|
+
* Each particle samples a curl-noise-like direction at its world position
|
|
58
|
+
* and is nudged toward that direction. The field animates over time via
|
|
59
|
+
* `parameters.speed`.
|
|
60
|
+
*/
|
|
61
|
+
export declare class FlowFieldForce extends BaseForce {
|
|
62
|
+
parameters: Record<string, number>;
|
|
63
|
+
/** World-space field origin for maxDistance range check. Not GSAP-animatable. */
|
|
64
|
+
position: Vector3;
|
|
65
|
+
/** Optional external velocity callback — overrides built-in noise when set. */
|
|
66
|
+
velocityFn: FlowVelocityFn | null;
|
|
67
|
+
private _t;
|
|
68
|
+
constructor(opts?: FlowFieldForceOptions);
|
|
69
|
+
apply(particle: Particle, dt: number): void;
|
|
70
|
+
private _noise;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=FlowFieldForce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowFieldForce.d.ts","sourceRoot":"","sources":["../../src/forces/FlowFieldForce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAQ,OAAO,CAAA;AACjC,OAAO,EAAE,SAAS,EAAE,MAAO,sBAAsB,CAAA;AACjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAErG,MAAM,WAAW,qBAAqB;IACpC,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oFAAoF;IACpF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iDAAiD;IACjD,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;IACrC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,cAAc,CAAA;CAC5B;AAED;;;;;;;GAOG;AACH,qBAAa,cAAe,SAAQ,SAAS;IAC3C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAElC,iFAAiF;IACjF,QAAQ,EAAE,OAAO,CAAA;IAEjB,+EAA+E;IAC/E,UAAU,EAAE,cAAc,GAAG,IAAI,CAAA;IAEjC,OAAO,CAAC,EAAE,CAAI;gBAEF,IAAI,GAAE,qBAA0B;IAa5C,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAwC3C,OAAO,CAAC,MAAM;CAIf"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Vector3 } from 'three';
|
|
2
|
+
import { BaseForce } from '../core/BaseForce.js';
|
|
3
|
+
/**
|
|
4
|
+
* FlowFieldForce — steers particles along a smooth 3-D noise velocity field.
|
|
5
|
+
* Blender: Force Field → Texture (with animated noise).
|
|
6
|
+
*
|
|
7
|
+
* Each particle samples a curl-noise-like direction at its world position
|
|
8
|
+
* and is nudged toward that direction. The field animates over time via
|
|
9
|
+
* `parameters.speed`.
|
|
10
|
+
*/
|
|
11
|
+
export class FlowFieldForce extends BaseForce {
|
|
12
|
+
constructor(opts = {}) {
|
|
13
|
+
super();
|
|
14
|
+
this._t = 0;
|
|
15
|
+
this.position = new Vector3(opts.px ?? 0, opts.py ?? 0, opts.pz ?? 0);
|
|
16
|
+
this.velocityFn = opts.velocityFn ?? null;
|
|
17
|
+
this.parameters = {
|
|
18
|
+
strength: opts.strength ?? 1,
|
|
19
|
+
scale: opts.scale ?? 1,
|
|
20
|
+
speed: opts.speed ?? 0.3,
|
|
21
|
+
influence: opts.influence ?? 0.5,
|
|
22
|
+
maxDistance: opts.maxDistance ?? 0,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
apply(particle, dt) {
|
|
26
|
+
const { strength, scale, speed, influence, maxDistance } = this.parameters;
|
|
27
|
+
this._t += dt * speed;
|
|
28
|
+
if (maxDistance > 0) {
|
|
29
|
+
const dx = particle.position.x - this.position.x;
|
|
30
|
+
const dy = particle.position.y - this.position.y;
|
|
31
|
+
const dz = particle.position.z - this.position.z;
|
|
32
|
+
if (dx * dx + dy * dy + dz * dz > maxDistance * maxDistance)
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
let vx, vy, vz;
|
|
36
|
+
if (this.velocityFn) {
|
|
37
|
+
// External fluid sim velocity field
|
|
38
|
+
const v = this.velocityFn(particle.position.x, particle.position.y, particle.position.z);
|
|
39
|
+
vx = v.x;
|
|
40
|
+
vy = v.y;
|
|
41
|
+
vz = v.z;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
// Built-in curl-noise field
|
|
45
|
+
const x = particle.position.x * scale;
|
|
46
|
+
const y = particle.position.y * scale;
|
|
47
|
+
const z = particle.position.z * scale;
|
|
48
|
+
const t = this._t;
|
|
49
|
+
vx = this._noise(x + 0.0, y + 1.7, z + 3.4, t + 0.0)
|
|
50
|
+
- this._noise(x + 0.0, y - 1.7, z - 3.4, t + 0.0);
|
|
51
|
+
vy = this._noise(x + 5.2, y + 0.0, z + 1.3, t + 1.1)
|
|
52
|
+
- this._noise(x - 5.2, y + 0.0, z - 1.3, t + 1.1);
|
|
53
|
+
vz = this._noise(x + 2.8, y + 4.1, z + 0.0, t + 2.3)
|
|
54
|
+
- this._noise(x - 2.8, y - 4.1, z + 0.0, t + 2.3);
|
|
55
|
+
}
|
|
56
|
+
const len = Math.sqrt(vx * vx + vy * vy + vz * vz) + 1e-6;
|
|
57
|
+
const nx = vx / len, ny = vy / len, nz = vz / len;
|
|
58
|
+
const s = strength * influence * dt;
|
|
59
|
+
particle.velocity.x += nx * s;
|
|
60
|
+
particle.velocity.y += ny * s;
|
|
61
|
+
particle.velocity.z += nz * s;
|
|
62
|
+
}
|
|
63
|
+
_noise(x, y, z, t) {
|
|
64
|
+
const n = Math.sin(x * 127.1 + y * 311.7 + z * 74.7 + t * 591.3) * 43758.5453;
|
|
65
|
+
return (n - Math.floor(n)) * 2 - 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=FlowFieldForce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FlowFieldForce.js","sourceRoot":"","sources":["../../src/forces/FlowFieldForce.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Vector3 } from 'three';
|
|
2
|
+
import { BaseForce } from '../core/BaseForce.js';
|
|
3
|
+
import type { Particle } from '../core/Particle.js';
|
|
4
|
+
/**
|
|
5
|
+
* Radial push/pull force field — a point in world space that repels or
|
|
6
|
+
* attracts particles. Positive strength = repel, negative = attract.
|
|
7
|
+
*
|
|
8
|
+
* Force magnitude = strength / (distance ^ falloff), clamped to maxDistance.
|
|
9
|
+
*
|
|
10
|
+
* Blender parallel: Force Fields → Force (Type: Force).
|
|
11
|
+
*/
|
|
12
|
+
export declare class ForceField extends BaseForce {
|
|
13
|
+
parameters: Record<string, number>;
|
|
14
|
+
/** World-space origin of the field. Not in parameters — not GSAP-animatable. */
|
|
15
|
+
position: Vector3;
|
|
16
|
+
constructor(opts?: {
|
|
17
|
+
strength?: number;
|
|
18
|
+
falloff?: number;
|
|
19
|
+
maxDistance?: number;
|
|
20
|
+
x?: number;
|
|
21
|
+
y?: number;
|
|
22
|
+
z?: number;
|
|
23
|
+
});
|
|
24
|
+
apply(particle: Particle, dt: number): void;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=ForceField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ForceField.d.ts","sourceRoot":"","sources":["../../src/forces/ForceField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAa,OAAO,CAAA;AACtC,OAAO,EAAE,SAAS,EAAE,MAAW,sBAAsB,CAAA;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAO,qBAAqB,CAAA;AAEpD;;;;;;;GAOG;AACH,qBAAa,UAAW,SAAQ,SAAS;IACvC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAElC,iFAAiF;IACjF,QAAQ,EAAE,OAAO,CAAA;gBAEL,IAAI,GAAE;QAChB,QAAQ,CAAC,EAAK,MAAM,CAAA;QACpB,OAAO,CAAC,EAAM,MAAM,CAAA;QACpB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,CAAC,CAAC,EAAY,MAAM,CAAA;QACpB,CAAC,CAAC,EAAY,MAAM,CAAA;QACpB,CAAC,CAAC,EAAY,MAAM,CAAA;KAChB;IAUN,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;CAsB5C"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Vector3 } from 'three';
|
|
2
|
+
import { BaseForce } from '../core/BaseForce.js';
|
|
3
|
+
/**
|
|
4
|
+
* Radial push/pull force field — a point in world space that repels or
|
|
5
|
+
* attracts particles. Positive strength = repel, negative = attract.
|
|
6
|
+
*
|
|
7
|
+
* Force magnitude = strength / (distance ^ falloff), clamped to maxDistance.
|
|
8
|
+
*
|
|
9
|
+
* Blender parallel: Force Fields → Force (Type: Force).
|
|
10
|
+
*/
|
|
11
|
+
export class ForceField extends BaseForce {
|
|
12
|
+
constructor(opts = {}) {
|
|
13
|
+
super();
|
|
14
|
+
this.position = new Vector3(opts.x ?? 0, opts.y ?? 0, opts.z ?? 0);
|
|
15
|
+
this.parameters = {
|
|
16
|
+
strength: opts.strength ?? 1.0,
|
|
17
|
+
falloff: opts.falloff ?? 1.0,
|
|
18
|
+
maxDistance: opts.maxDistance ?? 10.0,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
apply(particle, dt) {
|
|
22
|
+
if (!particle.alive)
|
|
23
|
+
return;
|
|
24
|
+
const { strength, falloff, maxDistance } = this.parameters;
|
|
25
|
+
const dx = particle.position.x - this.position.x;
|
|
26
|
+
const dy = particle.position.y - this.position.y;
|
|
27
|
+
const dz = particle.position.z - this.position.z;
|
|
28
|
+
const dist2 = dx * dx + dy * dy + dz * dz;
|
|
29
|
+
if (dist2 < 1e-10 || dist2 > maxDistance * maxDistance)
|
|
30
|
+
return;
|
|
31
|
+
const dist = Math.sqrt(dist2);
|
|
32
|
+
// Clamp falloff exponent to avoid unbounded forces at zero distance
|
|
33
|
+
const safeExp = Math.max(0, falloff);
|
|
34
|
+
const magnitude = strength / Math.pow(Math.max(dist, 0.01), safeExp);
|
|
35
|
+
const inv = magnitude / dist;
|
|
36
|
+
particle.velocity.x += dx * inv * dt;
|
|
37
|
+
particle.velocity.y += dy * inv * dt;
|
|
38
|
+
particle.velocity.z += dz * inv * dt;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=ForceField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ForceField.js","sourceRoot":"","sources":["../../src/forces/ForceField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAa,OAAO,CAAA;AACtC,OAAO,EAAE,SAAS,EAAE,MAAW,sBAAsB,CAAA;AAGrD;;;;;;;GAOG;AACH,MAAM,OAAO,UAAW,SAAQ,SAAS;IAMvC,YAAY,OAOR,EAAE;QACJ,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,QAAQ,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;QAClE,IAAI,CAAC,UAAU,GAAG;YAChB,QAAQ,EAAK,IAAI,CAAC,QAAQ,IAAO,GAAG;YACpC,OAAO,EAAM,IAAI,CAAC,OAAO,IAAQ,GAAG;YACpC,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI;SACtC,CAAA;IACH,CAAC;IAED,KAAK,CAAC,QAAkB,EAAE,EAAU;QAClC,IAAI,CAAC,QAAQ,CAAC,KAAK;YAAE,OAAM;QAE3B,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC,UAAU,CAAA;QAE1D,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;QAChD,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;QAChD,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;QAChD,MAAM,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAA;QAEzC,IAAI,KAAK,GAAG,KAAK,IAAI,KAAK,GAAG,WAAW,GAAG,WAAW;YAAE,OAAM;QAE9D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC7B,oEAAoE;QACpE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAA;QACpC,MAAM,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAA;QAEpE,MAAM,GAAG,GAAG,SAAS,GAAG,IAAI,CAAA;QAC5B,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,EAAE,CAAA;QACpC,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,EAAE,CAAA;QACpC,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,EAAE,CAAA;IACtC,CAAC;CACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseForce } from '../core/BaseForce.js';
|
|
2
|
+
import type { Particle } from '../core/Particle.js';
|
|
3
|
+
/** Directional gravity force (Blender: Force Field → Gravity) */
|
|
4
|
+
export declare class GravityForce extends BaseForce {
|
|
5
|
+
parameters: Record<string, number>;
|
|
6
|
+
constructor(strength?: number);
|
|
7
|
+
apply(particle: Particle, dt: number): void;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=GravityForce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GravityForce.d.ts","sourceRoot":"","sources":["../../src/forces/GravityForce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD,iEAAiE;AACjE,qBAAa,YAAa,SAAQ,SAAS;IACzC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;gBAEtB,QAAQ,SAAO;IAK3B,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;CAG5C"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseForce } from '../core/BaseForce.js';
|
|
2
|
+
/** Directional gravity force (Blender: Force Field → Gravity) */
|
|
3
|
+
export class GravityForce extends BaseForce {
|
|
4
|
+
constructor(strength = 9.81) {
|
|
5
|
+
super();
|
|
6
|
+
this.parameters = { strength };
|
|
7
|
+
}
|
|
8
|
+
apply(particle, dt) {
|
|
9
|
+
particle.velocity.y -= this.parameters.strength * dt;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=GravityForce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GravityForce.js","sourceRoot":"","sources":["../../src/forces/GravityForce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAGhD,iEAAiE;AACjE,MAAM,OAAO,YAAa,SAAQ,SAAS;IAGzC,YAAY,QAAQ,GAAG,IAAI;QACzB,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,UAAU,GAAG,EAAE,QAAQ,EAAE,CAAA;IAChC,CAAC;IAED,KAAK,CAAC,QAAkB,EAAE,EAAU;QAClC,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,GAAG,EAAE,CAAA;IACtD,CAAC;CACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Vector3 } from 'three';
|
|
2
|
+
import { BaseForce } from '../core/BaseForce.js';
|
|
3
|
+
import type { Particle } from '../core/Particle.js';
|
|
4
|
+
/** Spring attraction toward a point (Blender: Force Field → Harmonic) */
|
|
5
|
+
export declare class HarmonicForce extends BaseForce {
|
|
6
|
+
parameters: Record<string, number>;
|
|
7
|
+
/** World-space field origin for maxDistance range check. Not GSAP-animatable. */
|
|
8
|
+
position: Vector3;
|
|
9
|
+
constructor(opts?: {
|
|
10
|
+
strength?: number;
|
|
11
|
+
damping?: number;
|
|
12
|
+
x?: number;
|
|
13
|
+
y?: number;
|
|
14
|
+
z?: number;
|
|
15
|
+
maxDistance?: number;
|
|
16
|
+
px?: number;
|
|
17
|
+
py?: number;
|
|
18
|
+
pz?: number;
|
|
19
|
+
});
|
|
20
|
+
apply(particle: Particle, dt: number): void;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=HarmonicForce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HarmonicForce.d.ts","sourceRoot":"","sources":["../../src/forces/HarmonicForce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAS,OAAO,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAQ,sBAAsB,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD,yEAAyE;AACzE,qBAAa,aAAc,SAAQ,SAAS;IAC1C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAElC,iFAAiF;IACjF,QAAQ,EAAE,OAAO,CAAA;gBAEL,IAAI,GAAE;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;QACvE,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KACjC;IAaN,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;CAY5C"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Vector3 } from 'three';
|
|
2
|
+
import { BaseForce } from '../core/BaseForce.js';
|
|
3
|
+
/** Spring attraction toward a point (Blender: Force Field → Harmonic) */
|
|
4
|
+
export class HarmonicForce extends BaseForce {
|
|
5
|
+
constructor(opts = {}) {
|
|
6
|
+
super();
|
|
7
|
+
this.position = new Vector3(opts.px ?? 0, opts.py ?? 0, opts.pz ?? 0);
|
|
8
|
+
this.parameters = {
|
|
9
|
+
strength: opts.strength ?? 1,
|
|
10
|
+
damping: opts.damping ?? 0.5,
|
|
11
|
+
x: opts.x ?? 0,
|
|
12
|
+
y: opts.y ?? 0,
|
|
13
|
+
z: opts.z ?? 0,
|
|
14
|
+
maxDistance: opts.maxDistance ?? 0,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
apply(particle, dt) {
|
|
18
|
+
const p = this.parameters;
|
|
19
|
+
if (p.maxDistance > 0) {
|
|
20
|
+
const dx = particle.position.x - this.position.x;
|
|
21
|
+
const dy = particle.position.y - this.position.y;
|
|
22
|
+
const dz = particle.position.z - this.position.z;
|
|
23
|
+
if (dx * dx + dy * dy + dz * dz > p.maxDistance * p.maxDistance)
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const toTarget = new Vector3(p.x - particle.position.x, p.y - particle.position.y, p.z - particle.position.z);
|
|
27
|
+
particle.velocity.addScaledVector(toTarget, p.strength * dt);
|
|
28
|
+
particle.velocity.multiplyScalar(1 - Math.min(p.damping * dt, 1));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=HarmonicForce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HarmonicForce.js","sourceRoot":"","sources":["../../src/forces/HarmonicForce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAS,OAAO,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAQ,sBAAsB,CAAA;AAGlD,yEAAyE;AACzE,MAAM,OAAO,aAAc,SAAQ,SAAS;IAM1C,YAAY,OAIR,EAAE;QACJ,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,QAAQ,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,CAAA;QACrE,IAAI,CAAC,UAAU,GAAG;YAChB,QAAQ,EAAK,IAAI,CAAC,QAAQ,IAAO,CAAC;YAClC,OAAO,EAAM,IAAI,CAAC,OAAO,IAAQ,GAAG;YACpC,CAAC,EAAY,IAAI,CAAC,CAAC,IAAa,CAAC;YACjC,CAAC,EAAY,IAAI,CAAC,CAAC,IAAa,CAAC;YACjC,CAAC,EAAY,IAAI,CAAC,CAAC,IAAa,CAAC;YACjC,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,CAAC;SACnC,CAAA;IACH,CAAC;IAED,KAAK,CAAC,QAAkB,EAAE,EAAU;QAClC,MAAM,CAAC,GAAG,IAAI,CAAC,UAAU,CAAA;QACzB,IAAI,CAAC,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;YAChD,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;YAChD,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;YAChD,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW;gBAAE,OAAM;QACzE,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;QAC7G,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAA;QAC5D,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;IACnE,CAAC;CACF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Vector3 } from 'three';
|
|
2
|
+
import { BaseForce } from '../core/BaseForce.js';
|
|
3
|
+
import type { Particle } from '../core/Particle.js';
|
|
4
|
+
/**
|
|
5
|
+
* Lennard-Jones inter-particle force — attraction at long range, repulsion
|
|
6
|
+
* at short range. Models molecular-like particle interaction.
|
|
7
|
+
* (Blender: Force Field → Lennard-Jones)
|
|
8
|
+
*/
|
|
9
|
+
export declare class LennardJonesForce extends BaseForce {
|
|
10
|
+
parameters: Record<string, number>;
|
|
11
|
+
/** World-space field origin. Physics and range check both use this. Not GSAP-animatable. */
|
|
12
|
+
position: Vector3;
|
|
13
|
+
constructor(opts?: {
|
|
14
|
+
strength?: number;
|
|
15
|
+
equilibrium?: number;
|
|
16
|
+
maxDistance?: number;
|
|
17
|
+
px?: number;
|
|
18
|
+
py?: number;
|
|
19
|
+
pz?: number;
|
|
20
|
+
});
|
|
21
|
+
apply(particle: Particle, dt: number): void;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=LennardJonesForce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LennardJonesForce.d.ts","sourceRoot":"","sources":["../../src/forces/LennardJonesForce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAS,OAAO,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAQ,sBAAsB,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,SAAS;IAC9C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAElC,4FAA4F;IAC5F,QAAQ,EAAE,OAAO,CAAA;gBAEL,IAAI,GAAE;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;QAC7D,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KACjC;IAUN,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;CAiB5C"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Vector3 } from 'three';
|
|
2
|
+
import { BaseForce } from '../core/BaseForce.js';
|
|
3
|
+
/**
|
|
4
|
+
* Lennard-Jones inter-particle force — attraction at long range, repulsion
|
|
5
|
+
* at short range. Models molecular-like particle interaction.
|
|
6
|
+
* (Blender: Force Field → Lennard-Jones)
|
|
7
|
+
*/
|
|
8
|
+
export class LennardJonesForce extends BaseForce {
|
|
9
|
+
constructor(opts = {}) {
|
|
10
|
+
super();
|
|
11
|
+
this.position = new Vector3(opts.px ?? 0, opts.py ?? 0, opts.pz ?? 0);
|
|
12
|
+
this.parameters = {
|
|
13
|
+
strength: opts.strength ?? 1,
|
|
14
|
+
equilibrium: opts.equilibrium ?? 1,
|
|
15
|
+
maxDistance: opts.maxDistance ?? 0,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
apply(particle, dt) {
|
|
19
|
+
const p = this.parameters;
|
|
20
|
+
const dx = particle.position.x - this.position.x;
|
|
21
|
+
const dy = particle.position.y - this.position.y;
|
|
22
|
+
const dz = particle.position.z - this.position.z;
|
|
23
|
+
const r2 = dx * dx + dy * dy + dz * dz;
|
|
24
|
+
if (r2 < 1e-8)
|
|
25
|
+
return;
|
|
26
|
+
if (p.maxDistance > 0 && r2 > p.maxDistance * p.maxDistance)
|
|
27
|
+
return;
|
|
28
|
+
const r = Math.sqrt(r2);
|
|
29
|
+
const r0 = p.equilibrium;
|
|
30
|
+
const ratio = r0 / r;
|
|
31
|
+
// Simplified LJ: F = ε [ 2(r0/r)^7 - (r0/r)^13 ]
|
|
32
|
+
const mag = p.strength * (2 * Math.pow(ratio, 7) - Math.pow(ratio, 13));
|
|
33
|
+
particle.velocity.x -= (dx / r) * mag * dt;
|
|
34
|
+
particle.velocity.y -= (dy / r) * mag * dt;
|
|
35
|
+
particle.velocity.z -= (dz / r) * mag * dt;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=LennardJonesForce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LennardJonesForce.js","sourceRoot":"","sources":["../../src/forces/LennardJonesForce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAS,OAAO,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAQ,sBAAsB,CAAA;AAGlD;;;;GAIG;AACH,MAAM,OAAO,iBAAkB,SAAQ,SAAS;IAM9C,YAAY,OAGR,EAAE;QACJ,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,QAAQ,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,CAAA;QACrE,IAAI,CAAC,UAAU,GAAG;YAChB,QAAQ,EAAK,IAAI,CAAC,QAAQ,IAAO,CAAC;YAClC,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,CAAC;YAClC,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,CAAC;SACnC,CAAA;IACH,CAAC;IAED,KAAK,CAAC,QAAkB,EAAE,EAAU;QAClC,MAAM,CAAC,GAAI,IAAI,CAAC,UAAU,CAAA;QAC1B,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;QAChD,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;QAChD,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;QAChD,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAA;QACtC,IAAI,EAAE,GAAG,IAAI;YAAE,OAAM;QACrB,IAAI,CAAC,CAAC,WAAW,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW;YAAE,OAAM;QACnE,MAAM,CAAC,GAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC3B,MAAM,EAAE,GAAM,CAAC,CAAC,WAAW,CAAA;QAC3B,MAAM,KAAK,GAAG,EAAE,GAAG,CAAC,CAAA;QACpB,iDAAiD;QACjD,MAAM,GAAG,GAAG,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAA;QACvE,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,GAAG,EAAE,CAAA;QAC1C,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,GAAG,EAAE,CAAA;QAC1C,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,GAAG,EAAE,CAAA;IAC5C,CAAC;CACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Vector3 } from 'three';
|
|
2
|
+
import { BaseForce } from '../core/BaseForce.js';
|
|
3
|
+
import type { Particle } from '../core/Particle.js';
|
|
4
|
+
/** Velocity-dependent lateral force (Blender: Force Field → Magnetic) */
|
|
5
|
+
export declare class MagneticForce extends BaseForce {
|
|
6
|
+
parameters: Record<string, number>;
|
|
7
|
+
/** World-space field origin for maxDistance range check. Not GSAP-animatable. */
|
|
8
|
+
position: Vector3;
|
|
9
|
+
constructor(opts?: {
|
|
10
|
+
strength?: number;
|
|
11
|
+
axisX?: number;
|
|
12
|
+
axisY?: number;
|
|
13
|
+
axisZ?: number;
|
|
14
|
+
maxDistance?: number;
|
|
15
|
+
px?: number;
|
|
16
|
+
py?: number;
|
|
17
|
+
pz?: number;
|
|
18
|
+
});
|
|
19
|
+
apply(particle: Particle, dt: number): void;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=MagneticForce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MagneticForce.d.ts","sourceRoot":"","sources":["../../src/forces/MagneticForce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAS,OAAO,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAQ,sBAAsB,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD,yEAAyE;AACzE,qBAAa,aAAc,SAAQ,SAAS;IAC1C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAElC,iFAAiF;IACjF,QAAQ,EAAE,OAAO,CAAA;gBAEL,IAAI,GAAE;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;QACjE,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KACjC;IAYN,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;CAY5C"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Vector3 } from 'three';
|
|
2
|
+
import { BaseForce } from '../core/BaseForce.js';
|
|
3
|
+
/** Velocity-dependent lateral force (Blender: Force Field → Magnetic) */
|
|
4
|
+
export class MagneticForce extends BaseForce {
|
|
5
|
+
constructor(opts = {}) {
|
|
6
|
+
super();
|
|
7
|
+
this.position = new Vector3(opts.px ?? 0, opts.py ?? 0, opts.pz ?? 0);
|
|
8
|
+
this.parameters = {
|
|
9
|
+
strength: opts.strength ?? 1,
|
|
10
|
+
axisX: opts.axisX ?? 0,
|
|
11
|
+
axisY: opts.axisY ?? 1,
|
|
12
|
+
axisZ: opts.axisZ ?? 0,
|
|
13
|
+
maxDistance: opts.maxDistance ?? 0,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
apply(particle, dt) {
|
|
17
|
+
const p = this.parameters;
|
|
18
|
+
if (p.maxDistance > 0) {
|
|
19
|
+
const dx = particle.position.x - this.position.x;
|
|
20
|
+
const dy = particle.position.y - this.position.y;
|
|
21
|
+
const dz = particle.position.z - this.position.z;
|
|
22
|
+
if (dx * dx + dy * dy + dz * dz > p.maxDistance * p.maxDistance)
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const field = new Vector3(p.axisX, p.axisY, p.axisZ).normalize();
|
|
26
|
+
const force = new Vector3().crossVectors(particle.velocity, field);
|
|
27
|
+
particle.velocity.addScaledVector(force, p.strength * dt);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=MagneticForce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MagneticForce.js","sourceRoot":"","sources":["../../src/forces/MagneticForce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAS,OAAO,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAQ,sBAAsB,CAAA;AAGlD,yEAAyE;AACzE,MAAM,OAAO,aAAc,SAAQ,SAAS;IAM1C,YAAY,OAIR,EAAE;QACJ,KAAK,EAAE,CAAA;QACP,IAAI,CAAC,QAAQ,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,CAAA;QACrE,IAAI,CAAC,UAAU,GAAG;YAChB,QAAQ,EAAK,IAAI,CAAC,QAAQ,IAAO,CAAC;YAClC,KAAK,EAAQ,IAAI,CAAC,KAAK,IAAS,CAAC;YACjC,KAAK,EAAQ,IAAI,CAAC,KAAK,IAAS,CAAC;YACjC,KAAK,EAAQ,IAAI,CAAC,KAAK,IAAS,CAAC;YACjC,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,CAAC;SACnC,CAAA;IACH,CAAC;IAED,KAAK,CAAC,QAAkB,EAAE,EAAU;QAClC,MAAM,CAAC,GAAG,IAAI,CAAC,UAAU,CAAA;QACzB,IAAI,CAAC,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;YAChD,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;YAChD,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;YAChD,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW;gBAAE,OAAM;QACzE,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,EAAE,CAAA;QAChE,MAAM,KAAK,GAAG,IAAI,OAAO,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;QAClE,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAA;IAC3D,CAAC;CACF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { DataTexture } from 'three';
|
|
2
|
+
import { BaseForce } from '../core/BaseForce.js';
|
|
3
|
+
import type { Particle } from '../core/Particle.js';
|
|
4
|
+
export interface TextureForceOptions {
|
|
5
|
+
/** World units covered by one full texture tile. Default 10. */
|
|
6
|
+
scale?: number;
|
|
7
|
+
/** Force multiplier. Default 1. */
|
|
8
|
+
strength?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* TextureForce — per-particle force direction sampled from a DataTexture.
|
|
12
|
+
* RGB (or XYZ float) channels map to world-space X/Y/Z force components.
|
|
13
|
+
*
|
|
14
|
+
* Uint8 textures: channels [0..255] are remapped to [-1..1].
|
|
15
|
+
* FloatType textures: channels are used as-is.
|
|
16
|
+
*
|
|
17
|
+
* 2D sampling: particle.position XZ is projected onto the texture plane.
|
|
18
|
+
* Tile wraps, so the field repeats every `scale` world units.
|
|
19
|
+
* (Blender: Force Fields → Texture)
|
|
20
|
+
*/
|
|
21
|
+
export declare class TextureForce extends BaseForce {
|
|
22
|
+
/** All scalar inputs — GSAP / keyframe compatible */
|
|
23
|
+
parameters: {
|
|
24
|
+
strength: number;
|
|
25
|
+
scale: number;
|
|
26
|
+
/** World-space offset applied before UV projection */
|
|
27
|
+
offsetX: number;
|
|
28
|
+
offsetZ: number;
|
|
29
|
+
};
|
|
30
|
+
private readonly _data;
|
|
31
|
+
private readonly _width;
|
|
32
|
+
private readonly _height;
|
|
33
|
+
private readonly _isFloat;
|
|
34
|
+
constructor(texture: DataTexture, opts?: TextureForceOptions);
|
|
35
|
+
apply(particle: Particle, dt: number): void;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=TextureForce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TextureForce.d.ts","sourceRoot":"","sources":["../../src/forces/TextureForce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AACnC,OAAO,EAAE,SAAS,EAAE,MAAQ,sBAAsB,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,KAAK,CAAC,EAAK,MAAM,CAAA;IACjB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,YAAa,SAAQ,SAAS;IACzC,qDAAqD;IACrD,UAAU,EAAE;QACV,QAAQ,EAAE,MAAM,CAAA;QAChB,KAAK,EAAK,MAAM,CAAA;QAChB,sDAAsD;QACtD,OAAO,EAAG,MAAM,CAAA;QAChB,OAAO,EAAG,MAAM,CAAA;KACjB,CAAA;IAED,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;gBAEvB,OAAO,EAAE,WAAW,EAAE,IAAI,GAAE,mBAAwB;IAgBhE,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;CA8B5C"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { BaseForce } from '../core/BaseForce.js';
|
|
2
|
+
/**
|
|
3
|
+
* TextureForce — per-particle force direction sampled from a DataTexture.
|
|
4
|
+
* RGB (or XYZ float) channels map to world-space X/Y/Z force components.
|
|
5
|
+
*
|
|
6
|
+
* Uint8 textures: channels [0..255] are remapped to [-1..1].
|
|
7
|
+
* FloatType textures: channels are used as-is.
|
|
8
|
+
*
|
|
9
|
+
* 2D sampling: particle.position XZ is projected onto the texture plane.
|
|
10
|
+
* Tile wraps, so the field repeats every `scale` world units.
|
|
11
|
+
* (Blender: Force Fields → Texture)
|
|
12
|
+
*/
|
|
13
|
+
export class TextureForce extends BaseForce {
|
|
14
|
+
constructor(texture, opts = {}) {
|
|
15
|
+
super();
|
|
16
|
+
this.parameters = {
|
|
17
|
+
strength: opts.strength ?? 1.0,
|
|
18
|
+
scale: opts.scale ?? 10.0,
|
|
19
|
+
offsetX: 0,
|
|
20
|
+
offsetZ: 0,
|
|
21
|
+
};
|
|
22
|
+
const img = texture.image;
|
|
23
|
+
this._data = img.data;
|
|
24
|
+
this._width = img.width;
|
|
25
|
+
this._height = img.height;
|
|
26
|
+
this._isFloat = this._data instanceof Float32Array;
|
|
27
|
+
}
|
|
28
|
+
apply(particle, dt) {
|
|
29
|
+
const { strength, scale, offsetX, offsetZ } = this.parameters;
|
|
30
|
+
const invScale = 1 / Math.max(scale, 1e-6);
|
|
31
|
+
// Map particle XZ to tiling UV [0..1)
|
|
32
|
+
const u = (((particle.position.x - offsetX) * invScale % 1) + 1) % 1;
|
|
33
|
+
const v = (((particle.position.z - offsetZ) * invScale % 1) + 1) % 1;
|
|
34
|
+
const px = Math.floor(u * this._width) % this._width;
|
|
35
|
+
const py = Math.floor(v * this._height) % this._height;
|
|
36
|
+
const idx = (py * this._width + px) * 4; // RGBA stride
|
|
37
|
+
let fx, fy, fz;
|
|
38
|
+
if (this._isFloat) {
|
|
39
|
+
const d = this._data;
|
|
40
|
+
fx = d[idx];
|
|
41
|
+
fy = d[idx + 1];
|
|
42
|
+
fz = d[idx + 2];
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const d = this._data;
|
|
46
|
+
// [0..255] → [-1..1]
|
|
47
|
+
fx = d[idx] / 127.5 - 1;
|
|
48
|
+
fy = d[idx + 1] / 127.5 - 1;
|
|
49
|
+
fz = d[idx + 2] / 127.5 - 1;
|
|
50
|
+
}
|
|
51
|
+
particle.velocity.x += fx * strength * dt;
|
|
52
|
+
particle.velocity.y += fy * strength * dt;
|
|
53
|
+
particle.velocity.z += fz * strength * dt;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=TextureForce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TextureForce.js","sourceRoot":"","sources":["../../src/forces/TextureForce.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Vector3 } from 'three';
|
|
2
|
+
import { BaseForce } from '../core/BaseForce.js';
|
|
3
|
+
import type { Particle } from '../core/Particle.js';
|
|
4
|
+
/** Noise-based random force (Blender: Force Field → Turbulence) */
|
|
5
|
+
export declare class TurbulenceForce extends BaseForce {
|
|
6
|
+
parameters: Record<string, number>;
|
|
7
|
+
/** World-space field origin for maxDistance range check. Not GSAP-animatable. */
|
|
8
|
+
position: Vector3;
|
|
9
|
+
constructor(opts?: {
|
|
10
|
+
strength?: number;
|
|
11
|
+
scale?: number;
|
|
12
|
+
speed?: number;
|
|
13
|
+
maxDistance?: number;
|
|
14
|
+
px?: number;
|
|
15
|
+
py?: number;
|
|
16
|
+
pz?: number;
|
|
17
|
+
});
|
|
18
|
+
apply(particle: Particle, dt: number): void;
|
|
19
|
+
private _hash;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=TurbulenceForce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TurbulenceForce.d.ts","sourceRoot":"","sources":["../../src/forces/TurbulenceForce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAS,OAAO,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAQ,sBAAsB,CAAA;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD,mEAAmE;AACnE,qBAAa,eAAgB,SAAQ,SAAS;IAC5C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAElC,iFAAiF;IACjF,QAAQ,EAAE,OAAO,CAAA;gBAEL,IAAI,GAAE;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;QACvE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KACjC;IAWN,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAmB3C,OAAO,CAAC,KAAK;CAId"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Vector3 } from 'three';
|
|
2
|
+
import { BaseForce } from '../core/BaseForce.js';
|
|
3
|
+
/** Noise-based random force (Blender: Force Field → Turbulence) */
|
|
4
|
+
export class TurbulenceForce extends BaseForce {
|
|
5
|
+
constructor(opts = {}) {
|
|
6
|
+
super();
|
|
7
|
+
this.position = new Vector3(opts.px ?? 0, opts.py ?? 0, opts.pz ?? 0);
|
|
8
|
+
this.parameters = {
|
|
9
|
+
strength: opts.strength ?? 1,
|
|
10
|
+
scale: opts.scale ?? 1,
|
|
11
|
+
speed: opts.speed ?? 1,
|
|
12
|
+
maxDistance: opts.maxDistance ?? 0,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
apply(particle, dt) {
|
|
16
|
+
const { strength, scale, speed, maxDistance } = this.parameters;
|
|
17
|
+
if (maxDistance > 0) {
|
|
18
|
+
const dx = particle.position.x - this.position.x;
|
|
19
|
+
const dy = particle.position.y - this.position.y;
|
|
20
|
+
const dz = particle.position.z - this.position.z;
|
|
21
|
+
if (dx * dx + dy * dy + dz * dz > maxDistance * maxDistance)
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const t = performance.now() * 0.001 * speed;
|
|
25
|
+
const x = particle.position.x * scale;
|
|
26
|
+
const z = particle.position.z * scale;
|
|
27
|
+
const nx = this._hash(x + 0.0, z + 13.7, t + 0.0);
|
|
28
|
+
const ny = this._hash(x + 4.1, z + 7.3, t + 1.3);
|
|
29
|
+
const nz = this._hash(x + 9.2, z + 2.8, t + 2.7);
|
|
30
|
+
particle.velocity.x += nx * strength * dt;
|
|
31
|
+
particle.velocity.y += ny * strength * dt;
|
|
32
|
+
particle.velocity.z += nz * strength * dt;
|
|
33
|
+
}
|
|
34
|
+
_hash(x, y, z) {
|
|
35
|
+
const n = Math.sin(x * 127.1 + y * 311.7 + z * 74.7) * 43758.5453;
|
|
36
|
+
return (n - Math.floor(n)) * 2 - 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=TurbulenceForce.js.map
|