@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.
Files changed (153) hide show
  1. package/dist/core/BaseEmitter.d.ts +50 -0
  2. package/dist/core/BaseEmitter.d.ts.map +1 -0
  3. package/dist/core/BaseEmitter.js +53 -0
  4. package/dist/core/BaseEmitter.js.map +1 -0
  5. package/dist/core/BaseForce.d.ts +21 -0
  6. package/dist/core/BaseForce.d.ts.map +1 -0
  7. package/dist/core/BaseForce.js +15 -0
  8. package/dist/core/BaseForce.js.map +1 -0
  9. package/dist/core/BaseRenderer.d.ts +96 -0
  10. package/dist/core/BaseRenderer.d.ts.map +1 -0
  11. package/dist/core/BaseRenderer.js +190 -0
  12. package/dist/core/BaseRenderer.js.map +1 -0
  13. package/dist/core/DeflectorCollider.d.ts +46 -0
  14. package/dist/core/DeflectorCollider.d.ts.map +1 -0
  15. package/dist/core/DeflectorCollider.js +113 -0
  16. package/dist/core/DeflectorCollider.js.map +1 -0
  17. package/dist/core/Particle.d.ts +29 -0
  18. package/dist/core/Particle.d.ts.map +1 -0
  19. package/dist/core/Particle.js +41 -0
  20. package/dist/core/Particle.js.map +1 -0
  21. package/dist/core/ParticleCache.d.ts +73 -0
  22. package/dist/core/ParticleCache.d.ts.map +1 -0
  23. package/dist/core/ParticleCache.js +206 -0
  24. package/dist/core/ParticleCache.js.map +1 -0
  25. package/dist/core/ParticleSystem.d.ts +192 -0
  26. package/dist/core/ParticleSystem.d.ts.map +1 -0
  27. package/dist/core/ParticleSystem.js +484 -0
  28. package/dist/core/ParticleSystem.js.map +1 -0
  29. package/dist/core/SeededRandom.d.ts +17 -0
  30. package/dist/core/SeededRandom.d.ts.map +1 -0
  31. package/dist/core/SeededRandom.js +27 -0
  32. package/dist/core/SeededRandom.js.map +1 -0
  33. package/dist/emitters/EdgeEmitter.d.ts +43 -0
  34. package/dist/emitters/EdgeEmitter.d.ts.map +1 -0
  35. package/dist/emitters/EdgeEmitter.js +78 -0
  36. package/dist/emitters/EdgeEmitter.js.map +1 -0
  37. package/dist/emitters/MeshEmitter.d.ts +83 -0
  38. package/dist/emitters/MeshEmitter.d.ts.map +1 -0
  39. package/dist/emitters/MeshEmitter.js +240 -0
  40. package/dist/emitters/MeshEmitter.js.map +1 -0
  41. package/dist/emitters/PointEmitter.d.ts +22 -0
  42. package/dist/emitters/PointEmitter.d.ts.map +1 -0
  43. package/dist/emitters/PointEmitter.js +26 -0
  44. package/dist/emitters/PointEmitter.js.map +1 -0
  45. package/dist/forces/BoidField.d.ts +58 -0
  46. package/dist/forces/BoidField.d.ts.map +1 -0
  47. package/dist/forces/BoidField.js +82 -0
  48. package/dist/forces/BoidField.js.map +1 -0
  49. package/dist/forces/BoidForce.d.ts +54 -0
  50. package/dist/forces/BoidForce.d.ts.map +1 -0
  51. package/dist/forces/BoidForce.js +233 -0
  52. package/dist/forces/BoidForce.js.map +1 -0
  53. package/dist/forces/ChargeForce.d.ts +22 -0
  54. package/dist/forces/ChargeForce.d.ts.map +1 -0
  55. package/dist/forces/ChargeForce.js +35 -0
  56. package/dist/forces/ChargeForce.js.map +1 -0
  57. package/dist/forces/CurveGuideForce.d.ts +43 -0
  58. package/dist/forces/CurveGuideForce.d.ts.map +1 -0
  59. package/dist/forces/CurveGuideForce.js +118 -0
  60. package/dist/forces/CurveGuideForce.js.map +1 -0
  61. package/dist/forces/DragForce.d.ts +19 -0
  62. package/dist/forces/DragForce.d.ts.map +1 -0
  63. package/dist/forces/DragForce.js +31 -0
  64. package/dist/forces/DragForce.js.map +1 -0
  65. package/dist/forces/FlowFieldForce.d.ts +72 -0
  66. package/dist/forces/FlowFieldForce.d.ts.map +1 -0
  67. package/dist/forces/FlowFieldForce.js +68 -0
  68. package/dist/forces/FlowFieldForce.js.map +1 -0
  69. package/dist/forces/ForceField.d.ts +26 -0
  70. package/dist/forces/ForceField.d.ts.map +1 -0
  71. package/dist/forces/ForceField.js +41 -0
  72. package/dist/forces/ForceField.js.map +1 -0
  73. package/dist/forces/GravityForce.d.ts +9 -0
  74. package/dist/forces/GravityForce.d.ts.map +1 -0
  75. package/dist/forces/GravityForce.js +12 -0
  76. package/dist/forces/GravityForce.js.map +1 -0
  77. package/dist/forces/HarmonicForce.d.ts +22 -0
  78. package/dist/forces/HarmonicForce.d.ts.map +1 -0
  79. package/dist/forces/HarmonicForce.js +31 -0
  80. package/dist/forces/HarmonicForce.js.map +1 -0
  81. package/dist/forces/LennardJonesForce.d.ts +23 -0
  82. package/dist/forces/LennardJonesForce.d.ts.map +1 -0
  83. package/dist/forces/LennardJonesForce.js +38 -0
  84. package/dist/forces/LennardJonesForce.js.map +1 -0
  85. package/dist/forces/MagneticForce.d.ts +21 -0
  86. package/dist/forces/MagneticForce.d.ts.map +1 -0
  87. package/dist/forces/MagneticForce.js +30 -0
  88. package/dist/forces/MagneticForce.js.map +1 -0
  89. package/dist/forces/TextureForce.d.ts +37 -0
  90. package/dist/forces/TextureForce.d.ts.map +1 -0
  91. package/dist/forces/TextureForce.js +56 -0
  92. package/dist/forces/TextureForce.js.map +1 -0
  93. package/dist/forces/TurbulenceForce.d.ts +21 -0
  94. package/dist/forces/TurbulenceForce.d.ts.map +1 -0
  95. package/dist/forces/TurbulenceForce.js +39 -0
  96. package/dist/forces/TurbulenceForce.js.map +1 -0
  97. package/dist/forces/VortexForce.d.ts +21 -0
  98. package/dist/forces/VortexForce.d.ts.map +1 -0
  99. package/dist/forces/VortexForce.js +29 -0
  100. package/dist/forces/VortexForce.js.map +1 -0
  101. package/dist/forces/WindForce.d.ts +21 -0
  102. package/dist/forces/WindForce.d.ts.map +1 -0
  103. package/dist/forces/WindForce.js +29 -0
  104. package/dist/forces/WindForce.js.map +1 -0
  105. package/dist/index.d.ts +56 -0
  106. package/dist/index.d.ts.map +1 -0
  107. package/dist/index.js +44 -0
  108. package/dist/index.js.map +1 -0
  109. package/dist/physics/KeyedPhysics.d.ts +43 -0
  110. package/dist/physics/KeyedPhysics.d.ts.map +1 -0
  111. package/dist/physics/KeyedPhysics.js +73 -0
  112. package/dist/physics/KeyedPhysics.js.map +1 -0
  113. package/dist/physics/SPHPhysics.d.ts +27 -0
  114. package/dist/physics/SPHPhysics.d.ts.map +1 -0
  115. package/dist/physics/SPHPhysics.js +119 -0
  116. package/dist/physics/SPHPhysics.js.map +1 -0
  117. package/dist/renderers/BillboardRenderer.d.ts +75 -0
  118. package/dist/renderers/BillboardRenderer.d.ts.map +1 -0
  119. package/dist/renderers/BillboardRenderer.js +106 -0
  120. package/dist/renderers/BillboardRenderer.js.map +1 -0
  121. package/dist/renderers/CollectionRenderer.d.ts +83 -0
  122. package/dist/renderers/CollectionRenderer.d.ts.map +1 -0
  123. package/dist/renderers/CollectionRenderer.js +161 -0
  124. package/dist/renderers/CollectionRenderer.js.map +1 -0
  125. package/dist/renderers/HaloRenderer.d.ts +3 -0
  126. package/dist/renderers/HaloRenderer.d.ts.map +1 -0
  127. package/dist/renderers/HaloRenderer.js +4 -0
  128. package/dist/renderers/HaloRenderer.js.map +1 -0
  129. package/dist/renderers/InstanceRenderer.d.ts +70 -0
  130. package/dist/renderers/InstanceRenderer.d.ts.map +1 -0
  131. package/dist/renderers/InstanceRenderer.js +86 -0
  132. package/dist/renderers/InstanceRenderer.js.map +1 -0
  133. package/dist/renderers/LineRenderer.d.ts +52 -0
  134. package/dist/renderers/LineRenderer.d.ts.map +1 -0
  135. package/dist/renderers/LineRenderer.js +96 -0
  136. package/dist/renderers/LineRenderer.js.map +1 -0
  137. package/dist/renderers/NoneRenderer.d.ts +19 -0
  138. package/dist/renderers/NoneRenderer.d.ts.map +1 -0
  139. package/dist/renderers/NoneRenderer.js +24 -0
  140. package/dist/renderers/NoneRenderer.js.map +1 -0
  141. package/dist/renderers/ObjectRenderer.d.ts +3 -0
  142. package/dist/renderers/ObjectRenderer.d.ts.map +1 -0
  143. package/dist/renderers/ObjectRenderer.js +4 -0
  144. package/dist/renderers/ObjectRenderer.js.map +1 -0
  145. package/dist/renderers/StrandRenderer.d.ts +169 -0
  146. package/dist/renderers/StrandRenderer.d.ts.map +1 -0
  147. package/dist/renderers/StrandRenderer.js +235 -0
  148. package/dist/renderers/StrandRenderer.js.map +1 -0
  149. package/dist/renderers/TrailRenderer.d.ts +54 -0
  150. package/dist/renderers/TrailRenderer.d.ts.map +1 -0
  151. package/dist/renderers/TrailRenderer.js +114 -0
  152. package/dist/renderers/TrailRenderer.js.map +1 -0
  153. package/package.json +45 -0
@@ -0,0 +1,78 @@
1
+ import { Vector3 } from 'three';
2
+ import { BaseEmitter } from '../core/BaseEmitter.js';
3
+ /**
4
+ * EdgeEmitter — emits particles from mesh edges.
5
+ *
6
+ * Primary use case: waterline foam — edges where the ocean surface
7
+ * crosses y ≈ 0 (the shore) are the emission source.
8
+ *
9
+ * yThreshold filters to only edges near the water surface.
10
+ */
11
+ export class EdgeEmitter extends BaseEmitter {
12
+ constructor(opts = {}) {
13
+ super(opts);
14
+ /** Cached candidate edges — rebuilt when geometry reference changes */
15
+ this._edgeCache = [];
16
+ this._lastGeo = null;
17
+ this.parameters.yThreshold = opts.yThreshold ?? Infinity;
18
+ this.parameters.targetY = opts.targetY ?? 0;
19
+ this.parameters.zMin = opts.zMin ?? -Infinity;
20
+ this.parameters.zMax = opts.zMax ?? Infinity;
21
+ }
22
+ spawn(particle, geometry, rng) {
23
+ if (!geometry)
24
+ return;
25
+ const pos = geometry.getAttribute('position');
26
+ const norm = geometry.getAttribute('normal');
27
+ const index = geometry.getIndex();
28
+ const yThr = this.parameters.yThreshold;
29
+ const targetY = this.parameters.targetY;
30
+ const zMin = this.parameters.zMin;
31
+ const zMax = this.parameters.zMax;
32
+ if (!pos)
33
+ return;
34
+ // Rebuild edge cache only when geometry changes.
35
+ // Without caching, every spawn scans all 18k+ edges — O(n) per particle.
36
+ // With caching, scan happens once per geometry rebuild (once per frame).
37
+ if (geometry !== this._lastGeo) {
38
+ this._lastGeo = geometry;
39
+ this._edgeCache = [];
40
+ const addEdge = (a, b) => {
41
+ const my = (pos.getY(a) + pos.getY(b)) * 0.5;
42
+ const mz = (pos.getZ(a) + pos.getZ(b)) * 0.5;
43
+ if (Math.abs(my - targetY) > yThr)
44
+ return;
45
+ if (mz < zMin || mz > zMax)
46
+ return;
47
+ this._edgeCache.push([a, b]);
48
+ };
49
+ if (index) {
50
+ for (let i = 0; i < index.count; i += 3) {
51
+ const a = index.getX(i), b = index.getX(i + 1), c = index.getX(i + 2);
52
+ addEdge(a, b);
53
+ addEdge(b, c);
54
+ addEdge(c, a);
55
+ }
56
+ }
57
+ else {
58
+ for (let i = 0; i < pos.count; i += 3) {
59
+ addEdge(i, i + 1);
60
+ addEdge(i + 1, i + 2);
61
+ addEdge(i + 2, i);
62
+ }
63
+ }
64
+ }
65
+ if (this._edgeCache.length === 0)
66
+ return;
67
+ const [a, b] = this._edgeCache[Math.floor(rng.next() * this._edgeCache.length)];
68
+ // Random point along the edge
69
+ const t = rng.next();
70
+ particle.position.set(pos.getX(a) * (1 - t) + pos.getX(b) * t, pos.getY(a) * (1 - t) + pos.getY(b) * t, pos.getZ(a) * (1 - t) + pos.getZ(b) * t);
71
+ const normal = norm
72
+ ? new Vector3(norm.getX(a) * (1 - t) + norm.getX(b) * t, norm.getY(a) * (1 - t) + norm.getY(b) * t, norm.getZ(a) * (1 - t) + norm.getZ(b) * t).normalize()
73
+ : new Vector3(0, 1, 0);
74
+ const tangent = new Vector3(pos.getX(b) - pos.getX(a), pos.getY(b) - pos.getY(a), pos.getZ(b) - pos.getZ(a)).normalize();
75
+ this.applyVelocity(particle, normal, tangent, rng);
76
+ }
77
+ }
78
+ //# sourceMappingURL=EdgeEmitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EdgeEmitter.js","sourceRoot":"","sources":["../../src/emitters/EdgeEmitter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import { BufferGeometry, Texture } from 'three';
2
+ import { BaseEmitter, BaseEmitterOptions } from '../core/BaseEmitter.js';
3
+ import type { Particle } from '../core/Particle.js';
4
+ import type { SeededRandom } from '../core/SeededRandom.js';
5
+ export interface MeshEmitterOptions extends BaseEmitterOptions {
6
+ /**
7
+ * Name of a Float32BufferAttribute (values in [0, 1] per vertex) that
8
+ * drives emission density — like Blender's Source → Vertex Group → Density.
9
+ * Stored as a string, NOT in parameters, because strings are not
10
+ * GSAP-animatable. Use weightStrength (in parameters) to blend.
11
+ */
12
+ weightAttribute?: string;
13
+ /**
14
+ * Name of a Float32BufferAttribute (values in [0, 1] per vertex) that
15
+ * scales particle size at spawn — like Blender's Source → Vertex Group → Size.
16
+ * Use sizeStrength (in parameters) to blend between no effect and full effect.
17
+ */
18
+ sizeAttribute?: string;
19
+ /** 0–1 blend: 0 = no size attribute effect, 1 = full effect. Default 1. */
20
+ sizeStrength?: number;
21
+ }
22
+ /**
23
+ * MeshEmitter — emits particles from the surface of a BufferGeometry.
24
+ *
25
+ * Emit From modes (Blender: Emit From):
26
+ * verts — spawn at a random vertex position
27
+ * faces — spawn at a random point on a random triangle face
28
+ * volume — spawn at a random point inside the mesh bounding box
29
+ *
30
+ * Even Distribution (Blender: Even Distribution):
31
+ * false — pick a random face regardless of area
32
+ * true — pick faces weighted by area so large faces emit more
33
+ *
34
+ * Source Vertex Groups (Blender: Source → Vertex Group → Density):
35
+ * Set weightAttribute to the name of a Float32BufferAttribute ([0,1]).
36
+ * Adjust weightStrength (parameters) to blend from uniform to full weighting.
37
+ *
38
+ * Also reads the `foam` Float32BufferAttribute if present —
39
+ * used by OceanModifier geometry to weight emission toward wave crests.
40
+ */
41
+ export declare class MeshEmitter extends BaseEmitter {
42
+ private _faceCDF;
43
+ private _geoVersion;
44
+ /**
45
+ * Name of the per-vertex Float32BufferAttribute used as emission density weights.
46
+ * Not in parameters — strings are not GSAP-animatable.
47
+ */
48
+ weightAttribute: string | null;
49
+ /**
50
+ * Name of the per-vertex Float32BufferAttribute used to scale particle size at spawn.
51
+ * Not in parameters — strings are not GSAP-animatable.
52
+ */
53
+ sizeAttribute: string | null;
54
+ /**
55
+ * THREE.Texture whose red channel drives emission density (UV-sampled per face).
56
+ * Not in parameters — object references are not GSAP-animatable.
57
+ * When set, red channel value at each face's UV centroid is used as a per-face weight.
58
+ * null = uniform (or weightAttribute-only) distribution.
59
+ */
60
+ densityTexture: Texture | null;
61
+ private _lastWeightAttribute;
62
+ private _lastDensityTexture;
63
+ constructor(opts?: MeshEmitterOptions);
64
+ spawn(particle: Particle, geometry: BufferGeometry | null, rng: SeededRandom): void;
65
+ private _spawnFromVert;
66
+ private _spawnFromFace;
67
+ private _spawnFromVolume;
68
+ private _buildCDF;
69
+ /**
70
+ * Sample the red channel of densityTexture at normalised UV (u,v).
71
+ * Returns a value in [0, 1]. Falls back to 1.0 if the texture has no
72
+ * readable image data.
73
+ */
74
+ private _sampleTexture;
75
+ private _sampleCDF;
76
+ /**
77
+ * Apply the sizeAttribute multiplier to particle.size at spawn time.
78
+ * a/b/c are vertex indices; w0/w1/w2 are barycentric weights (must sum to 1).
79
+ */
80
+ private _applySizeAttribute;
81
+ private _makeTangent;
82
+ }
83
+ //# sourceMappingURL=MeshEmitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MeshEmitter.d.ts","sourceRoot":"","sources":["../../src/emitters/MeshEmitter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,cAAc,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AACxE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAU,qBAAqB,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAE3D,MAAM,WAAW,kBAAmB,SAAQ,kBAAkB;IAC5D;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,WAAY,SAAQ,WAAW;IAC1C,OAAO,CAAC,QAAQ,CAAoC;IACpD,OAAO,CAAC,WAAW,CAAK;IAExB;;;OAGG;IACH,eAAe,EAAE,MAAM,GAAG,IAAI,CAAO;IAErC;;;OAGG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAO;IAEnC;;;;;OAKG;IACH,cAAc,EAAE,OAAO,GAAG,IAAI,CAAO;IAErC,OAAO,CAAC,oBAAoB,CAAsB;IAClD,OAAO,CAAC,mBAAmB,CAAwB;gBAEvC,IAAI,GAAE,kBAAuB;IAQzC,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,EAAE,GAAG,EAAE,YAAY,GAAG,IAAI;IAcnF,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,cAAc;IAmEtB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,SAAS;IAwCjB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAqCtB,OAAO,CAAC,UAAU;IAUlB;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAc3B,OAAO,CAAC,YAAY;CAIrB"}
@@ -0,0 +1,240 @@
1
+ import { Vector3 } from 'three';
2
+ import { BaseEmitter } from '../core/BaseEmitter.js';
3
+ /**
4
+ * MeshEmitter — emits particles from the surface of a BufferGeometry.
5
+ *
6
+ * Emit From modes (Blender: Emit From):
7
+ * verts — spawn at a random vertex position
8
+ * faces — spawn at a random point on a random triangle face
9
+ * volume — spawn at a random point inside the mesh bounding box
10
+ *
11
+ * Even Distribution (Blender: Even Distribution):
12
+ * false — pick a random face regardless of area
13
+ * true — pick faces weighted by area so large faces emit more
14
+ *
15
+ * Source Vertex Groups (Blender: Source → Vertex Group → Density):
16
+ * Set weightAttribute to the name of a Float32BufferAttribute ([0,1]).
17
+ * Adjust weightStrength (parameters) to blend from uniform to full weighting.
18
+ *
19
+ * Also reads the `foam` Float32BufferAttribute if present —
20
+ * used by OceanModifier geometry to weight emission toward wave crests.
21
+ */
22
+ export class MeshEmitter extends BaseEmitter {
23
+ constructor(opts = {}) {
24
+ super(opts);
25
+ this._faceCDF = new Float32Array(0); // cumulative area for weighted sampling
26
+ this._geoVersion = -1;
27
+ /**
28
+ * Name of the per-vertex Float32BufferAttribute used as emission density weights.
29
+ * Not in parameters — strings are not GSAP-animatable.
30
+ */
31
+ this.weightAttribute = null;
32
+ /**
33
+ * Name of the per-vertex Float32BufferAttribute used to scale particle size at spawn.
34
+ * Not in parameters — strings are not GSAP-animatable.
35
+ */
36
+ this.sizeAttribute = null;
37
+ /**
38
+ * THREE.Texture whose red channel drives emission density (UV-sampled per face).
39
+ * Not in parameters — object references are not GSAP-animatable.
40
+ * When set, red channel value at each face's UV centroid is used as a per-face weight.
41
+ * null = uniform (or weightAttribute-only) distribution.
42
+ */
43
+ this.densityTexture = null;
44
+ this._lastWeightAttribute = null;
45
+ this._lastDensityTexture = null;
46
+ this.parameters.weightStrength = 1;
47
+ this.parameters.sizeStrength = opts.sizeStrength ?? 1;
48
+ this.weightAttribute = opts.weightAttribute ?? null;
49
+ this.sizeAttribute = opts.sizeAttribute ?? null;
50
+ }
51
+ spawn(particle, geometry, rng) {
52
+ if (!geometry)
53
+ return;
54
+ const mode = this.parameters.emitFrom; // 0=verts, 1=faces, 2=volume
55
+ if (mode === 0) {
56
+ this._spawnFromVert(particle, geometry, rng);
57
+ }
58
+ else if (mode === 1) {
59
+ this._spawnFromFace(particle, geometry, rng);
60
+ }
61
+ else {
62
+ this._spawnFromVolume(particle, geometry, rng);
63
+ }
64
+ }
65
+ _spawnFromVert(particle, geo, rng) {
66
+ const pos = geo.getAttribute('position');
67
+ const norm = geo.getAttribute('normal');
68
+ if (!pos)
69
+ return;
70
+ const idx = Math.floor(rng.next() * pos.count);
71
+ particle.position.fromBufferAttribute(pos, idx);
72
+ const normal = norm ? new Vector3().fromBufferAttribute(norm, idx).normalize() : new Vector3(0, 1, 0);
73
+ const tangent = this._makeTangent(normal);
74
+ this.applyVelocity(particle, normal, tangent, rng);
75
+ this._applySizeAttribute(particle, geo, idx, idx, idx, 0, 0, 1);
76
+ }
77
+ _spawnFromFace(particle, geo, rng) {
78
+ const pos = geo.getAttribute('position');
79
+ const norm = geo.getAttribute('normal');
80
+ const idx = geo.getIndex();
81
+ if (!pos)
82
+ return;
83
+ let triCount;
84
+ let getTriVerts;
85
+ if (idx) {
86
+ triCount = Math.floor(idx.count / 3);
87
+ getTriVerts = (i) => [idx.getX(i * 3), idx.getX(i * 3 + 1), idx.getX(i * 3 + 2)];
88
+ }
89
+ else {
90
+ triCount = Math.floor(pos.count / 3);
91
+ getTriVerts = (i) => [i * 3, i * 3 + 1, i * 3 + 2];
92
+ }
93
+ if (triCount === 0)
94
+ return;
95
+ const evenDist = this.parameters.evenDistribution > 0;
96
+ let triIndex;
97
+ if (evenDist) {
98
+ // Rebuild CDF if geometry or texture/weight inputs changed
99
+ const weightAttr = this.weightAttribute;
100
+ if (this._faceCDF.length !== triCount ||
101
+ weightAttr !== this._lastWeightAttribute ||
102
+ this.densityTexture !== this._lastDensityTexture) {
103
+ const weightBuf = weightAttr ? geo.getAttribute(weightAttr) : null;
104
+ const uvBuf = this.densityTexture ? geo.getAttribute('uv') : null;
105
+ this._buildCDF(pos, triCount, getTriVerts, weightBuf ?? undefined, uvBuf ?? undefined);
106
+ this._lastWeightAttribute = weightAttr;
107
+ this._lastDensityTexture = this.densityTexture;
108
+ }
109
+ triIndex = this._sampleCDF(rng.next());
110
+ }
111
+ else {
112
+ triIndex = Math.floor(rng.next() * triCount);
113
+ }
114
+ const [a, b, c] = getTriVerts(triIndex);
115
+ const vA = new Vector3().fromBufferAttribute(pos, a);
116
+ const vB = new Vector3().fromBufferAttribute(pos, b);
117
+ const vC = new Vector3().fromBufferAttribute(pos, c);
118
+ // Uniform random point on triangle (barycentric)
119
+ let r1 = rng.next(), r2 = rng.next();
120
+ if (r1 + r2 > 1) {
121
+ r1 = 1 - r1;
122
+ r2 = 1 - r2;
123
+ }
124
+ particle.position
125
+ .copy(vA)
126
+ .addScaledVector(vB.sub(vA), r1)
127
+ .addScaledVector(vC.sub(new Vector3().fromBufferAttribute(pos, a)), r2);
128
+ const normal = norm
129
+ ? new Vector3().fromBufferAttribute(norm, a).normalize()
130
+ : new Vector3().crossVectors(new Vector3().fromBufferAttribute(pos, b).sub(new Vector3().fromBufferAttribute(pos, a)), new Vector3().fromBufferAttribute(pos, c).sub(new Vector3().fromBufferAttribute(pos, a))).normalize();
131
+ const tangent = this._makeTangent(normal);
132
+ this.applyVelocity(particle, normal, tangent, rng);
133
+ this._applySizeAttribute(particle, geo, a, b, c, r1, r2, 1 - r1 - r2);
134
+ }
135
+ _spawnFromVolume(particle, geo, rng) {
136
+ geo.computeBoundingBox();
137
+ const bb = geo.boundingBox;
138
+ particle.position.set(rng.range(bb.min.x, bb.max.x), rng.range(bb.min.y, bb.max.y), rng.range(bb.min.z, bb.max.z));
139
+ const normal = new Vector3(0, 1, 0);
140
+ const tangent = new Vector3(1, 0, 0);
141
+ this.applyVelocity(particle, normal, tangent, rng);
142
+ }
143
+ _buildCDF(pos, triCount, getTriVerts, weightBuf, uvBuf) {
144
+ this._faceCDF = new Float32Array(triCount);
145
+ let total = 0;
146
+ const vA = new Vector3(), vB = new Vector3(), vC = new Vector3();
147
+ const strength = this.parameters.weightStrength;
148
+ for (let i = 0; i < triCount; i++) {
149
+ const [a, b, c] = getTriVerts(i);
150
+ vA.fromBufferAttribute(pos, a);
151
+ vB.fromBufferAttribute(pos, b);
152
+ vC.fromBufferAttribute(pos, c);
153
+ const area = vB.sub(vA).cross(vC.sub(vA)).length() * 0.5;
154
+ let weight = 1;
155
+ if (weightBuf && strength > 0) {
156
+ const avgW = (weightBuf.getX(a) + weightBuf.getX(b) + weightBuf.getX(c)) / 3;
157
+ weight = 1 - strength + strength * avgW;
158
+ }
159
+ if (this.densityTexture && uvBuf) {
160
+ const u = (uvBuf.getX(a) + uvBuf.getX(b) + uvBuf.getX(c)) / 3;
161
+ const v = (uvBuf.getY(a) + uvBuf.getY(b) + uvBuf.getY(c)) / 3;
162
+ weight *= this._sampleTexture(u, v);
163
+ }
164
+ total += area * weight;
165
+ this._faceCDF[i] = total;
166
+ }
167
+ if (total > 0) {
168
+ for (let i = 0; i < triCount; i++)
169
+ this._faceCDF[i] /= total;
170
+ }
171
+ }
172
+ /**
173
+ * Sample the red channel of densityTexture at normalised UV (u,v).
174
+ * Returns a value in [0, 1]. Falls back to 1.0 if the texture has no
175
+ * readable image data.
176
+ */
177
+ _sampleTexture(u, v) {
178
+ const tex = this.densityTexture;
179
+ if (!tex)
180
+ return 1.0;
181
+ const img = tex.image;
182
+ if (!img)
183
+ return 1.0;
184
+ // DataTexture / ImageData: has data (TypedArray), width, height
185
+ const data = img['data'];
186
+ const width = img['width'];
187
+ const height = img['height'];
188
+ if (data && width && height) {
189
+ const x = Math.max(0, Math.min(width - 1, Math.floor(u * width)));
190
+ const y = Math.max(0, Math.min(height - 1, Math.floor((1 - v) * height)));
191
+ const idx = (y * width + x) * 4; // RGBA
192
+ if (data instanceof Float32Array) {
193
+ return Math.max(0, Math.min(1, data[idx]));
194
+ }
195
+ return data[idx] / 255;
196
+ }
197
+ // HTMLCanvasElement
198
+ if (typeof img['getContext'] === 'function') {
199
+ const canvas = img;
200
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
201
+ if (ctx) {
202
+ const x = Math.max(0, Math.min(canvas.width - 1, Math.floor(u * canvas.width)));
203
+ const y = Math.max(0, Math.min(canvas.height - 1, Math.floor((1 - v) * canvas.height)));
204
+ const px = ctx.getImageData(x, y, 1, 1).data;
205
+ return px[0] / 255;
206
+ }
207
+ }
208
+ return 1.0;
209
+ }
210
+ _sampleCDF(r) {
211
+ let lo = 0, hi = this._faceCDF.length - 1;
212
+ while (lo < hi) {
213
+ const mid = (lo + hi) >>> 1;
214
+ if (this._faceCDF[mid] < r)
215
+ lo = mid + 1;
216
+ else
217
+ hi = mid;
218
+ }
219
+ return lo;
220
+ }
221
+ /**
222
+ * Apply the sizeAttribute multiplier to particle.size at spawn time.
223
+ * a/b/c are vertex indices; w0/w1/w2 are barycentric weights (must sum to 1).
224
+ */
225
+ _applySizeAttribute(particle, geo, a, b, c, w0, w1, w2) {
226
+ if (!this.sizeAttribute)
227
+ return;
228
+ const attr = geo.getAttribute(this.sizeAttribute);
229
+ if (!attr)
230
+ return;
231
+ const strength = this.parameters.sizeStrength;
232
+ const sampled = attr.getX(a) * w0 + attr.getX(b) * w1 + attr.getX(c) * w2;
233
+ particle.size *= 1 - strength + strength * sampled;
234
+ }
235
+ _makeTangent(normal) {
236
+ const up = Math.abs(normal.y) < 0.99 ? new Vector3(0, 1, 0) : new Vector3(1, 0, 0);
237
+ return new Vector3().crossVectors(normal, up).normalize();
238
+ }
239
+ }
240
+ //# sourceMappingURL=MeshEmitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MeshEmitter.js","sourceRoot":"","sources":["../../src/emitters/MeshEmitter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { BufferGeometry } from 'three';
2
+ import { BaseEmitter, BaseEmitterOptions } from '../core/BaseEmitter.js';
3
+ import type { Particle } from '../core/Particle.js';
4
+ import type { SeededRandom } from '../core/SeededRandom.js';
5
+ export interface PointEmitterOptions extends BaseEmitterOptions {
6
+ /** World-space origin of the point emitter */
7
+ position?: {
8
+ x: number;
9
+ y: number;
10
+ z: number;
11
+ };
12
+ }
13
+ /**
14
+ * PointEmitter — emits particles from a single world-space point.
15
+ * Equivalent to Blender's particle system on an Empty object.
16
+ */
17
+ export declare class PointEmitter extends BaseEmitter {
18
+ private _origin;
19
+ constructor(opts?: PointEmitterOptions);
20
+ spawn(particle: Particle, _geometry: BufferGeometry | null, rng: SeededRandom): void;
21
+ }
22
+ //# sourceMappingURL=PointEmitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PointEmitter.d.ts","sourceRoot":"","sources":["../../src/emitters/PointEmitter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,cAAc,EAAE,MAAM,OAAO,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AACxE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAU,qBAAqB,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAE3D,MAAM,WAAW,mBAAoB,SAAQ,kBAAkB;IAC7D,8CAA8C;IAC9C,QAAQ,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC/C;AAED;;;GAGG;AACH,qBAAa,YAAa,SAAQ,WAAW;IAC3C,OAAO,CAAC,OAAO,CAAgB;gBAEnB,IAAI,GAAE,mBAAwB;IAU1C,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,GAAG,IAAI,EAAE,GAAG,EAAE,YAAY,GAAG,IAAI;CAYrF"}
@@ -0,0 +1,26 @@
1
+ import { Vector3 } from 'three';
2
+ import { BaseEmitter } from '../core/BaseEmitter.js';
3
+ /**
4
+ * PointEmitter — emits particles from a single world-space point.
5
+ * Equivalent to Blender's particle system on an Empty object.
6
+ */
7
+ export class PointEmitter extends BaseEmitter {
8
+ constructor(opts = {}) {
9
+ super(opts);
10
+ this._origin = new Vector3();
11
+ if (opts.position) {
12
+ this._origin.set(opts.position.x, opts.position.y, opts.position.z);
13
+ }
14
+ this.parameters.positionX = this._origin.x;
15
+ this.parameters.positionY = this._origin.y;
16
+ this.parameters.positionZ = this._origin.z;
17
+ }
18
+ spawn(particle, _geometry, rng) {
19
+ particle.position.set(this.parameters.positionX, this.parameters.positionY, this.parameters.positionZ);
20
+ // Default normal = world up
21
+ const normal = new Vector3(0, 1, 0);
22
+ const tangent = new Vector3(1, 0, 0);
23
+ this.applyVelocity(particle, normal, tangent, rng);
24
+ }
25
+ }
26
+ //# sourceMappingURL=PointEmitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PointEmitter.js","sourceRoot":"","sources":["../../src/emitters/PointEmitter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAkB,MAAM,OAAO,CAAA;AAC/C,OAAO,EAAE,WAAW,EAAsB,MAAM,wBAAwB,CAAA;AASxE;;;GAGG;AACH,MAAM,OAAO,YAAa,SAAQ,WAAW;IAG3C,YAAY,OAA4B,EAAE;QACxC,KAAK,CAAC,IAAI,CAAC,CAAA;QAHL,YAAO,GAAG,IAAI,OAAO,EAAE,CAAA;QAI7B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;QACrE,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAA;QAC1C,IAAI,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAA;QAC1C,IAAI,CAAC,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAA;IAC5C,CAAC;IAED,KAAK,CAAC,QAAkB,EAAE,SAAgC,EAAE,GAAiB;QAC3E,QAAQ,CAAC,QAAQ,CAAC,GAAG,CACnB,IAAI,CAAC,UAAU,CAAC,SAAS,EACzB,IAAI,CAAC,UAAU,CAAC,SAAS,EACzB,IAAI,CAAC,UAAU,CAAC,SAAS,CAC1B,CAAA;QAED,4BAA4B;QAC5B,MAAM,MAAM,GAAI,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;QACpC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;QACpC,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAA;IACpD,CAAC;CACF"}
@@ -0,0 +1,58 @@
1
+ import { BaseForce } from '../core/BaseForce.js';
2
+ import type { Particle } from '../core/Particle.js';
3
+ /**
4
+ * A minimal interface for any particle pool that BoidField can read positions from.
5
+ * Pass `otherSystem.pool` or any array of objects with a `position` and `alive` flag.
6
+ */
7
+ export interface BoidFieldSource {
8
+ readonly position: {
9
+ x: number;
10
+ y: number;
11
+ z: number;
12
+ };
13
+ readonly alive: boolean;
14
+ }
15
+ export interface BoidFieldOptions {
16
+ /** Radius within which agents are repelled away from source particles. Default 1. */
17
+ repelRadius?: number;
18
+ /** Radius within which agents are attracted toward source particles. Default 0 (disabled). */
19
+ attractRadius?: number;
20
+ /** Strength of the repulsion force. Default 3. */
21
+ repelStrength?: number;
22
+ /** Strength of the attraction force. Default 1. */
23
+ attractStrength?: number;
24
+ /** Maximum velocity magnitude a particle may reach. Default 8. */
25
+ maxSpeed?: number;
26
+ }
27
+ /**
28
+ * BoidField — a scene-level force that steers one particle system
29
+ * based on the positions of another (or an arbitrary point cloud).
30
+ *
31
+ * Blender equivalent: a Boid rules effector applied at scene level,
32
+ * letting different systems interact (predator/prey, crowd avoidance,
33
+ * swarming around attractors).
34
+ *
35
+ * @example
36
+ * // System B avoids system A's particles
37
+ * const field = new BoidField(systemA.pool, { repelRadius: 1.5, repelStrength: 5 })
38
+ * systemB.addForce(field)
39
+ *
40
+ * // System B swarms toward system A
41
+ * const field = new BoidField(systemA.pool, { attractRadius: 3, attractStrength: 2 })
42
+ * systemB.addForce(field)
43
+ */
44
+ export declare class BoidField extends BaseForce {
45
+ parameters: {
46
+ repelRadius: number;
47
+ attractRadius: number;
48
+ repelStrength: number;
49
+ attractStrength: number;
50
+ maxSpeed: number;
51
+ };
52
+ private _source;
53
+ constructor(source: BoidFieldSource[], opts?: BoidFieldOptions);
54
+ /** Swap the source pool at runtime — e.g. switch which system acts as the attractor. */
55
+ setSource(source: BoidFieldSource[]): void;
56
+ apply(particle: Particle, dt: number): void;
57
+ }
58
+ //# sourceMappingURL=BoidField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BoidField.d.ts","sourceRoot":"","sources":["../../src/forces/BoidField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACtD,QAAQ,CAAC,KAAK,EAAK,OAAO,CAAA;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,qFAAqF;IACrF,WAAW,CAAC,EAAI,MAAM,CAAA;IACtB,8FAA8F;IAC9F,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mDAAmD;IACnD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,SAAU,SAAQ,SAAS;IACtC,UAAU,EAAE;QACV,WAAW,EAAK,MAAM,CAAA;QACtB,aAAa,EAAG,MAAM,CAAA;QACtB,aAAa,EAAG,MAAM,CAAA;QACtB,eAAe,EAAE,MAAM,CAAA;QACvB,QAAQ,EAAQ,MAAM,CAAA;KACvB,CAAA;IAED,OAAO,CAAC,OAAO,CAAmB;gBAEtB,MAAM,EAAE,eAAe,EAAE,EAAE,IAAI,GAAE,gBAAqB;IAYlE,wFAAwF;IACxF,SAAS,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,IAAI;IAI1C,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;CAkD5C"}
@@ -0,0 +1,82 @@
1
+ import { BaseForce } from '../core/BaseForce.js';
2
+ /**
3
+ * BoidField — a scene-level force that steers one particle system
4
+ * based on the positions of another (or an arbitrary point cloud).
5
+ *
6
+ * Blender equivalent: a Boid rules effector applied at scene level,
7
+ * letting different systems interact (predator/prey, crowd avoidance,
8
+ * swarming around attractors).
9
+ *
10
+ * @example
11
+ * // System B avoids system A's particles
12
+ * const field = new BoidField(systemA.pool, { repelRadius: 1.5, repelStrength: 5 })
13
+ * systemB.addForce(field)
14
+ *
15
+ * // System B swarms toward system A
16
+ * const field = new BoidField(systemA.pool, { attractRadius: 3, attractStrength: 2 })
17
+ * systemB.addForce(field)
18
+ */
19
+ export class BoidField extends BaseForce {
20
+ constructor(source, opts = {}) {
21
+ super();
22
+ this._source = source;
23
+ this.parameters = {
24
+ repelRadius: opts.repelRadius ?? 1.0,
25
+ attractRadius: opts.attractRadius ?? 0.0,
26
+ repelStrength: opts.repelStrength ?? 3.0,
27
+ attractStrength: opts.attractStrength ?? 1.0,
28
+ maxSpeed: opts.maxSpeed ?? 8.0,
29
+ };
30
+ }
31
+ /** Swap the source pool at runtime — e.g. switch which system acts as the attractor. */
32
+ setSource(source) {
33
+ this._source = source;
34
+ }
35
+ apply(particle, dt) {
36
+ const { repelRadius, attractRadius, repelStrength, attractStrength, maxSpeed } = this.parameters;
37
+ const repR2 = repelRadius * repelRadius;
38
+ const attR2 = attractRadius * attractRadius;
39
+ // Minimum distance to prevent force explosion at near-zero separation
40
+ const minD = 0.05;
41
+ let fx = 0, fy = 0, fz = 0;
42
+ for (const agent of this._source) {
43
+ if (!agent.alive)
44
+ continue;
45
+ const dx = particle.position.x - agent.position.x;
46
+ const dy = particle.position.y - agent.position.y;
47
+ const dz = particle.position.z - agent.position.z;
48
+ const d2 = dx * dx + dy * dy + dz * dz;
49
+ if (d2 < 1e-8)
50
+ continue;
51
+ const d = Math.max(Math.sqrt(d2), minD);
52
+ // Repulsion — push away
53
+ if (repR2 > 0 && d2 < repR2) {
54
+ const fade = 1 - d / repelRadius;
55
+ const mag = repelStrength * fade / d;
56
+ fx += dx * mag;
57
+ fy += dy * mag;
58
+ fz += dz * mag;
59
+ }
60
+ // Attraction — pull toward
61
+ if (attR2 > 0 && d2 < attR2) {
62
+ const fade = 1 - d / attractRadius;
63
+ const mag = -attractStrength * fade / d;
64
+ fx += dx * mag;
65
+ fy += dy * mag;
66
+ fz += dz * mag;
67
+ }
68
+ }
69
+ particle.velocity.x += fx * dt;
70
+ particle.velocity.y += fy * dt;
71
+ particle.velocity.z += fz * dt;
72
+ // Clamp speed to prevent runaway / NaN
73
+ const spd2 = particle.velocity.x ** 2 + particle.velocity.y ** 2 + particle.velocity.z ** 2;
74
+ if (spd2 > maxSpeed * maxSpeed) {
75
+ const inv = maxSpeed / Math.sqrt(spd2);
76
+ particle.velocity.x *= inv;
77
+ particle.velocity.y *= inv;
78
+ particle.velocity.z *= inv;
79
+ }
80
+ }
81
+ }
82
+ //# sourceMappingURL=BoidField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BoidField.js","sourceRoot":"","sources":["../../src/forces/BoidField.ts"],"names":[],"mappings":"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"}