@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,54 @@
1
+ import { Vector3 } from 'three';
2
+ import { BaseForce } from '../core/BaseForce.js';
3
+ import type { Particle } from '../core/Particle.js';
4
+ export interface BoidObstacle {
5
+ position: Vector3;
6
+ radius: number;
7
+ }
8
+ /**
9
+ * Boids flocking AI — implements the classic separation / alignment / cohesion
10
+ * rules plus Blender Boids panel extras (stubbed as numeric params for GSAP).
11
+ *
12
+ * Inject the live pool reference so the force can read neighbour state each tick.
13
+ * Use ParticleSystem.pool as the source:
14
+ *
15
+ * const boid = new BoidForce(sys.pool)
16
+ * sys.addForce(boid)
17
+ *
18
+ * Blender parallel: Boids physics type.
19
+ */
20
+ export declare class BoidForce extends BaseForce {
21
+ parameters: Record<string, number>;
22
+ private readonly _pool;
23
+ /** Live obstacle list — can be mutated at runtime. */
24
+ obstacles: BoidObstacle[];
25
+ constructor(pool: Particle[], opts?: {
26
+ separationRadius?: number;
27
+ separationStrength?: number;
28
+ alignmentRadius?: number;
29
+ alignmentStrength?: number;
30
+ cohesionRadius?: number;
31
+ cohesionStrength?: number;
32
+ maxSpeed?: number;
33
+ maxForce?: number;
34
+ separationWeight?: number;
35
+ alignmentWeight?: number;
36
+ cohesionWeight?: number;
37
+ avoidWeight?: number;
38
+ avoidRadius?: number;
39
+ flightHeight?: number;
40
+ bankingAngle?: number;
41
+ pitchAngle?: number;
42
+ obstacles?: BoidObstacle[];
43
+ leaderIndex?: number;
44
+ leaderWeight?: number;
45
+ leaderRadius?: number;
46
+ groundMode?: number;
47
+ groundLevel?: number;
48
+ groundStrength?: number;
49
+ collisionRadius?: number;
50
+ collisionStiffness?: number;
51
+ });
52
+ apply(particle: Particle, dt: number): void;
53
+ }
54
+ //# sourceMappingURL=BoidForce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BoidForce.d.ts","sourceRoot":"","sources":["../../src/forces/BoidForce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAY,OAAO,CAAA;AACrC,OAAO,EAAE,SAAS,EAAE,MAAU,sBAAsB,CAAA;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAI,MAAM,CAAA;CACjB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,SAAU,SAAQ,SAAS;IACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAElC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAElC,sDAAsD;IACtD,SAAS,EAAE,YAAY,EAAE,CAAK;gBAElB,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,GAAE;QAClC,gBAAgB,CAAC,EAAI,MAAM,CAAA;QAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAA;QAC3B,eAAe,CAAC,EAAK,MAAM,CAAA;QAC3B,iBAAiB,CAAC,EAAG,MAAM,CAAA;QAC3B,cAAc,CAAC,EAAM,MAAM,CAAA;QAC3B,gBAAgB,CAAC,EAAI,MAAM,CAAA;QAC3B,QAAQ,CAAC,EAAY,MAAM,CAAA;QAC3B,QAAQ,CAAC,EAAY,MAAM,CAAA;QAC3B,gBAAgB,CAAC,EAAI,MAAM,CAAA;QAC3B,eAAe,CAAC,EAAK,MAAM,CAAA;QAC3B,cAAc,CAAC,EAAM,MAAM,CAAA;QAC3B,WAAW,CAAC,EAAS,MAAM,CAAA;QAC3B,WAAW,CAAC,EAAS,MAAM,CAAA;QAC3B,YAAY,CAAC,EAAQ,MAAM,CAAA;QAC3B,YAAY,CAAC,EAAQ,MAAM,CAAA;QAC3B,UAAU,CAAC,EAAU,MAAM,CAAA;QAC3B,SAAS,CAAC,EAAW,YAAY,EAAE,CAAA;QAEnC,WAAW,CAAC,EAAS,MAAM,CAAA;QAC3B,YAAY,CAAC,EAAQ,MAAM,CAAA;QAC3B,YAAY,CAAC,EAAQ,MAAM,CAAA;QAE3B,UAAU,CAAC,EAAU,MAAM,CAAA;QAC3B,WAAW,CAAC,EAAS,MAAM,CAAA;QAC3B,cAAc,CAAC,EAAM,MAAM,CAAA;QAE3B,eAAe,CAAC,EAAK,MAAM,CAAA;QAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAA;KACvB;IAmCN,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;CAuM5C"}
@@ -0,0 +1,233 @@
1
+ import { BaseForce } from '../core/BaseForce.js';
2
+ /**
3
+ * Boids flocking AI — implements the classic separation / alignment / cohesion
4
+ * rules plus Blender Boids panel extras (stubbed as numeric params for GSAP).
5
+ *
6
+ * Inject the live pool reference so the force can read neighbour state each tick.
7
+ * Use ParticleSystem.pool as the source:
8
+ *
9
+ * const boid = new BoidForce(sys.pool)
10
+ * sys.addForce(boid)
11
+ *
12
+ * Blender parallel: Boids physics type.
13
+ */
14
+ export class BoidForce extends BaseForce {
15
+ constructor(pool, opts = {}) {
16
+ super();
17
+ /** Live obstacle list — can be mutated at runtime. */
18
+ this.obstacles = [];
19
+ this._pool = pool;
20
+ if (opts.obstacles)
21
+ this.obstacles = opts.obstacles;
22
+ this.parameters = {
23
+ separationRadius: opts.separationRadius ?? 0.5,
24
+ separationStrength: opts.separationStrength ?? 2.0,
25
+ alignmentRadius: opts.alignmentRadius ?? 1.5,
26
+ alignmentStrength: opts.alignmentStrength ?? 1.0,
27
+ cohesionRadius: opts.cohesionRadius ?? 2.0,
28
+ cohesionStrength: opts.cohesionStrength ?? 0.8,
29
+ maxSpeed: opts.maxSpeed ?? 5.0,
30
+ maxForce: opts.maxForce ?? 3.0,
31
+ separationWeight: opts.separationWeight ?? 0,
32
+ alignmentWeight: opts.alignmentWeight ?? 0,
33
+ cohesionWeight: opts.cohesionWeight ?? 0,
34
+ avoidWeight: opts.avoidWeight ?? 1.0,
35
+ avoidRadius: opts.avoidRadius ?? 2.0,
36
+ flightHeight: opts.flightHeight ?? 0,
37
+ bankingAngle: opts.bankingAngle ?? 0,
38
+ pitchAngle: opts.pitchAngle ?? 0,
39
+ // Leader following
40
+ leaderIndex: opts.leaderIndex ?? -1,
41
+ leaderWeight: opts.leaderWeight ?? 1.0,
42
+ leaderRadius: opts.leaderRadius ?? 5.0,
43
+ // Ground walking
44
+ groundMode: opts.groundMode ?? 0,
45
+ groundLevel: opts.groundLevel ?? 0,
46
+ groundStrength: opts.groundStrength ?? 3.0,
47
+ // Collision stiffness
48
+ collisionRadius: opts.collisionRadius ?? 0,
49
+ collisionStiffness: opts.collisionStiffness ?? 5.0,
50
+ };
51
+ }
52
+ apply(particle, dt) {
53
+ const p = this.parameters;
54
+ if (!particle.alive)
55
+ return;
56
+ // Accumulators for each rule — three passes in one loop
57
+ let sepX = 0, sepY = 0, sepZ = 0, sepN = 0;
58
+ let aliX = 0, aliY = 0, aliZ = 0, aliN = 0;
59
+ let cohX = 0, cohY = 0, cohZ = 0, cohN = 0;
60
+ const px = particle.position.x;
61
+ const py = particle.position.y;
62
+ const pz = particle.position.z;
63
+ const sepR2 = p.separationRadius * p.separationRadius;
64
+ const aliR2 = p.alignmentRadius * p.alignmentRadius;
65
+ const cohR2 = p.cohesionRadius * p.cohesionRadius;
66
+ for (let i = 0; i < this._pool.length; i++) {
67
+ const n = this._pool[i];
68
+ if (!n.alive || n === particle)
69
+ continue;
70
+ const dx = n.position.x - px;
71
+ const dy = n.position.y - py;
72
+ const dz = n.position.z - pz;
73
+ const r2 = dx * dx + dy * dy + dz * dz;
74
+ // Separation — repel when inside separationRadius
75
+ if (r2 < sepR2 && r2 > 1e-8) {
76
+ const r = Math.sqrt(r2);
77
+ const inv = 1 / r;
78
+ // Push away (negate direction toward neighbour)
79
+ sepX -= dx * inv;
80
+ sepY -= dy * inv;
81
+ sepZ -= dz * inv;
82
+ sepN++;
83
+ }
84
+ // Alignment — average neighbour velocity
85
+ if (r2 < aliR2) {
86
+ aliX += n.velocity.x;
87
+ aliY += n.velocity.y;
88
+ aliZ += n.velocity.z;
89
+ aliN++;
90
+ }
91
+ // Cohesion — average neighbour position
92
+ if (r2 < cohR2) {
93
+ cohX += n.position.x;
94
+ cohY += n.position.y;
95
+ cohZ += n.position.z;
96
+ cohN++;
97
+ }
98
+ }
99
+ // Accumulate force contributions
100
+ let fx = 0, fy = 0, fz = 0;
101
+ if (sepN > 0) {
102
+ const inv = 1 / sepN;
103
+ fx += sepX * inv * p.separationStrength;
104
+ fy += sepY * inv * p.separationStrength;
105
+ fz += sepZ * inv * p.separationStrength;
106
+ }
107
+ if (aliN > 0) {
108
+ const inv = 1 / aliN;
109
+ fx += (aliX * inv - particle.velocity.x) * p.alignmentStrength;
110
+ fy += (aliY * inv - particle.velocity.y) * p.alignmentStrength;
111
+ fz += (aliZ * inv - particle.velocity.z) * p.alignmentStrength;
112
+ }
113
+ if (cohN > 0) {
114
+ const inv = 1 / cohN;
115
+ fx += (cohX * inv - px) * p.cohesionStrength;
116
+ fy += (cohY * inv - py) * p.cohesionStrength;
117
+ fz += (cohZ * inv - pz) * p.cohesionStrength;
118
+ }
119
+ // ── Leader following ────────────────────────────────────────────────────
120
+ const leaderIdx = Math.round(p.leaderIndex);
121
+ if (leaderIdx >= 0 && leaderIdx < this._pool.length && p.leaderWeight > 0) {
122
+ const leader = this._pool[leaderIdx];
123
+ if (leader.alive && leader !== particle) {
124
+ const ldx = leader.position.x - px;
125
+ const ldy = leader.position.y - py;
126
+ const ldz = leader.position.z - pz;
127
+ const lDist2 = ldx * ldx + ldy * ldy + ldz * ldz;
128
+ const lR2 = p.leaderRadius * p.leaderRadius;
129
+ if (lDist2 < lR2) {
130
+ // Steer toward leader — cohesion-like pull weighted by proximity
131
+ const proximity = 1 - Math.sqrt(lDist2) / p.leaderRadius;
132
+ fx += ldx * p.leaderWeight * proximity;
133
+ fy += ldy * p.leaderWeight * proximity;
134
+ fz += ldz * p.leaderWeight * proximity;
135
+ }
136
+ }
137
+ }
138
+ // ── Boid-boid collision stiffness ────────────────────────────────────────
139
+ if (p.collisionRadius > 0 && p.collisionStiffness > 0) {
140
+ const cr2 = p.collisionRadius * p.collisionRadius;
141
+ for (let i = 0; i < this._pool.length; i++) {
142
+ const n = this._pool[i];
143
+ if (!n.alive || n === particle)
144
+ continue;
145
+ const cdx = px - n.position.x;
146
+ const cdy = py - n.position.y;
147
+ const cdz = pz - n.position.z;
148
+ const cr2d = cdx * cdx + cdy * cdy + cdz * cdz;
149
+ if (cr2d < cr2 && cr2d > 1e-10) {
150
+ const dist = Math.sqrt(cr2d);
151
+ const overlap = p.collisionRadius - dist;
152
+ const stiffness = p.collisionStiffness * overlap / dist;
153
+ fx += cdx * stiffness;
154
+ fy += cdy * stiffness;
155
+ fz += cdz * stiffness;
156
+ }
157
+ }
158
+ }
159
+ // ── Obstacle avoidance ──────────────────────────────────────────────────
160
+ if (this.obstacles.length > 0 && p.avoidWeight > 0) {
161
+ const avoidR = p.avoidRadius;
162
+ const avoidW = p.avoidWeight;
163
+ for (let o = 0; o < this.obstacles.length; o++) {
164
+ const obs = this.obstacles[o];
165
+ const totalR = avoidR + obs.radius;
166
+ const odx = px - obs.position.x;
167
+ const ody = py - obs.position.y;
168
+ const odz = pz - obs.position.z;
169
+ const odist2 = odx * odx + ody * ody + odz * odz;
170
+ if (odist2 < totalR * totalR && odist2 > 1e-10) {
171
+ const odist = Math.sqrt(odist2);
172
+ // Scale force by how close the particle is to the obstacle surface
173
+ const proximity = 1 - odist / totalR;
174
+ const steer = avoidW * proximity / odist;
175
+ fx += odx * steer;
176
+ fy += ody * steer;
177
+ fz += odz * steer;
178
+ }
179
+ }
180
+ }
181
+ // ── Flight height restoring force ───────────────────────────────────────
182
+ if (p.flightHeight !== 0) {
183
+ fy += (p.flightHeight - py) * 0.5;
184
+ }
185
+ // Clamp force magnitude
186
+ const fMag2 = fx * fx + fy * fy + fz * fz;
187
+ const maxF2 = p.maxForce * p.maxForce;
188
+ if (fMag2 > maxF2 && fMag2 > 1e-12) {
189
+ const scale = p.maxForce / Math.sqrt(fMag2);
190
+ fx *= scale;
191
+ fy *= scale;
192
+ fz *= scale;
193
+ }
194
+ // Apply force: dv = F * dt
195
+ particle.velocity.x += fx * dt;
196
+ particle.velocity.y += fy * dt;
197
+ particle.velocity.z += fz * dt;
198
+ // Clamp speed
199
+ const vMag2 = particle.velocity.x ** 2 + particle.velocity.y ** 2 + particle.velocity.z ** 2;
200
+ const maxV2 = p.maxSpeed * p.maxSpeed;
201
+ if (vMag2 > maxV2 && vMag2 > 1e-12) {
202
+ const scale = p.maxSpeed / Math.sqrt(vMag2);
203
+ particle.velocity.x *= scale;
204
+ particle.velocity.y *= scale;
205
+ particle.velocity.z *= scale;
206
+ }
207
+ // ── Banking / pitch (cosmetic rotation only) ────────────────────────────
208
+ if (p.bankingAngle !== 0) {
209
+ // Lateral acceleration ≈ change in horizontal velocity direction
210
+ // Proxy: cross product of current horizontal velocity with world-up gives lateral
211
+ const vx = particle.velocity.x, vz = particle.velocity.z;
212
+ const speed2d = Math.sqrt(vx * vx + vz * vz);
213
+ if (speed2d > 0.01) {
214
+ // Signed lateral tilt: use fx (left/right force) scaled by bankingAngle
215
+ particle.rotation.z = -Math.atan2(fx, p.maxForce) * p.bankingAngle;
216
+ }
217
+ }
218
+ if (p.pitchAngle !== 0) {
219
+ const vy = particle.velocity.y;
220
+ particle.rotation.x = Math.atan2(vy, Math.max(0.01, p.maxSpeed)) * p.pitchAngle;
221
+ }
222
+ // ── Ground walking mode ──────────────────────────────────────────────────
223
+ if (p.groundMode >= 0.5) {
224
+ // Restoring force pushes particle toward groundLevel
225
+ particle.velocity.y += (p.groundLevel - particle.position.y) * p.groundStrength * dt;
226
+ // Suppress strong vertical velocity so boids stay near the ground
227
+ particle.velocity.y *= Math.max(0, 1 - 0.8 * dt);
228
+ // Align rotation.x to zero (flat ground orientation)
229
+ particle.rotation.x *= Math.max(0, 1 - 5 * dt);
230
+ }
231
+ }
232
+ }
233
+ //# sourceMappingURL=BoidForce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BoidForce.js","sourceRoot":"","sources":["../../src/forces/BoidForce.ts"],"names":[],"mappings":"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"}
@@ -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
+ /**
5
+ * Radial attract / repel force — positive strength attracts, negative repels.
6
+ * (Blender: Force Field → Charge / Force)
7
+ */
8
+ export declare class ChargeForce extends BaseForce {
9
+ parameters: Record<string, number>;
10
+ /** World-space charge origin. Physics and range check both use this. Not GSAP-animatable. */
11
+ position: Vector3;
12
+ constructor(opts?: {
13
+ strength?: number;
14
+ falloff?: number;
15
+ maxDistance?: number;
16
+ px?: number;
17
+ py?: number;
18
+ pz?: number;
19
+ });
20
+ apply(particle: Particle, dt: number): void;
21
+ }
22
+ //# sourceMappingURL=ChargeForce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChargeForce.d.ts","sourceRoot":"","sources":["../../src/forces/ChargeForce.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;;;GAGG;AACH,qBAAa,WAAY,SAAQ,SAAS;IACxC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAElC,6FAA6F;IAC7F,QAAQ,EAAE,OAAO,CAAA;gBAEL,IAAI,GAAE;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;QACzD,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;CAe5C"}
@@ -0,0 +1,35 @@
1
+ import { Vector3 } from 'three';
2
+ import { BaseForce } from '../core/BaseForce.js';
3
+ /**
4
+ * Radial attract / repel force — positive strength attracts, negative repels.
5
+ * (Blender: Force Field → Charge / Force)
6
+ */
7
+ export class ChargeForce extends BaseForce {
8
+ constructor(opts = {}) {
9
+ super();
10
+ this.position = new Vector3(opts.px ?? 0, opts.py ?? 0, opts.pz ?? 0);
11
+ this.parameters = {
12
+ strength: opts.strength ?? 1,
13
+ falloff: opts.falloff ?? 2,
14
+ maxDistance: opts.maxDistance ?? 0,
15
+ };
16
+ }
17
+ apply(particle, dt) {
18
+ const p = this.parameters;
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
+ const r2 = dx * dx + dy * dy + dz * dz;
23
+ if (r2 < 1e-8)
24
+ return;
25
+ if (p.maxDistance > 0 && r2 > p.maxDistance * p.maxDistance)
26
+ return;
27
+ const r = Math.sqrt(r2);
28
+ const mag = p.strength / Math.pow(r, p.falloff);
29
+ // toward origin = negative direction from particle
30
+ particle.velocity.x -= (dx / r) * mag * dt;
31
+ particle.velocity.y -= (dy / r) * mag * dt;
32
+ particle.velocity.z -= (dz / r) * mag * dt;
33
+ }
34
+ }
35
+ //# sourceMappingURL=ChargeForce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChargeForce.js","sourceRoot":"","sources":["../../src/forces/ChargeForce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAS,OAAO,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAQ,sBAAsB,CAAA;AAGlD;;;GAGG;AACH,MAAM,OAAO,WAAY,SAAQ,SAAS;IAMxC,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,OAAO,EAAM,IAAI,CAAC,OAAO,IAAQ,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,GAAK,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACzB,MAAM,GAAG,GAAG,CAAC,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;QAC/C,mDAAmD;QACnD,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,43 @@
1
+ import type { CatmullRomCurve3 } from 'three';
2
+ import { BaseForce } from '../core/BaseForce.js';
3
+ import type { Particle } from '../core/Particle.js';
4
+ /**
5
+ * CurveGuideForce — attracts particles toward the nearest point on a
6
+ * CatmullRomCurve3. (Blender: Force Fields → Curve Guide)
7
+ *
8
+ * The curve is sampled at `samples` points each construction; the nearest
9
+ * sample is found each apply() call (O(samples) — keep samples ≤ 256 for
10
+ * large pools). The particle is attracted to that nearest point.
11
+ *
12
+ * parameters.clumpFactor: 0 = attract to nearest point, 1 = also align
13
+ * velocity toward the curve tangent at that point (Blender: Clump).
14
+ * parameters.freeEnd: 0 = full curve influence, 1 = weaker at the
15
+ * end of the curve (Blender: Free End).
16
+ */
17
+ export declare class CurveGuideForce extends BaseForce {
18
+ curve: CatmullRomCurve3;
19
+ parameters: Record<string, number>;
20
+ private readonly _pts;
21
+ private readonly _tans;
22
+ private readonly _tmp;
23
+ /**
24
+ * @param curve A THREE.CatmullRomCurve3 — must be set before the first
25
+ * update() call. Can be replaced at runtime; call resample().
26
+ * @param opts strength: attraction force magnitude (Blender: Strength).
27
+ * clumpFactor: tangent-alignment blend 0–1 (Blender: Clump).
28
+ * freeEnd: reduce influence near curve end 0–1 (Blender: Free End).
29
+ * samples: internal sampling resolution (default 64).
30
+ */
31
+ constructor(curve: CatmullRomCurve3, opts?: {
32
+ strength?: number;
33
+ clumpFactor?: number;
34
+ freeEnd?: number;
35
+ samples?: number;
36
+ maxDistance?: number;
37
+ });
38
+ /** Re-sample the curve — call after modifying curve control points. */
39
+ resample(): void;
40
+ apply(particle: Particle, dt: number): void;
41
+ private _buildSamples;
42
+ }
43
+ //# sourceMappingURL=CurveGuideForce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CurveGuideForce.d.ts","sourceRoot":"","sources":["../../src/forces/CurveGuideForce.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAY,sBAAsB,CAAA;AACtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAQ,qBAAqB,CAAA;AAErD;;;;;;;;;;;;GAYG;AACH,qBAAa,eAAgB,SAAQ,SAAS;IAgBnC,KAAK,EAAE,gBAAgB;IAfhC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAElC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAY;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAW;IACjC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA0B;IAE/C;;;;;;;OAOG;gBAEM,KAAK,EAAE,gBAAgB,EAC9B,IAAI,GAAE;QACJ,QAAQ,CAAC,EAAM,MAAM,CAAA;QACrB,WAAW,CAAC,EAAG,MAAM,CAAA;QACrB,OAAO,CAAC,EAAO,MAAM,CAAA;QACrB,OAAO,CAAC,EAAO,MAAM,CAAA;QACrB,WAAW,CAAC,EAAG,MAAM,CAAA;KACjB;IAeR,uEAAuE;IACvE,QAAQ,IAAI,IAAI;IAIhB,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAwE3C,OAAO,CAAC,aAAa;CAOtB"}
@@ -0,0 +1,118 @@
1
+ import { Vector3 } from 'three';
2
+ import { BaseForce } from '../core/BaseForce.js';
3
+ /**
4
+ * CurveGuideForce — attracts particles toward the nearest point on a
5
+ * CatmullRomCurve3. (Blender: Force Fields → Curve Guide)
6
+ *
7
+ * The curve is sampled at `samples` points each construction; the nearest
8
+ * sample is found each apply() call (O(samples) — keep samples ≤ 256 for
9
+ * large pools). The particle is attracted to that nearest point.
10
+ *
11
+ * parameters.clumpFactor: 0 = attract to nearest point, 1 = also align
12
+ * velocity toward the curve tangent at that point (Blender: Clump).
13
+ * parameters.freeEnd: 0 = full curve influence, 1 = weaker at the
14
+ * end of the curve (Blender: Free End).
15
+ */
16
+ export class CurveGuideForce extends BaseForce {
17
+ /**
18
+ * @param curve A THREE.CatmullRomCurve3 — must be set before the first
19
+ * update() call. Can be replaced at runtime; call resample().
20
+ * @param opts strength: attraction force magnitude (Blender: Strength).
21
+ * clumpFactor: tangent-alignment blend 0–1 (Blender: Clump).
22
+ * freeEnd: reduce influence near curve end 0–1 (Blender: Free End).
23
+ * samples: internal sampling resolution (default 64).
24
+ */
25
+ constructor(curve, opts = {}) {
26
+ super();
27
+ this.curve = curve;
28
+ this._tmp = new Vector3();
29
+ this.parameters = {
30
+ strength: opts.strength ?? 1,
31
+ clumpFactor: opts.clumpFactor ?? 0,
32
+ freeEnd: opts.freeEnd ?? 0,
33
+ maxDistance: opts.maxDistance ?? 0,
34
+ };
35
+ const n = Math.max(4, Math.min(256, opts.samples ?? 64));
36
+ this._pts = Array.from({ length: n }, () => new Vector3());
37
+ this._tans = Array.from({ length: n }, () => new Vector3());
38
+ this._buildSamples(n);
39
+ }
40
+ /** Re-sample the curve — call after modifying curve control points. */
41
+ resample() {
42
+ this._buildSamples(this._pts.length);
43
+ }
44
+ apply(particle, dt) {
45
+ const n = this._pts.length;
46
+ if (n === 0)
47
+ return;
48
+ // maxDistance: skip particles too far from the closest curve point
49
+ const maxD = this.parameters.maxDistance;
50
+ if (maxD > 0) {
51
+ let minD2 = Infinity;
52
+ const px = particle.position.x, py = particle.position.y, pz = particle.position.z;
53
+ for (let i = 0; i < n; i++) {
54
+ const q = this._pts[i];
55
+ const d2 = (px - q.x) ** 2 + (py - q.y) ** 2 + (pz - q.z) ** 2;
56
+ if (d2 < minD2)
57
+ minD2 = d2;
58
+ }
59
+ if (minD2 > maxD * maxD)
60
+ return;
61
+ }
62
+ // Find nearest sampled point
63
+ let nearestIdx = 0;
64
+ let nearestDist2 = Infinity;
65
+ const px = particle.position.x;
66
+ const py = particle.position.y;
67
+ const pz = particle.position.z;
68
+ for (let i = 0; i < n; i++) {
69
+ const q = this._pts[i];
70
+ const dx = px - q.x;
71
+ const dy = py - q.y;
72
+ const dz = pz - q.z;
73
+ const d2 = dx * dx + dy * dy + dz * dz;
74
+ if (d2 < nearestDist2) {
75
+ nearestDist2 = d2;
76
+ nearestIdx = i;
77
+ }
78
+ }
79
+ const target = this._pts[nearestIdx];
80
+ const tan = this._tans[nearestIdx];
81
+ const { strength, clumpFactor, freeEnd } = this.parameters;
82
+ // Free-end attenuation: t ∈ [0,1] along curve — weaken near the end
83
+ const t = nearestIdx / Math.max(n - 1, 1);
84
+ const freeAtt = freeEnd > 0 ? 1 - t * freeEnd : 1;
85
+ const effStr = strength * freeAtt * dt;
86
+ // Attraction toward nearest point
87
+ this._tmp.set(target.x - px, target.y - py, target.z - pz);
88
+ const dist = this._tmp.length();
89
+ if (dist > 1e-6 && clumpFactor < 1) {
90
+ const attStr = effStr * (1 - clumpFactor);
91
+ particle.velocity.x += (this._tmp.x / dist) * attStr;
92
+ particle.velocity.y += (this._tmp.y / dist) * attStr;
93
+ particle.velocity.z += (this._tmp.z / dist) * attStr;
94
+ }
95
+ // Tangent alignment (clump): steer velocity toward curve direction
96
+ if (clumpFactor > 0) {
97
+ const tanLen = tan.length();
98
+ if (tanLen > 1e-6) {
99
+ const tnx = tan.x / tanLen;
100
+ const tny = tan.y / tanLen;
101
+ const tnz = tan.z / tanLen;
102
+ // Project current velocity onto tangent then lerp
103
+ const dot = particle.velocity.x * tnx + particle.velocity.y * tny + particle.velocity.z * tnz;
104
+ particle.velocity.x += (dot * tnx - particle.velocity.x) * clumpFactor * effStr;
105
+ particle.velocity.y += (dot * tny - particle.velocity.y) * clumpFactor * effStr;
106
+ particle.velocity.z += (dot * tnz - particle.velocity.z) * clumpFactor * effStr;
107
+ }
108
+ }
109
+ }
110
+ _buildSamples(n) {
111
+ for (let i = 0; i < n; i++) {
112
+ const t = i / Math.max(n - 1, 1);
113
+ this.curve.getPoint(t, this._pts[i]);
114
+ this.curve.getTangent(t, this._tans[i]);
115
+ }
116
+ }
117
+ }
118
+ //# sourceMappingURL=CurveGuideForce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CurveGuideForce.js","sourceRoot":"","sources":["../../src/forces/CurveGuideForce.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { Vector3 } from 'three';
2
+ import { BaseForce } from '../core/BaseForce.js';
3
+ import type { Particle } from '../core/Particle.js';
4
+ /** Velocity-proportional air resistance (Blender: Force Field → Drag) */
5
+ export declare class DragForce 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
+ linear?: number;
11
+ quadratic?: number;
12
+ maxDistance?: number;
13
+ px?: number;
14
+ py?: number;
15
+ pz?: number;
16
+ });
17
+ apply(particle: Particle, dt: number): void;
18
+ }
19
+ //# sourceMappingURL=DragForce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DragForce.d.ts","sourceRoot":"","sources":["../../src/forces/DragForce.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,SAAU,SAAQ,SAAS;IACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAElC,iFAAiF;IACjF,QAAQ,EAAE,OAAO,CAAA;gBAEL,IAAI,GAAE;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;QACzD,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;CAc5C"}
@@ -0,0 +1,31 @@
1
+ import { Vector3 } from 'three';
2
+ import { BaseForce } from '../core/BaseForce.js';
3
+ /** Velocity-proportional air resistance (Blender: Force Field → Drag) */
4
+ export class DragForce 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
+ linear: opts.linear ?? 1,
10
+ quadratic: opts.quadratic ?? 0,
11
+ maxDistance: opts.maxDistance ?? 0,
12
+ };
13
+ }
14
+ apply(particle, dt) {
15
+ const p = this.parameters;
16
+ if (p.maxDistance > 0) {
17
+ const dx = particle.position.x - this.position.x;
18
+ const dy = particle.position.y - this.position.y;
19
+ const dz = particle.position.z - this.position.z;
20
+ if (dx * dx + dy * dy + dz * dz > p.maxDistance * p.maxDistance)
21
+ return;
22
+ }
23
+ const speed = particle.velocity.length();
24
+ if (speed < 0.0001)
25
+ return;
26
+ const drag = p.linear + p.quadratic * speed;
27
+ const factor = Math.max(0, 1 - drag * dt);
28
+ particle.velocity.multiplyScalar(factor);
29
+ }
30
+ }
31
+ //# sourceMappingURL=DragForce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DragForce.js","sourceRoot":"","sources":["../../src/forces/DragForce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAS,OAAO,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAQ,sBAAsB,CAAA;AAGlD,yEAAyE;AACzE,MAAM,OAAO,SAAU,SAAQ,SAAS;IAMtC,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,MAAM,EAAO,IAAI,CAAC,MAAM,IAAS,CAAC;YAClC,SAAS,EAAI,IAAI,CAAC,SAAS,IAAM,CAAC;YAClC,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,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAA;QACxC,IAAI,KAAK,GAAG,MAAM;YAAE,OAAM;QAC1B,MAAM,IAAI,GAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,SAAS,GAAG,KAAK,CAAA;QAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,GAAG,EAAE,CAAC,CAAA;QACzC,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAA;IAC1C,CAAC;CACF"}