@holoscript/engine 6.0.3 → 6.0.4

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 (192) hide show
  1. package/dist/AutoMesher-CK47F6AV.js +17 -0
  2. package/dist/GPUBuffers-2LHBCD7X.js +9 -0
  3. package/dist/WebGPUContext-TNEUYU2Y.js +11 -0
  4. package/dist/animation/index.cjs +38 -38
  5. package/dist/animation/index.d.cts +1 -1
  6. package/dist/animation/index.d.ts +1 -1
  7. package/dist/animation/index.js +1 -1
  8. package/dist/audio/index.cjs +16 -6
  9. package/dist/audio/index.d.cts +1 -1
  10. package/dist/audio/index.d.ts +1 -1
  11. package/dist/audio/index.js +1 -1
  12. package/dist/camera/index.cjs +23 -23
  13. package/dist/camera/index.d.cts +1 -1
  14. package/dist/camera/index.d.ts +1 -1
  15. package/dist/camera/index.js +1 -1
  16. package/dist/character/index.cjs +6 -4
  17. package/dist/character/index.js +1 -1
  18. package/dist/choreography/index.cjs +1194 -0
  19. package/dist/choreography/index.d.cts +687 -0
  20. package/dist/choreography/index.d.ts +687 -0
  21. package/dist/choreography/index.js +1156 -0
  22. package/dist/chunk-2CSNRI2N.js +217 -0
  23. package/dist/chunk-33T2WINR.js +266 -0
  24. package/dist/chunk-35R73OFM.js +1257 -0
  25. package/dist/chunk-4MMDSUNP.js +1256 -0
  26. package/dist/chunk-5V6HOU72.js +319 -0
  27. package/dist/chunk-6QOP6PYF.js +1038 -0
  28. package/dist/chunk-7KMJVHIL.js +8944 -0
  29. package/dist/chunk-7VPUC62U.js +1106 -0
  30. package/dist/chunk-A2Y6RCAT.js +1878 -0
  31. package/dist/chunk-AHM42MK6.js +8944 -0
  32. package/dist/chunk-BL7IDTHE.js +218 -0
  33. package/dist/chunk-CITOMSWL.js +10462 -0
  34. package/dist/chunk-CXDPKW2K.js +8944 -0
  35. package/dist/chunk-CXZPLD4S.js +223 -0
  36. package/dist/chunk-CZYJE7IH.js +5169 -0
  37. package/dist/chunk-D2OP7YC7.js +6325 -0
  38. package/dist/chunk-EDRVQHUU.js +1544 -0
  39. package/dist/chunk-EJSLOOW2.js +3589 -0
  40. package/dist/chunk-F53SFGW5.js +1878 -0
  41. package/dist/chunk-HCFPELPY.js +919 -0
  42. package/dist/chunk-HNEE36PY.js +93 -0
  43. package/dist/chunk-HYXNV36F.js +1256 -0
  44. package/dist/chunk-IB7KHVFY.js +821 -0
  45. package/dist/chunk-IBBO7YYG.js +690 -0
  46. package/dist/chunk-ILIBGINU.js +5470 -0
  47. package/dist/chunk-IS4MHLKN.js +5479 -0
  48. package/dist/chunk-JT2PFKWD.js +5479 -0
  49. package/dist/chunk-K4CUB4NY.js +1038 -0
  50. package/dist/chunk-KATDQXRJ.js +10462 -0
  51. package/dist/chunk-KBQE6ZFJ.js +8944 -0
  52. package/dist/chunk-KBVD5K7E.js +560 -0
  53. package/dist/chunk-KCDPVQRY.js +4088 -0
  54. package/dist/chunk-KN4QJPKN.js +8944 -0
  55. package/dist/chunk-KWJ3ROSI.js +8944 -0
  56. package/dist/chunk-L45VF6DD.js +919 -0
  57. package/dist/chunk-LY4T37YK.js +307 -0
  58. package/dist/chunk-MDN5WZXA.js +1544 -0
  59. package/dist/chunk-MGCDP6VU.js +928 -0
  60. package/dist/chunk-NCX7X6G2.js +8681 -0
  61. package/dist/chunk-OF54BPVD.js +913 -0
  62. package/dist/chunk-OWSN2Q3Q.js +690 -0
  63. package/dist/chunk-PRRB5TTA.js +406 -0
  64. package/dist/chunk-PXWVQF76.js +4086 -0
  65. package/dist/chunk-PYCOIDT2.js +812 -0
  66. package/dist/chunk-PZCSADOV.js +928 -0
  67. package/dist/chunk-Q2XBVS2K.js +1038 -0
  68. package/dist/chunk-QDZRXWN5.js +1776 -0
  69. package/dist/chunk-RNWOZ6WQ.js +913 -0
  70. package/dist/chunk-ROLFT4CJ.js +1693 -0
  71. package/dist/chunk-SLTJRZ2N.js +266 -0
  72. package/dist/chunk-SRUS5XSU.js +4088 -0
  73. package/dist/chunk-TKCA3WZ5.js +5409 -0
  74. package/dist/chunk-TNRMXYI2.js +1650 -0
  75. package/dist/chunk-TQB3GJGM.js +9763 -0
  76. package/dist/chunk-TUFGXG6K.js +510 -0
  77. package/dist/chunk-U6KMTGQJ.js +632 -0
  78. package/dist/chunk-VMGJQST6.js +8681 -0
  79. package/dist/chunk-X4F4TCG4.js +5470 -0
  80. package/dist/chunk-ZIFROE75.js +1544 -0
  81. package/dist/chunk-ZIJQYHSQ.js +1204 -0
  82. package/dist/combat/index.cjs +4 -4
  83. package/dist/combat/index.d.cts +1 -1
  84. package/dist/combat/index.d.ts +1 -1
  85. package/dist/combat/index.js +1 -1
  86. package/dist/ecs/index.cjs +1 -1
  87. package/dist/ecs/index.js +1 -1
  88. package/dist/environment/index.cjs +14 -14
  89. package/dist/environment/index.d.cts +1 -1
  90. package/dist/environment/index.d.ts +1 -1
  91. package/dist/environment/index.js +1 -1
  92. package/dist/gpu/index.cjs +4810 -0
  93. package/dist/gpu/index.js +3714 -0
  94. package/dist/hologram/index.cjs +27 -1
  95. package/dist/hologram/index.js +1 -1
  96. package/dist/index-B2PIsAmR.d.cts +2180 -0
  97. package/dist/index-B2PIsAmR.d.ts +2180 -0
  98. package/dist/index-BHySEPX7.d.cts +2921 -0
  99. package/dist/index-BJV21zuy.d.cts +341 -0
  100. package/dist/index-BJV21zuy.d.ts +341 -0
  101. package/dist/index-BQutTphC.d.cts +790 -0
  102. package/dist/index-ByIq2XrS.d.cts +3910 -0
  103. package/dist/index-BysHjDSO.d.cts +224 -0
  104. package/dist/index-BysHjDSO.d.ts +224 -0
  105. package/dist/index-CKwAJGck.d.ts +455 -0
  106. package/dist/index-CUl3QstQ.d.cts +3006 -0
  107. package/dist/index-CUl3QstQ.d.ts +3006 -0
  108. package/dist/index-CmYtNiI-.d.cts +953 -0
  109. package/dist/index-CmYtNiI-.d.ts +953 -0
  110. package/dist/index-CnRzWxi_.d.cts +522 -0
  111. package/dist/index-CnRzWxi_.d.ts +522 -0
  112. package/dist/index-CwRWbSC7.d.ts +2921 -0
  113. package/dist/index-CxKIBstO.d.ts +790 -0
  114. package/dist/index-DJ6-R8vh.d.cts +455 -0
  115. package/dist/index-DQKisbcI.d.cts +4968 -0
  116. package/dist/index-DQKisbcI.d.ts +4968 -0
  117. package/dist/index-DRT2zJez.d.ts +3910 -0
  118. package/dist/index-DfNLiAka.d.cts +192 -0
  119. package/dist/index-DfNLiAka.d.ts +192 -0
  120. package/dist/index-nMvkoRm8.d.cts +405 -0
  121. package/dist/index-nMvkoRm8.d.ts +405 -0
  122. package/dist/index-s9yOFU37.d.cts +604 -0
  123. package/dist/index-s9yOFU37.d.ts +604 -0
  124. package/dist/index.cjs +22966 -6960
  125. package/dist/index.d.cts +864 -20
  126. package/dist/index.d.ts +864 -20
  127. package/dist/index.js +3062 -48
  128. package/dist/input/index.cjs +1 -1
  129. package/dist/input/index.js +1 -1
  130. package/dist/orbital/index.cjs +3 -3
  131. package/dist/orbital/index.d.cts +1 -1
  132. package/dist/orbital/index.d.ts +1 -1
  133. package/dist/orbital/index.js +1 -1
  134. package/dist/particles/index.cjs +16 -16
  135. package/dist/particles/index.d.cts +1 -1
  136. package/dist/particles/index.d.ts +1 -1
  137. package/dist/particles/index.js +1 -1
  138. package/dist/physics/index.cjs +2377 -21
  139. package/dist/physics/index.d.cts +1 -1
  140. package/dist/physics/index.d.ts +1 -1
  141. package/dist/physics/index.js +35 -1
  142. package/dist/postfx/index.cjs +3491 -0
  143. package/dist/postfx/index.js +93 -0
  144. package/dist/procedural/index.cjs +1 -1
  145. package/dist/procedural/index.js +1 -1
  146. package/dist/puppeteer-5VF6KDVO.js +52197 -0
  147. package/dist/puppeteer-IZVZ3SG4.js +52197 -0
  148. package/dist/rendering/index.cjs +33 -32
  149. package/dist/rendering/index.d.cts +1 -1
  150. package/dist/rendering/index.d.ts +1 -1
  151. package/dist/rendering/index.js +8 -6
  152. package/dist/runtime/index.cjs +23 -13
  153. package/dist/runtime/index.d.cts +1 -1
  154. package/dist/runtime/index.d.ts +1 -1
  155. package/dist/runtime/index.js +8 -6
  156. package/dist/runtime/protocols/index.cjs +349 -0
  157. package/dist/runtime/protocols/index.js +15 -0
  158. package/dist/scene/index.cjs +8 -8
  159. package/dist/scene/index.d.cts +1 -1
  160. package/dist/scene/index.d.ts +1 -1
  161. package/dist/scene/index.js +1 -1
  162. package/dist/shader/index.cjs +3087 -0
  163. package/dist/shader/index.js +3044 -0
  164. package/dist/simulation/index.cjs +10680 -0
  165. package/dist/simulation/index.d.cts +3 -0
  166. package/dist/simulation/index.d.ts +3 -0
  167. package/dist/simulation/index.js +307 -0
  168. package/dist/spatial/index.cjs +2443 -0
  169. package/dist/spatial/index.d.cts +1545 -0
  170. package/dist/spatial/index.d.ts +1545 -0
  171. package/dist/spatial/index.js +2400 -0
  172. package/dist/terrain/index.cjs +1 -1
  173. package/dist/terrain/index.d.cts +1 -1
  174. package/dist/terrain/index.d.ts +1 -1
  175. package/dist/terrain/index.js +1 -1
  176. package/dist/transformers.node-4NKAPD5U.js +45620 -0
  177. package/dist/vm/index.cjs +7 -8
  178. package/dist/vm/index.d.cts +1 -1
  179. package/dist/vm/index.d.ts +1 -1
  180. package/dist/vm/index.js +1 -1
  181. package/dist/vm-bridge/index.cjs +2 -2
  182. package/dist/vm-bridge/index.d.cts +2 -2
  183. package/dist/vm-bridge/index.d.ts +2 -2
  184. package/dist/vm-bridge/index.js +1 -1
  185. package/dist/vr/index.cjs +6 -6
  186. package/dist/vr/index.js +1 -1
  187. package/dist/world/index.cjs +3 -3
  188. package/dist/world/index.d.cts +1 -1
  189. package/dist/world/index.d.ts +1 -1
  190. package/dist/world/index.js +1 -1
  191. package/package.json +53 -21
  192. package/LICENSE +0 -21
@@ -0,0 +1,1038 @@
1
+ import {
2
+ __export
3
+ } from "./chunk-AKLW2MUS.js";
4
+
5
+ // src/particles/index.ts
6
+ var particles_exports = {};
7
+ __export(particles_exports, {
8
+ ParticleAttractorSystem: () => ParticleAttractorSystem,
9
+ ParticleCollisionSystem: () => ParticleCollisionSystem,
10
+ ParticleEmitter: () => ParticleEmitter,
11
+ ParticleForceSystem: () => ParticleForceSystem,
12
+ ParticlePresets: () => ParticlePresets,
13
+ ParticleSystem: () => ParticleSystem,
14
+ ParticleTurbulence: () => ParticleTurbulence,
15
+ attractor: () => attractor,
16
+ drag: () => drag,
17
+ floorBounce: () => floorBounce,
18
+ getNodeParticleSystem: () => getNodeParticleSystem,
19
+ gravity: () => gravity,
20
+ particleTraitHandler: () => particleTraitHandler,
21
+ sizeOscillate: () => sizeOscillate,
22
+ turbulence: () => turbulence,
23
+ vortex: () => vortex,
24
+ wind: () => wind
25
+ });
26
+
27
+ // src/particles/ParticleSystem.ts
28
+ function randRange(min, max) {
29
+ return min + Math.random() * (max - min);
30
+ }
31
+ function lerpColor(a, b, t) {
32
+ return {
33
+ r: a.r + (b.r - a.r) * t,
34
+ g: a.g + (b.g - a.g) * t,
35
+ b: a.b + (b.b - a.b) * t,
36
+ a: a.a + (b.a - a.a) * t
37
+ };
38
+ }
39
+ function lerp(a, b, t) {
40
+ return a + (b - a) * t;
41
+ }
42
+ var ParticleSystem = class {
43
+ pool = [];
44
+ config;
45
+ emitAccumulator = 0;
46
+ activeCount = 0;
47
+ _isEmitting = true;
48
+ totalElapsed = 0;
49
+ /** External affectors applied each frame */
50
+ affectors = [];
51
+ constructor(config) {
52
+ this.config = config;
53
+ for (let i = 0; i < config.maxParticles; i++) {
54
+ this.pool.push(this.createDeadParticle());
55
+ }
56
+ }
57
+ createDeadParticle() {
58
+ return {
59
+ alive: false,
60
+ age: 0,
61
+ lifetime: 1,
62
+ x: 0,
63
+ y: 0,
64
+ z: 0,
65
+ vx: 0,
66
+ vy: 0,
67
+ vz: 0,
68
+ ax: 0,
69
+ ay: 0,
70
+ az: 0,
71
+ size: 1,
72
+ sizeStart: 1,
73
+ sizeEnd: 0,
74
+ color: { r: 1, g: 1, b: 1, a: 1 },
75
+ colorStart: { r: 1, g: 1, b: 1, a: 1 },
76
+ colorEnd: { r: 1, g: 1, b: 1, a: 0 },
77
+ rotation: 0,
78
+ rotationSpeed: 0
79
+ };
80
+ }
81
+ /**
82
+ * Add an affector function that runs on each particle per update.
83
+ */
84
+ addAffector(fn) {
85
+ this.affectors.push(fn);
86
+ }
87
+ /**
88
+ * Start/stop emitting new particles.
89
+ */
90
+ setEmitting(emitting) {
91
+ this._isEmitting = emitting;
92
+ }
93
+ isEmitting() {
94
+ return this._isEmitting;
95
+ }
96
+ /**
97
+ * Emit a burst of N particles immediately.
98
+ */
99
+ burst(count) {
100
+ for (let i = 0; i < count; i++) {
101
+ this.emitOne();
102
+ }
103
+ }
104
+ /**
105
+ * Update all particles. Call every frame.
106
+ */
107
+ update(delta) {
108
+ this.totalElapsed += delta;
109
+ if (this._isEmitting) {
110
+ this.emitAccumulator += this.config.rate * delta;
111
+ while (this.emitAccumulator >= 1) {
112
+ this.emitOne();
113
+ this.emitAccumulator -= 1;
114
+ }
115
+ }
116
+ this.activeCount = 0;
117
+ for (const p of this.pool) {
118
+ if (!p.alive) continue;
119
+ p.age += delta;
120
+ if (p.age >= p.lifetime) {
121
+ p.alive = false;
122
+ continue;
123
+ }
124
+ this.activeCount++;
125
+ const lifeT = p.age / p.lifetime;
126
+ for (const affector of this.affectors) {
127
+ affector(p, delta);
128
+ }
129
+ p.vx += p.ax * delta;
130
+ p.vy += p.ay * delta;
131
+ p.vz += p.az * delta;
132
+ p.x += p.vx * delta;
133
+ p.y += p.vy * delta;
134
+ p.z += p.vz * delta;
135
+ p.size = lerp(p.sizeStart, p.sizeEnd, lifeT);
136
+ p.color = lerpColor(p.colorStart, p.colorEnd, lifeT);
137
+ p.rotation += p.rotationSpeed * delta;
138
+ }
139
+ }
140
+ emitOne() {
141
+ const p = this.pool.find((particle) => !particle.alive);
142
+ if (!p) return;
143
+ const cfg = this.config;
144
+ const { px, py, pz, vx, vy, vz } = this.sampleEmitterShape();
145
+ p.alive = true;
146
+ p.age = 0;
147
+ p.lifetime = randRange(cfg.lifetime[0], cfg.lifetime[1]);
148
+ p.x = cfg.position.x + px;
149
+ p.y = cfg.position.y + py;
150
+ p.z = cfg.position.z + pz;
151
+ const speed = randRange(cfg.speed[0], cfg.speed[1]);
152
+ p.vx = vx * speed;
153
+ p.vy = vy * speed;
154
+ p.vz = vz * speed;
155
+ p.ax = 0;
156
+ p.ay = 0;
157
+ p.az = 0;
158
+ p.sizeStart = randRange(cfg.size[0], cfg.size[1]);
159
+ p.sizeEnd = randRange(cfg.sizeEnd[0], cfg.sizeEnd[1]);
160
+ p.size = p.sizeStart;
161
+ p.colorStart = { ...cfg.colorStart };
162
+ p.colorEnd = { ...cfg.colorEnd };
163
+ p.color = { ...cfg.colorStart };
164
+ const rotRange = cfg.rotationSpeed || [0, 0];
165
+ p.rotation = Math.random() * Math.PI * 2;
166
+ p.rotationSpeed = randRange(rotRange[0], rotRange[1]);
167
+ }
168
+ sampleEmitterShape() {
169
+ const dir = this.config.direction || { x: 0, y: 1, z: 0 };
170
+ switch (this.config.shape) {
171
+ case "point":
172
+ return { px: 0, py: 0, pz: 0, vx: dir.x, vy: dir.y, vz: dir.z };
173
+ case "sphere": {
174
+ const r = (this.config.radius || 0.1) * Math.random();
175
+ const theta = Math.random() * Math.PI * 2;
176
+ const phi = Math.acos(2 * Math.random() - 1);
177
+ const sx = Math.sin(phi) * Math.cos(theta);
178
+ const sy = Math.sin(phi) * Math.sin(theta);
179
+ const sz = Math.cos(phi);
180
+ return { px: sx * r, py: sy * r, pz: sz * r, vx: sx, vy: sy, vz: sz };
181
+ }
182
+ case "cone": {
183
+ const angle = this.config.coneAngle || Math.PI / 6;
184
+ const theta = Math.random() * Math.PI * 2;
185
+ const phi = Math.random() * angle;
186
+ const sx = Math.sin(phi) * Math.cos(theta);
187
+ const sy = Math.cos(phi);
188
+ const sz = Math.sin(phi) * Math.sin(theta);
189
+ return { px: 0, py: 0, pz: 0, vx: sx, vy: sy, vz: sz };
190
+ }
191
+ case "box": {
192
+ const r = this.config.radius || 0.5;
193
+ const px = (Math.random() - 0.5) * r * 2;
194
+ const py = (Math.random() - 0.5) * r * 2;
195
+ const pz = (Math.random() - 0.5) * r * 2;
196
+ return { px, py, pz, vx: dir.x, vy: dir.y, vz: dir.z };
197
+ }
198
+ default:
199
+ return { px: 0, py: 0, pz: 0, vx: 0, vy: 1, vz: 0 };
200
+ }
201
+ }
202
+ /**
203
+ * Get all alive particles (for rendering).
204
+ */
205
+ getAliveParticles() {
206
+ return this.pool.filter((p) => p.alive);
207
+ }
208
+ /**
209
+ * Get active particle count.
210
+ */
211
+ getActiveCount() {
212
+ return this.activeCount;
213
+ }
214
+ /**
215
+ * Get the config.
216
+ */
217
+ getConfig() {
218
+ return this.config;
219
+ }
220
+ /**
221
+ * Update emitter position.
222
+ */
223
+ setPosition(x, y, z) {
224
+ this.config.position = { x, y, z };
225
+ }
226
+ };
227
+
228
+ // src/particles/ParticleAffectors.ts
229
+ function gravity(strength = -9.81) {
230
+ return (p, _delta) => {
231
+ p.ay = strength;
232
+ };
233
+ }
234
+ function wind(x, y, z) {
235
+ return (p, delta) => {
236
+ p.vx += x * delta;
237
+ p.vy += y * delta;
238
+ p.vz += z * delta;
239
+ };
240
+ }
241
+ function turbulence(strength = 1) {
242
+ return (p, delta) => {
243
+ p.vx += (Math.random() - 0.5) * strength * delta;
244
+ p.vy += (Math.random() - 0.5) * strength * delta;
245
+ p.vz += (Math.random() - 0.5) * strength * delta;
246
+ };
247
+ }
248
+ function drag(coefficient = 0.98) {
249
+ const factor = Math.pow(coefficient, 60);
250
+ return (p, delta) => {
251
+ const f = Math.pow(factor, delta * 60);
252
+ p.vx *= f;
253
+ p.vy *= f;
254
+ p.vz *= f;
255
+ };
256
+ }
257
+ function attractor(x, y, z, strength = 5, minDist = 0.1) {
258
+ return (p, delta) => {
259
+ const dx = x - p.x;
260
+ const dy = y - p.y;
261
+ const dz = z - p.z;
262
+ const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
263
+ if (dist < minDist) return;
264
+ const force = strength / (dist * dist);
265
+ p.vx += dx / dist * force * delta;
266
+ p.vy += dy / dist * force * delta;
267
+ p.vz += dz / dist * force * delta;
268
+ };
269
+ }
270
+ function vortex(axisX, axisY, axisZ, strength = 2) {
271
+ const len = Math.sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ) || 1;
272
+ const ax = axisX / len, ay = axisY / len, az = axisZ / len;
273
+ return (p, delta) => {
274
+ const cx = ay * p.vz - az * p.vy;
275
+ const cy = az * p.vx - ax * p.vz;
276
+ const cz = ax * p.vy - ay * p.vx;
277
+ p.vx += cx * strength * delta;
278
+ p.vy += cy * strength * delta;
279
+ p.vz += cz * strength * delta;
280
+ };
281
+ }
282
+ function floorBounce(floorY = 0, bounciness = 0.6) {
283
+ return (p, _delta) => {
284
+ if (p.y <= floorY && p.vy < 0) {
285
+ p.y = floorY;
286
+ p.vy = -p.vy * bounciness;
287
+ }
288
+ };
289
+ }
290
+ function sizeOscillate(frequency = 3, amplitude = 0.3) {
291
+ return (p, _delta) => {
292
+ const pulse = Math.sin(p.age * frequency * Math.PI * 2) * amplitude;
293
+ p.size = Math.max(1e-3, p.size + pulse * p.size);
294
+ };
295
+ }
296
+
297
+ // src/particles/ParticlePresets.ts
298
+ var ParticlePresets = {
299
+ /** Ambient floating dust motes */
300
+ dust: {
301
+ shape: "box",
302
+ rate: 5,
303
+ maxParticles: 80,
304
+ lifetime: [4, 8],
305
+ speed: [0.01, 0.03],
306
+ size: [3e-3, 8e-3],
307
+ sizeEnd: [1e-3, 3e-3],
308
+ colorStart: { r: 0.9, g: 0.85, b: 0.7, a: 0.4 },
309
+ colorEnd: { r: 0.9, g: 0.85, b: 0.7, a: 0 },
310
+ position: { x: 0, y: 1, z: 0 },
311
+ radius: 2,
312
+ direction: { x: 0, y: 0.2, z: 0 }
313
+ },
314
+ /** Sparks flying upward */
315
+ sparks: {
316
+ shape: "cone",
317
+ rate: 30,
318
+ maxParticles: 200,
319
+ lifetime: [0.3, 1],
320
+ speed: [1.5, 4],
321
+ size: [5e-3, 0.015],
322
+ sizeEnd: [1e-3, 3e-3],
323
+ colorStart: { r: 1, g: 0.8, b: 0.2, a: 1 },
324
+ colorEnd: { r: 1, g: 0.3, b: 0, a: 0 },
325
+ position: { x: 0, y: 0, z: 0 },
326
+ coneAngle: Math.PI / 8,
327
+ direction: { x: 0, y: 1, z: 0 }
328
+ },
329
+ /** Flame / fire effect */
330
+ fire: {
331
+ shape: "cone",
332
+ rate: 40,
333
+ maxParticles: 150,
334
+ lifetime: [0.5, 1.5],
335
+ speed: [0.3, 0.8],
336
+ size: [0.03, 0.08],
337
+ sizeEnd: [0.01, 0.02],
338
+ colorStart: { r: 1, g: 0.6, b: 0.1, a: 0.9 },
339
+ colorEnd: { r: 0.8, g: 0.1, b: 0, a: 0 },
340
+ position: { x: 0, y: 0, z: 0 },
341
+ coneAngle: Math.PI / 10,
342
+ direction: { x: 0, y: 1, z: 0 }
343
+ },
344
+ /** Confetti burst (use with burst() method) */
345
+ confetti: {
346
+ shape: "sphere",
347
+ rate: 0,
348
+ // Manual burst only
349
+ burst: 50,
350
+ maxParticles: 100,
351
+ lifetime: [2, 4],
352
+ speed: [2, 5],
353
+ size: [0.01, 0.025],
354
+ sizeEnd: [0.01, 0.025],
355
+ colorStart: { r: 1, g: 0.2, b: 0.5, a: 1 },
356
+ colorEnd: { r: 0.2, g: 0.5, b: 1, a: 0.5 },
357
+ position: { x: 0, y: 1, z: 0 },
358
+ radius: 0.1,
359
+ rotationSpeed: [-5, 5]
360
+ },
361
+ /** Snow */
362
+ snow: {
363
+ shape: "box",
364
+ rate: 15,
365
+ maxParticles: 300,
366
+ lifetime: [5, 10],
367
+ speed: [0.1, 0.3],
368
+ size: [5e-3, 0.015],
369
+ sizeEnd: [3e-3, 0.01],
370
+ colorStart: { r: 1, g: 1, b: 1, a: 0.8 },
371
+ colorEnd: { r: 1, g: 1, b: 1, a: 0 },
372
+ position: { x: 0, y: 3, z: 0 },
373
+ radius: 3,
374
+ direction: { x: 0, y: -1, z: 0 },
375
+ rotationSpeed: [-1, 1]
376
+ },
377
+ /** UI haptic feedback pulse (tiny, fast) */
378
+ hapticPulse: {
379
+ shape: "sphere",
380
+ rate: 0,
381
+ burst: 12,
382
+ maxParticles: 30,
383
+ lifetime: [0.15, 0.35],
384
+ speed: [0.3, 0.8],
385
+ size: [2e-3, 6e-3],
386
+ sizeEnd: [0, 0],
387
+ colorStart: { r: 0.3, g: 0.6, b: 1, a: 0.9 },
388
+ colorEnd: { r: 0.3, g: 0.6, b: 1, a: 0 },
389
+ position: { x: 0, y: 0, z: 0 },
390
+ radius: 0.01
391
+ },
392
+ /** Smoke */
393
+ smoke: {
394
+ shape: "cone",
395
+ rate: 10,
396
+ maxParticles: 100,
397
+ lifetime: [2, 5],
398
+ speed: [0.1, 0.4],
399
+ size: [0.03, 0.06],
400
+ sizeEnd: [0.08, 0.15],
401
+ colorStart: { r: 0.4, g: 0.4, b: 0.4, a: 0.5 },
402
+ colorEnd: { r: 0.6, g: 0.6, b: 0.6, a: 0 },
403
+ position: { x: 0, y: 0, z: 0 },
404
+ coneAngle: Math.PI / 12,
405
+ direction: { x: 0, y: 1, z: 0 }
406
+ }
407
+ };
408
+
409
+ // src/particles/ParticleAttractor.ts
410
+ var ParticleAttractorSystem = class {
411
+ attractors = /* @__PURE__ */ new Map();
412
+ addAttractor(a) {
413
+ this.attractors.set(a.id, a);
414
+ }
415
+ removeAttractor(id) {
416
+ this.attractors.delete(id);
417
+ }
418
+ apply(particles, dt) {
419
+ for (const attractor2 of this.attractors.values()) {
420
+ for (const p of particles) {
421
+ if (!p.alive) continue;
422
+ const dx = attractor2.position.x - p.x;
423
+ const dy = attractor2.position.y - p.y;
424
+ const dz = attractor2.position.z - p.z;
425
+ const distSq = dx * dx + dy * dy + dz * dz;
426
+ const dist = Math.sqrt(distSq);
427
+ if (dist > attractor2.radius) continue;
428
+ if (dist < attractor2.killRadius) {
429
+ p.alive = false;
430
+ continue;
431
+ }
432
+ const force = attractor2.strength / Math.max(distSq, 0.01);
433
+ const nx = dx / dist, ny = dy / dist, nz = dz / dist;
434
+ if (attractor2.orbit) {
435
+ const tx = ny * 0 - nz * 1 || nz;
436
+ const ty = nz * 0 - nx * 0 || -nx;
437
+ const tz = nx * 1 - ny * 0 || 0;
438
+ p.vx += tx * force * dt;
439
+ p.vy += ty * force * dt;
440
+ p.vz += tz * force * dt;
441
+ } else {
442
+ p.vx += nx * force * dt;
443
+ p.vy += ny * force * dt;
444
+ p.vz += nz * force * dt;
445
+ }
446
+ }
447
+ }
448
+ }
449
+ getAttractorCount() {
450
+ return this.attractors.size;
451
+ }
452
+ };
453
+
454
+ // src/particles/ParticleEmitter.ts
455
+ function lerp2(a, b, t) {
456
+ return a + (b - a) * t;
457
+ }
458
+ function randomRange(min, max) {
459
+ return min + Math.random() * (max - min);
460
+ }
461
+ function sampleCurve(keyframes, t) {
462
+ if (keyframes.length === 0) return 1;
463
+ if (t <= keyframes[0].time) return keyframes[0].value;
464
+ if (t >= keyframes[keyframes.length - 1].time) return keyframes[keyframes.length - 1].value;
465
+ for (let i = 0; i < keyframes.length - 1; i++) {
466
+ if (t >= keyframes[i].time && t <= keyframes[i + 1].time) {
467
+ const segT = (t - keyframes[i].time) / (keyframes[i + 1].time - keyframes[i].time);
468
+ return lerp2(keyframes[i].value, keyframes[i + 1].value, segT);
469
+ }
470
+ }
471
+ return keyframes[keyframes.length - 1].value;
472
+ }
473
+ var ParticleEmitter = class {
474
+ config;
475
+ particles = [];
476
+ state;
477
+ constructor(config) {
478
+ this.config = { ...config };
479
+ this.state = {
480
+ id: config.id,
481
+ playing: false,
482
+ elapsed: 0,
483
+ emissionAccum: 0,
484
+ aliveCount: 0,
485
+ totalEmitted: 0
486
+ };
487
+ for (let i = 0; i < config.maxParticles; i++) {
488
+ this.particles.push(this.createDeadParticle());
489
+ }
490
+ if (config.prewarm) {
491
+ this.play();
492
+ this.update(2);
493
+ }
494
+ }
495
+ // ---------------------------------------------------------------------------
496
+ // Controls
497
+ // ---------------------------------------------------------------------------
498
+ play() {
499
+ this.state.playing = true;
500
+ }
501
+ pause() {
502
+ this.state.playing = false;
503
+ }
504
+ stop() {
505
+ this.state.playing = false;
506
+ this.state.elapsed = 0;
507
+ this.state.emissionAccum = 0;
508
+ for (const p of this.particles) p.alive = false;
509
+ this.state.aliveCount = 0;
510
+ }
511
+ isPlaying() {
512
+ return this.state.playing;
513
+ }
514
+ getState() {
515
+ return { ...this.state };
516
+ }
517
+ // ---------------------------------------------------------------------------
518
+ // Update
519
+ // ---------------------------------------------------------------------------
520
+ update(dt) {
521
+ if (!this.state.playing) return this.getAliveParticles();
522
+ this.state.elapsed += dt;
523
+ this.state.emissionAccum += this.config.emissionRate * dt;
524
+ while (this.state.emissionAccum >= 1) {
525
+ this.emit();
526
+ this.state.emissionAccum -= 1;
527
+ }
528
+ this.state.aliveCount = 0;
529
+ for (const p of this.particles) {
530
+ if (!p.alive) continue;
531
+ p.age += dt;
532
+ if (p.age >= p.lifetime) {
533
+ p.alive = false;
534
+ continue;
535
+ }
536
+ const t = p.age / p.lifetime;
537
+ p.velocity.y -= this.config.gravity * dt;
538
+ if (this.config.speedOverLifetime) {
539
+ const speedMul = sampleCurve(this.config.speedOverLifetime, t);
540
+ const speed = Math.sqrt(p.velocity.x ** 2 + p.velocity.y ** 2 + p.velocity.z ** 2);
541
+ if (speed > 1e-3) {
542
+ const targetSpeed = p.startSpeed * speedMul;
543
+ const scale = targetSpeed / speed;
544
+ p.velocity.x *= scale;
545
+ p.velocity.y *= scale;
546
+ p.velocity.z *= scale;
547
+ }
548
+ }
549
+ p.position.x += p.velocity.x * dt;
550
+ p.position.y += p.velocity.y * dt;
551
+ p.position.z += p.velocity.z * dt;
552
+ if (this.config.sizeOverLifetime) {
553
+ p.size = sampleCurve(this.config.sizeOverLifetime, t);
554
+ }
555
+ if (this.config.alphaOverLifetime) {
556
+ p.color.a = sampleCurve(this.config.alphaOverLifetime, t);
557
+ }
558
+ if (this.config.endColor) {
559
+ p.color.r = lerp2(this.config.startColor.r, this.config.endColor.r, t);
560
+ p.color.g = lerp2(this.config.startColor.g, this.config.endColor.g, t);
561
+ p.color.b = lerp2(this.config.startColor.b, this.config.endColor.b, t);
562
+ }
563
+ this.state.aliveCount++;
564
+ }
565
+ return this.getAliveParticles();
566
+ }
567
+ // ---------------------------------------------------------------------------
568
+ // Emission
569
+ // ---------------------------------------------------------------------------
570
+ emit() {
571
+ const p = this.particles.find((p2) => !p2.alive);
572
+ if (!p) return;
573
+ p.alive = true;
574
+ p.age = 0;
575
+ p.lifetime = randomRange(this.config.lifetime.min, this.config.lifetime.max);
576
+ p.size = randomRange(this.config.startSize.min, this.config.startSize.max);
577
+ p.color = { ...this.config.startColor };
578
+ p.startSpeed = randomRange(this.config.startSpeed.min, this.config.startSpeed.max);
579
+ const { position, direction } = this.sampleShape();
580
+ p.position = position;
581
+ p.velocity = {
582
+ x: direction.x * p.startSpeed,
583
+ y: direction.y * p.startSpeed,
584
+ z: direction.z * p.startSpeed
585
+ };
586
+ this.state.totalEmitted++;
587
+ }
588
+ sampleShape() {
589
+ const shape = this.config.emissionShape;
590
+ const params = this.config.shapeParams;
591
+ switch (shape) {
592
+ case "point":
593
+ return { position: { x: 0, y: 0, z: 0 }, direction: this.randomDirection() };
594
+ case "sphere": {
595
+ const dir = this.randomDirection();
596
+ const r = (params.radius ?? 1) * Math.cbrt(Math.random());
597
+ return {
598
+ position: { x: dir.x * r, y: dir.y * r, z: dir.z * r },
599
+ direction: dir
600
+ };
601
+ }
602
+ case "box": {
603
+ const ext = params.extents ?? { x: 1, y: 1, z: 1 };
604
+ return {
605
+ position: {
606
+ x: randomRange(-ext.x, ext.x),
607
+ y: randomRange(-ext.y, ext.y),
608
+ z: randomRange(-ext.z, ext.z)
609
+ },
610
+ direction: { x: 0, y: 1, z: 0 }
611
+ };
612
+ }
613
+ case "cone": {
614
+ const angle = (params.angle ?? 30) * Math.PI / 180;
615
+ const phi = Math.random() * 2 * Math.PI;
616
+ const cosTheta = Math.cos(angle * Math.random());
617
+ const sinTheta = Math.sqrt(1 - cosTheta * cosTheta);
618
+ return {
619
+ position: { x: 0, y: 0, z: 0 },
620
+ direction: {
621
+ x: sinTheta * Math.cos(phi),
622
+ y: cosTheta,
623
+ z: sinTheta * Math.sin(phi)
624
+ }
625
+ };
626
+ }
627
+ case "circle": {
628
+ const r = (params.radius ?? 1) * Math.sqrt(Math.random());
629
+ const theta = Math.random() * 2 * Math.PI;
630
+ return {
631
+ position: { x: r * Math.cos(theta), y: 0, z: r * Math.sin(theta) },
632
+ direction: { x: 0, y: 1, z: 0 }
633
+ };
634
+ }
635
+ case "line": {
636
+ const len = params.length ?? 2;
637
+ return {
638
+ position: { x: randomRange(-len / 2, len / 2), y: 0, z: 0 },
639
+ direction: { x: 0, y: 1, z: 0 }
640
+ };
641
+ }
642
+ }
643
+ }
644
+ randomDirection() {
645
+ const theta = Math.acos(2 * Math.random() - 1);
646
+ const phi = 2 * Math.PI * Math.random();
647
+ return {
648
+ x: Math.sin(theta) * Math.cos(phi),
649
+ y: Math.sin(theta) * Math.sin(phi),
650
+ z: Math.cos(theta)
651
+ };
652
+ }
653
+ createDeadParticle() {
654
+ return {
655
+ position: { x: 0, y: 0, z: 0 },
656
+ velocity: { x: 0, y: 0, z: 0 },
657
+ color: { r: 1, g: 1, b: 1, a: 1 },
658
+ size: 1,
659
+ age: 0,
660
+ lifetime: 1,
661
+ alive: false,
662
+ startSpeed: 0
663
+ };
664
+ }
665
+ // ---------------------------------------------------------------------------
666
+ // Queries
667
+ // ---------------------------------------------------------------------------
668
+ getAliveParticles() {
669
+ return this.particles.filter((p) => p.alive);
670
+ }
671
+ getAliveCount() {
672
+ return this.state.aliveCount;
673
+ }
674
+ getCapacity() {
675
+ return this.config.maxParticles;
676
+ }
677
+ };
678
+
679
+ // src/particles/ParticleCollision.ts
680
+ var ParticleCollisionSystem = class {
681
+ planes = [];
682
+ spheres = [];
683
+ subEmitCallback = null;
684
+ subEmitCount = 3;
685
+ collisionCount = 0;
686
+ // ---------------------------------------------------------------------------
687
+ // Collider Management
688
+ // ---------------------------------------------------------------------------
689
+ addPlane(plane) {
690
+ this.planes.push(plane);
691
+ }
692
+ addSphere(sphere) {
693
+ this.spheres.push(sphere);
694
+ }
695
+ onSubEmit(callback, count = 3) {
696
+ this.subEmitCallback = callback;
697
+ this.subEmitCount = count;
698
+ }
699
+ // ---------------------------------------------------------------------------
700
+ // Collision Detection & Response
701
+ // ---------------------------------------------------------------------------
702
+ resolve(particles) {
703
+ this.collisionCount = 0;
704
+ for (const p of particles) {
705
+ if (!p.alive) continue;
706
+ for (const plane of this.planes) {
707
+ const dist = plane.nx * p.x + plane.ny * p.y + plane.nz * p.z + plane.d;
708
+ if (dist < 0) {
709
+ p.x -= plane.nx * dist;
710
+ p.y -= plane.ny * dist;
711
+ p.z -= plane.nz * dist;
712
+ const vDotN = p.vx * plane.nx + p.vy * plane.ny + p.vz * plane.nz;
713
+ p.vx = (p.vx - 2 * vDotN * plane.nx) * plane.bounce;
714
+ p.vy = (p.vy - 2 * vDotN * plane.ny) * plane.bounce;
715
+ p.vz = (p.vz - 2 * vDotN * plane.nz) * plane.bounce;
716
+ p.vx *= 1 - plane.friction;
717
+ p.vy *= 1 - plane.friction;
718
+ p.vz *= 1 - plane.friction;
719
+ p.lifetime -= plane.lifetimeLoss;
720
+ if (p.lifetime <= 0) p.alive = false;
721
+ this.collisionCount++;
722
+ if (this.subEmitCallback) this.subEmitCallback(p.x, p.y, p.z, this.subEmitCount);
723
+ }
724
+ }
725
+ for (const sphere of this.spheres) {
726
+ const dx = p.x - sphere.cx, dy = p.y - sphere.cy, dz = p.z - sphere.cz;
727
+ const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
728
+ if (dist < sphere.radius && dist > 0) {
729
+ const nx = dx / dist, ny = dy / dist, nz = dz / dist;
730
+ const pen = sphere.radius - dist;
731
+ p.x += nx * pen;
732
+ p.y += ny * pen;
733
+ p.z += nz * pen;
734
+ const vDotN = p.vx * nx + p.vy * ny + p.vz * nz;
735
+ p.vx = (p.vx - 2 * vDotN * nx) * sphere.bounce;
736
+ p.vy = (p.vy - 2 * vDotN * ny) * sphere.bounce;
737
+ p.vz = (p.vz - 2 * vDotN * nz) * sphere.bounce;
738
+ p.vx *= 1 - sphere.friction;
739
+ p.vy *= 1 - sphere.friction;
740
+ p.vz *= 1 - sphere.friction;
741
+ this.collisionCount++;
742
+ }
743
+ }
744
+ }
745
+ }
746
+ getCollisionCount() {
747
+ return this.collisionCount;
748
+ }
749
+ };
750
+
751
+ // src/particles/ParticleForces.ts
752
+ function computeFalloff(distance, radius, type) {
753
+ if (type === "none" || radius <= 0) return 1;
754
+ if (distance >= radius) return 0;
755
+ const t = distance / radius;
756
+ return type === "linear" ? 1 - t : (1 - t) * (1 - t);
757
+ }
758
+ function simpleNoise3D(x, y, z) {
759
+ const px = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
760
+ const py = Math.sin(y * 12.9898 + z * 78.233) * 43758.5453;
761
+ const pz = Math.sin(z * 12.9898 + x * 78.233) * 43758.5453;
762
+ return {
763
+ x: (px - Math.floor(px)) * 2 - 1,
764
+ y: (py - Math.floor(py)) * 2 - 1,
765
+ z: (pz - Math.floor(pz)) * 2 - 1
766
+ };
767
+ }
768
+ var ParticleForceSystem = class {
769
+ fields = /* @__PURE__ */ new Map();
770
+ time = 0;
771
+ // ---------------------------------------------------------------------------
772
+ // Management
773
+ // ---------------------------------------------------------------------------
774
+ addForce(config) {
775
+ this.fields.set(config.id, { config, enabled: true });
776
+ }
777
+ removeForce(id) {
778
+ this.fields.delete(id);
779
+ }
780
+ setEnabled(id, enabled) {
781
+ const f = this.fields.get(id);
782
+ if (f) f.enabled = enabled;
783
+ }
784
+ getForce(id) {
785
+ return this.fields.get(id);
786
+ }
787
+ getForceCount() {
788
+ return this.fields.size;
789
+ }
790
+ // ---------------------------------------------------------------------------
791
+ // Application
792
+ // ---------------------------------------------------------------------------
793
+ /**
794
+ * Apply all force fields to a set of particles.
795
+ */
796
+ apply(particles, dt) {
797
+ this.time += dt;
798
+ for (const particle of particles) {
799
+ if (!particle.alive) continue;
800
+ for (const field of this.fields.values()) {
801
+ if (!field.enabled) continue;
802
+ this.applyForce(particle, field.config, dt);
803
+ }
804
+ }
805
+ }
806
+ applyForce(p, cfg, dt) {
807
+ switch (cfg.type) {
808
+ case "gravity":
809
+ this.applyGravity(p, cfg, dt);
810
+ break;
811
+ case "wind":
812
+ this.applyWind(p, cfg, dt);
813
+ break;
814
+ case "turbulence":
815
+ this.applyTurbulence(p, cfg, dt);
816
+ break;
817
+ case "attractor":
818
+ this.applyAttractor(p, cfg, dt);
819
+ break;
820
+ case "vortex":
821
+ this.applyVortex(p, cfg, dt);
822
+ break;
823
+ case "drag":
824
+ this.applyDrag(p, cfg, dt);
825
+ break;
826
+ }
827
+ }
828
+ applyGravity(p, cfg, dt) {
829
+ const dir = cfg.direction ?? { x: 0, y: -1, z: 0 };
830
+ p.velocity.x += dir.x * cfg.strength * dt;
831
+ p.velocity.y += dir.y * cfg.strength * dt;
832
+ p.velocity.z += dir.z * cfg.strength * dt;
833
+ }
834
+ applyWind(p, cfg, dt) {
835
+ const dir = cfg.direction ?? { x: 1, y: 0, z: 0 };
836
+ const falloff = this.getPositionalFalloff(p, cfg);
837
+ p.velocity.x += dir.x * cfg.strength * falloff * dt;
838
+ p.velocity.y += dir.y * cfg.strength * falloff * dt;
839
+ p.velocity.z += dir.z * cfg.strength * falloff * dt;
840
+ }
841
+ applyTurbulence(p, cfg, dt) {
842
+ const freq = cfg.frequency ?? 1;
843
+ const noise = simpleNoise3D(
844
+ p.position.x * freq + this.time,
845
+ p.position.y * freq + this.time * 0.7,
846
+ p.position.z * freq + this.time * 1.3
847
+ );
848
+ const falloff = this.getPositionalFalloff(p, cfg);
849
+ p.velocity.x += noise.x * cfg.strength * falloff * dt;
850
+ p.velocity.y += noise.y * cfg.strength * falloff * dt;
851
+ p.velocity.z += noise.z * cfg.strength * falloff * dt;
852
+ }
853
+ applyAttractor(p, cfg, dt) {
854
+ const target = cfg.position ?? { x: 0, y: 0, z: 0 };
855
+ const dx = target.x - p.position.x;
856
+ const dy = target.y - p.position.y;
857
+ const dz = target.z - p.position.z;
858
+ const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
859
+ if (dist < 1e-3) return;
860
+ const falloff = this.getPositionalFalloff(p, cfg);
861
+ const force = cfg.strength * falloff / dist;
862
+ p.velocity.x += dx * force * dt;
863
+ p.velocity.y += dy * force * dt;
864
+ p.velocity.z += dz * force * dt;
865
+ }
866
+ applyVortex(p, cfg, dt) {
867
+ const center = cfg.position ?? { x: 0, y: 0, z: 0 };
868
+ const dx = p.position.x - center.x;
869
+ const dz = p.position.z - center.z;
870
+ const dist = Math.sqrt(dx * dx + dz * dz);
871
+ if (dist < 1e-3) return;
872
+ const falloff = this.getPositionalFalloff(p, cfg);
873
+ const force = cfg.strength * falloff / dist;
874
+ p.velocity.x += -dz * force * dt;
875
+ p.velocity.z += dx * force * dt;
876
+ }
877
+ applyDrag(p, cfg, dt) {
878
+ const drag2 = cfg.dragCoefficient ?? 0.1;
879
+ const factor = Math.max(0, 1 - drag2 * dt);
880
+ p.velocity.x *= factor;
881
+ p.velocity.y *= factor;
882
+ p.velocity.z *= factor;
883
+ }
884
+ getPositionalFalloff(p, cfg) {
885
+ if (!cfg.position || !cfg.radius) return 1;
886
+ const dx = p.position.x - cfg.position.x;
887
+ const dy = p.position.y - cfg.position.y;
888
+ const dz = p.position.z - cfg.position.z;
889
+ const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
890
+ return computeFalloff(dist, cfg.radius, cfg.falloff ?? "linear");
891
+ }
892
+ };
893
+
894
+ // src/particles/ParticleTurbulence.ts
895
+ var ParticleTurbulence = class {
896
+ config;
897
+ constructor(config) {
898
+ this.config = { strength: 1, frequency: 1, octaves: 1, time: 0, ...config };
899
+ }
900
+ // ---------------------------------------------------------------------------
901
+ // Configuration
902
+ // ---------------------------------------------------------------------------
903
+ setStrength(s) {
904
+ this.config.strength = s;
905
+ }
906
+ setFrequency(f) {
907
+ this.config.frequency = f;
908
+ }
909
+ setTime(t) {
910
+ this.config.time = t;
911
+ }
912
+ getConfig() {
913
+ return { ...this.config };
914
+ }
915
+ // ---------------------------------------------------------------------------
916
+ // Curl Noise Approximation
917
+ // ---------------------------------------------------------------------------
918
+ sampleCurl(x, y, z) {
919
+ const f = this.config.frequency;
920
+ const t = this.config.time;
921
+ let fx = 0, fy = 0, fz = 0;
922
+ for (let o = 0; o < this.config.octaves; o++) {
923
+ const scale = Math.pow(2, o);
924
+ const amp = 1 / scale;
925
+ fx += Math.sin(y * f * scale + t) * amp - Math.cos(z * f * scale + t * 0.7) * amp;
926
+ fy += Math.sin(z * f * scale + t * 1.3) * amp - Math.cos(x * f * scale + t * 0.5) * amp;
927
+ fz += Math.sin(x * f * scale + t * 0.9) * amp - Math.cos(y * f * scale + t * 1.1) * amp;
928
+ }
929
+ return {
930
+ fx: fx * this.config.strength,
931
+ fy: fy * this.config.strength,
932
+ fz: fz * this.config.strength
933
+ };
934
+ }
935
+ // ---------------------------------------------------------------------------
936
+ // Application
937
+ // ---------------------------------------------------------------------------
938
+ apply(particles, dt) {
939
+ for (const p of particles) {
940
+ const curl = this.sampleCurl(p.x, p.y, p.z);
941
+ p.vx += curl.fx * dt;
942
+ p.vy += curl.fy * dt;
943
+ p.vz += curl.fz * dt;
944
+ }
945
+ }
946
+ // ---------------------------------------------------------------------------
947
+ // Advance Time
948
+ // ---------------------------------------------------------------------------
949
+ tick(dt) {
950
+ this.config.time += dt;
951
+ }
952
+ };
953
+
954
+ // src/particles/ParticleTrait.ts
955
+ var nodeSystems = /* @__PURE__ */ new Map();
956
+ var defaultConfig = {
957
+ preset: "sparks",
958
+ followNode: true,
959
+ autoStart: true
960
+ };
961
+ var affectorRegistry = {
962
+ gravity: gravity(-9.81),
963
+ lightGravity: gravity(-2),
964
+ wind: wind(0.5, 0, 0),
965
+ turbulence: turbulence(1),
966
+ drag: drag(0.98),
967
+ floorBounce: floorBounce(0, 0.6)
968
+ };
969
+ var particleTraitHandler = {
970
+ name: "particles",
971
+ defaultConfig,
972
+ onAttach(node, config, _context) {
973
+ const nodeId = node.id;
974
+ const presetConfig = config.preset ? ParticlePresets[config.preset] : ParticlePresets.sparks;
975
+ if (!presetConfig) return;
976
+ const emitterConfig = {
977
+ ...presetConfig,
978
+ ...config.emitter || {}
979
+ };
980
+ if (config.followNode && node.properties?.position) {
981
+ const pos = node.properties.position;
982
+ emitterConfig.position = { x: pos.x || 0, y: pos.y || 0, z: pos.z || 0 };
983
+ }
984
+ const system = new ParticleSystem(emitterConfig);
985
+ if (config.affectors) {
986
+ for (const name of config.affectors) {
987
+ if (affectorRegistry[name]) {
988
+ system.addAffector(affectorRegistry[name]);
989
+ }
990
+ }
991
+ }
992
+ system.setEmitting(config.autoStart !== false);
993
+ if (emitterConfig.burst && config.autoStart !== false) {
994
+ system.burst(emitterConfig.burst);
995
+ }
996
+ nodeSystems.set(nodeId, system);
997
+ },
998
+ onDetach(node, _config, _context) {
999
+ nodeSystems.delete(node.id);
1000
+ },
1001
+ onUpdate(node, config, _context, delta) {
1002
+ const system = nodeSystems.get(node.id);
1003
+ if (!system) return;
1004
+ if (config.followNode && node.properties?.position) {
1005
+ const pos = node.properties.position;
1006
+ system.setPosition(pos.x || 0, pos.y || 0, pos.z || 0);
1007
+ }
1008
+ system.update(delta);
1009
+ if (node.properties) {
1010
+ node.properties._particles = system.getAliveParticles();
1011
+ node.properties._particleCount = system.getActiveCount();
1012
+ }
1013
+ }
1014
+ };
1015
+ function getNodeParticleSystem(nodeId) {
1016
+ return nodeSystems.get(nodeId);
1017
+ }
1018
+
1019
+ export {
1020
+ ParticleSystem,
1021
+ gravity,
1022
+ wind,
1023
+ turbulence,
1024
+ drag,
1025
+ attractor,
1026
+ vortex,
1027
+ floorBounce,
1028
+ sizeOscillate,
1029
+ ParticlePresets,
1030
+ ParticleAttractorSystem,
1031
+ ParticleEmitter,
1032
+ ParticleCollisionSystem,
1033
+ ParticleForceSystem,
1034
+ ParticleTurbulence,
1035
+ particleTraitHandler,
1036
+ getNodeParticleSystem,
1037
+ particles_exports
1038
+ };