@nice2dev/ui-3d 1.0.0 → 1.0.3

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 (204) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/dist/cjs/collaborative/collaborativeScene.js +210 -0
  3. package/dist/cjs/collaborative/collaborativeScene.js.map +1 -0
  4. package/dist/cjs/core/i18n.js +3 -3
  5. package/dist/cjs/core/i18n.js.map +1 -1
  6. package/dist/cjs/dance/DanceBridge.js +162 -0
  7. package/dist/cjs/dance/DanceBridge.js.map +1 -0
  8. package/dist/cjs/dance/DanceScoreEngine.js +210 -0
  9. package/dist/cjs/dance/DanceScoreEngine.js.map +1 -0
  10. package/dist/cjs/dance/PoseDetector.js +199 -0
  11. package/dist/cjs/dance/PoseDetector.js.map +1 -0
  12. package/dist/cjs/index.js +254 -0
  13. package/dist/cjs/index.js.map +1 -1
  14. package/dist/cjs/material/MaterialEditor.module.css.js +6 -0
  15. package/dist/cjs/material/MaterialEditor.module.css.js.map +1 -0
  16. package/dist/cjs/material/NiceMaterialEditor.js +737 -0
  17. package/dist/cjs/material/NiceMaterialEditor.js.map +1 -0
  18. package/dist/cjs/material/materialEditorTypes.js +73 -0
  19. package/dist/cjs/material/materialEditorTypes.js.map +1 -0
  20. package/dist/cjs/material/materialEditorUtils.js +841 -0
  21. package/dist/cjs/material/materialEditorUtils.js.map +1 -0
  22. package/dist/cjs/material/materialNodeDefinitions.js +1285 -0
  23. package/dist/cjs/material/materialNodeDefinitions.js.map +1 -0
  24. package/dist/cjs/model/ModelEditor.js +4 -1
  25. package/dist/cjs/model/ModelEditor.js.map +1 -1
  26. package/dist/cjs/model/ModelEditor.module.css.js +1 -1
  27. package/dist/cjs/model/ModelEditorLeftPanel.js +5 -4
  28. package/dist/cjs/model/ModelEditorLeftPanel.js.map +1 -1
  29. package/dist/cjs/model/ModelEditorMenuBar.js +8 -3
  30. package/dist/cjs/model/ModelEditorMenuBar.js.map +1 -1
  31. package/dist/cjs/model/ModelEditorRightPanel.js +27 -26
  32. package/dist/cjs/model/ModelEditorRightPanel.js.map +1 -1
  33. package/dist/cjs/model/ModelEditorSubComponents.js +20 -16
  34. package/dist/cjs/model/ModelEditorSubComponents.js.map +1 -1
  35. package/dist/cjs/model/ModelEditorTimeline.js +5 -4
  36. package/dist/cjs/model/ModelEditorTimeline.js.map +1 -1
  37. package/dist/cjs/model/ModelEditorToolbar.js +4 -3
  38. package/dist/cjs/model/ModelEditorToolbar.js.map +1 -1
  39. package/dist/cjs/model/ModelEditorViewport.js +2 -2
  40. package/dist/cjs/model/ModelEditorViewport.js.map +1 -1
  41. package/dist/cjs/model/ModelViewer.js +68 -0
  42. package/dist/cjs/model/ModelViewer.js.map +1 -0
  43. package/dist/cjs/model/ModelViewer.module.css.js +6 -0
  44. package/dist/cjs/model/ModelViewer.module.css.js.map +1 -0
  45. package/dist/cjs/model/NiceArmatureEditor.js +255 -0
  46. package/dist/cjs/model/NiceArmatureEditor.js.map +1 -0
  47. package/dist/cjs/model/NiceMorphTargetEditor.js +206 -0
  48. package/dist/cjs/model/NiceMorphTargetEditor.js.map +1 -0
  49. package/dist/cjs/model/NiceOctree.js +339 -0
  50. package/dist/cjs/model/NiceOctree.js.map +1 -0
  51. package/dist/cjs/model/NicePhysicsSimulation.js +283 -0
  52. package/dist/cjs/model/NicePhysicsSimulation.js.map +1 -0
  53. package/dist/cjs/model/NiceProceduralGeometry.js +269 -0
  54. package/dist/cjs/model/NiceProceduralGeometry.js.map +1 -0
  55. package/dist/cjs/model/NiceTerrainEditor.js +343 -0
  56. package/dist/cjs/model/NiceTerrainEditor.js.map +1 -0
  57. package/dist/cjs/model/NiceWeightPainter.js +258 -0
  58. package/dist/cjs/model/NiceWeightPainter.js.map +1 -0
  59. package/dist/cjs/model/NiceXRPreview.js +269 -0
  60. package/dist/cjs/model/NiceXRPreview.js.map +1 -0
  61. package/dist/cjs/model/cadModeUtils.js +130 -0
  62. package/dist/cjs/model/cadModeUtils.js.map +1 -0
  63. package/dist/cjs/model/editorShortcuts.js +187 -0
  64. package/dist/cjs/model/editorShortcuts.js.map +1 -0
  65. package/dist/cjs/model/modelEditorTypes.js +11 -0
  66. package/dist/cjs/model/modelEditorTypes.js.map +1 -1
  67. package/dist/cjs/model/modelEditorUtils.js +1049 -0
  68. package/dist/cjs/model/modelEditorUtils.js.map +1 -0
  69. package/dist/cjs/model/simsModeUtils.js +358 -0
  70. package/dist/cjs/model/simsModeUtils.js.map +1 -0
  71. package/dist/cjs/model/useModelEditor.js +319 -115
  72. package/dist/cjs/model/useModelEditor.js.map +1 -1
  73. package/dist/cjs/model/useModelViewer.js +634 -0
  74. package/dist/cjs/model/useModelViewer.js.map +1 -0
  75. package/dist/cjs/nice2dev-ui-3d.css +1 -1
  76. package/dist/cjs/particle/NiceParticleEditor.js +526 -0
  77. package/dist/cjs/particle/NiceParticleEditor.js.map +1 -0
  78. package/dist/cjs/particle/ParticleEditor.module.css.js +6 -0
  79. package/dist/cjs/particle/ParticleEditor.module.css.js.map +1 -0
  80. package/dist/cjs/particle/particleEditorTypes.js +92 -0
  81. package/dist/cjs/particle/particleEditorTypes.js.map +1 -0
  82. package/dist/cjs/particle/particleEditorUtils.js +1084 -0
  83. package/dist/cjs/particle/particleEditorUtils.js.map +1 -0
  84. package/dist/cjs/rendering/NiceCascadedShadows.js +266 -0
  85. package/dist/cjs/rendering/NiceCascadedShadows.js.map +1 -0
  86. package/dist/cjs/rendering/NiceRenderExport.js +341 -0
  87. package/dist/cjs/rendering/NiceRenderExport.js.map +1 -0
  88. package/dist/cjs/rendering/NiceSSAO.js +359 -0
  89. package/dist/cjs/rendering/NiceSSAO.js.map +1 -0
  90. package/dist/cjs/rendering/NiceSSR.js +277 -0
  91. package/dist/cjs/rendering/NiceSSR.js.map +1 -0
  92. package/dist/cjs/rendering/NiceWebGPURenderer.js +215 -0
  93. package/dist/cjs/rendering/NiceWebGPURenderer.js.map +1 -0
  94. package/dist/cjs/ui/dist/index.js +50089 -0
  95. package/dist/cjs/ui/dist/index.js.map +1 -0
  96. package/dist/cjs/uv/NiceUVEditor.js +520 -0
  97. package/dist/cjs/uv/NiceUVEditor.js.map +1 -0
  98. package/dist/cjs/uv/UVEditor.module.css.js +6 -0
  99. package/dist/cjs/uv/UVEditor.module.css.js.map +1 -0
  100. package/dist/cjs/uv/uvEditorTypes.js +98 -0
  101. package/dist/cjs/uv/uvEditorTypes.js.map +1 -0
  102. package/dist/cjs/uv/uvEditorUtils.js +670 -0
  103. package/dist/cjs/uv/uvEditorUtils.js.map +1 -0
  104. package/dist/esm/collaborative/collaborativeScene.js +206 -0
  105. package/dist/esm/collaborative/collaborativeScene.js.map +1 -0
  106. package/dist/esm/dance/DanceBridge.js +158 -0
  107. package/dist/esm/dance/DanceBridge.js.map +1 -0
  108. package/dist/esm/dance/DanceScoreEngine.js +207 -0
  109. package/dist/esm/dance/DanceScoreEngine.js.map +1 -0
  110. package/dist/esm/dance/PoseDetector.js +195 -0
  111. package/dist/esm/dance/PoseDetector.js.map +1 -0
  112. package/dist/esm/index.js +35 -1
  113. package/dist/esm/index.js.map +1 -1
  114. package/dist/esm/material/MaterialEditor.module.css.js +4 -0
  115. package/dist/esm/material/MaterialEditor.module.css.js.map +1 -0
  116. package/dist/esm/material/NiceMaterialEditor.js +734 -0
  117. package/dist/esm/material/NiceMaterialEditor.js.map +1 -0
  118. package/dist/esm/material/materialEditorTypes.js +62 -0
  119. package/dist/esm/material/materialEditorTypes.js.map +1 -0
  120. package/dist/esm/material/materialEditorUtils.js +811 -0
  121. package/dist/esm/material/materialEditorUtils.js.map +1 -0
  122. package/dist/esm/material/materialNodeDefinitions.js +1280 -0
  123. package/dist/esm/material/materialNodeDefinitions.js.map +1 -0
  124. package/dist/esm/model/ModelEditor.js +4 -2
  125. package/dist/esm/model/ModelEditor.js.map +1 -1
  126. package/dist/esm/model/ModelEditor.module.css.js +1 -1
  127. package/dist/esm/model/ModelEditorLeftPanel.js +5 -4
  128. package/dist/esm/model/ModelEditorLeftPanel.js.map +1 -1
  129. package/dist/esm/model/ModelEditorMenuBar.js +8 -3
  130. package/dist/esm/model/ModelEditorMenuBar.js.map +1 -1
  131. package/dist/esm/model/ModelEditorRightPanel.js +27 -26
  132. package/dist/esm/model/ModelEditorRightPanel.js.map +1 -1
  133. package/dist/esm/model/ModelEditorSubComponents.js +17 -13
  134. package/dist/esm/model/ModelEditorSubComponents.js.map +1 -1
  135. package/dist/esm/model/ModelEditorTimeline.js +5 -4
  136. package/dist/esm/model/ModelEditorTimeline.js.map +1 -1
  137. package/dist/esm/model/ModelEditorToolbar.js +4 -3
  138. package/dist/esm/model/ModelEditorToolbar.js.map +1 -1
  139. package/dist/esm/model/ModelEditorViewport.js +2 -2
  140. package/dist/esm/model/ModelEditorViewport.js.map +1 -1
  141. package/dist/esm/model/ModelViewer.js +65 -0
  142. package/dist/esm/model/ModelViewer.js.map +1 -0
  143. package/dist/esm/model/ModelViewer.module.css.js +4 -0
  144. package/dist/esm/model/ModelViewer.module.css.js.map +1 -0
  145. package/dist/esm/model/NiceArmatureEditor.js +233 -0
  146. package/dist/esm/model/NiceArmatureEditor.js.map +1 -0
  147. package/dist/esm/model/NiceMorphTargetEditor.js +184 -0
  148. package/dist/esm/model/NiceMorphTargetEditor.js.map +1 -0
  149. package/dist/esm/model/NiceOctree.js +317 -0
  150. package/dist/esm/model/NiceOctree.js.map +1 -0
  151. package/dist/esm/model/NicePhysicsSimulation.js +261 -0
  152. package/dist/esm/model/NicePhysicsSimulation.js.map +1 -0
  153. package/dist/esm/model/NiceProceduralGeometry.js +242 -0
  154. package/dist/esm/model/NiceProceduralGeometry.js.map +1 -0
  155. package/dist/esm/model/NiceTerrainEditor.js +321 -0
  156. package/dist/esm/model/NiceTerrainEditor.js.map +1 -0
  157. package/dist/esm/model/NiceWeightPainter.js +236 -0
  158. package/dist/esm/model/NiceWeightPainter.js.map +1 -0
  159. package/dist/esm/model/NiceXRPreview.js +247 -0
  160. package/dist/esm/model/NiceXRPreview.js.map +1 -0
  161. package/dist/esm/model/cadModeUtils.js +103 -0
  162. package/dist/esm/model/cadModeUtils.js.map +1 -0
  163. package/dist/esm/model/editorShortcuts.js +185 -0
  164. package/dist/esm/model/editorShortcuts.js.map +1 -0
  165. package/dist/esm/model/modelEditorTypes.js +11 -0
  166. package/dist/esm/model/modelEditorTypes.js.map +1 -1
  167. package/dist/esm/model/modelEditorUtils.js +997 -0
  168. package/dist/esm/model/modelEditorUtils.js.map +1 -0
  169. package/dist/esm/model/simsModeUtils.js +325 -0
  170. package/dist/esm/model/simsModeUtils.js.map +1 -0
  171. package/dist/esm/model/useModelEditor.js +204 -0
  172. package/dist/esm/model/useModelEditor.js.map +1 -1
  173. package/dist/esm/model/useModelViewer.js +613 -0
  174. package/dist/esm/model/useModelViewer.js.map +1 -0
  175. package/dist/esm/nice2dev-ui-3d.css +1 -1
  176. package/dist/esm/particle/NiceParticleEditor.js +523 -0
  177. package/dist/esm/particle/NiceParticleEditor.js.map +1 -0
  178. package/dist/esm/particle/ParticleEditor.module.css.js +4 -0
  179. package/dist/esm/particle/ParticleEditor.module.css.js.map +1 -0
  180. package/dist/esm/particle/particleEditorTypes.js +84 -0
  181. package/dist/esm/particle/particleEditorTypes.js.map +1 -0
  182. package/dist/esm/particle/particleEditorUtils.js +1054 -0
  183. package/dist/esm/particle/particleEditorUtils.js.map +1 -0
  184. package/dist/esm/rendering/NiceCascadedShadows.js +244 -0
  185. package/dist/esm/rendering/NiceCascadedShadows.js.map +1 -0
  186. package/dist/esm/rendering/NiceRenderExport.js +319 -0
  187. package/dist/esm/rendering/NiceRenderExport.js.map +1 -0
  188. package/dist/esm/rendering/NiceSSAO.js +337 -0
  189. package/dist/esm/rendering/NiceSSAO.js.map +1 -0
  190. package/dist/esm/rendering/NiceSSR.js +255 -0
  191. package/dist/esm/rendering/NiceSSR.js.map +1 -0
  192. package/dist/esm/rendering/NiceWebGPURenderer.js +193 -0
  193. package/dist/esm/rendering/NiceWebGPURenderer.js.map +1 -0
  194. package/dist/esm/ui/dist/index.js +49686 -0
  195. package/dist/esm/ui/dist/index.js.map +1 -0
  196. package/dist/esm/uv/NiceUVEditor.js +518 -0
  197. package/dist/esm/uv/NiceUVEditor.js.map +1 -0
  198. package/dist/esm/uv/UVEditor.module.css.js +4 -0
  199. package/dist/esm/uv/UVEditor.module.css.js.map +1 -0
  200. package/dist/esm/uv/uvEditorTypes.js +88 -0
  201. package/dist/esm/uv/uvEditorTypes.js.map +1 -0
  202. package/dist/esm/uv/uvEditorUtils.js +621 -0
  203. package/dist/esm/uv/uvEditorUtils.js.map +1 -0
  204. package/package.json +3 -4
@@ -0,0 +1,1054 @@
1
+ import { DEFAULT_CURVE, DEFAULT_GRADIENT } from './particleEditorTypes.js';
2
+
3
+ /* ════════════════════════════════════════════════════════════════════════════
4
+ Particle System Editor — Utility Functions
5
+
6
+ Core utilities for particle system creation, manipulation, and simulation
7
+ ════════════════════════════════════════════════════════════════════════════ */
8
+ // ─── ID Generation ───────────────────────────────────────────────────────────
9
+ let systemIdCounter = 0;
10
+ let forceIdCounter = 0;
11
+ let subEmitterIdCounter = 0;
12
+ function generateSystemId() {
13
+ return `system_${++systemIdCounter}_${Date.now()}`;
14
+ }
15
+ function generateForceId() {
16
+ return `force_${++forceIdCounter}_${Date.now()}`;
17
+ }
18
+ function generateSubEmitterId() {
19
+ return `subemitter_${++subEmitterIdCounter}_${Date.now()}`;
20
+ }
21
+ // ─── Distribution Operations ─────────────────────────────────────────────────
22
+ function evaluateDistribution(dist, t, seed = Math.random()) {
23
+ var _a, _b;
24
+ switch (dist.type) {
25
+ case 'constant':
26
+ return dist.value;
27
+ case 'random':
28
+ return dist.min + seed * (dist.max - dist.min);
29
+ case 'curve':
30
+ return evaluateCurve(dist.curve, t) * ((_a = dist.multiplier) !== null && _a !== void 0 ? _a : 1);
31
+ case 'random_curve': {
32
+ const min = evaluateCurve(dist.curveMin, t);
33
+ const max = evaluateCurve(dist.curveMax, t);
34
+ return (min + seed * (max - min)) * ((_b = dist.multiplier) !== null && _b !== void 0 ? _b : 1);
35
+ }
36
+ default:
37
+ return 1;
38
+ }
39
+ }
40
+ function evaluateCurve(curve, t) {
41
+ var _a, _b;
42
+ if (curve.length === 0)
43
+ return 0;
44
+ if (curve.length === 1)
45
+ return curve[0].value;
46
+ // Clamp t to [0, 1]
47
+ t = Math.max(0, Math.min(1, t));
48
+ // Find surrounding keyframes
49
+ let leftIdx = 0;
50
+ let rightIdx = curve.length - 1;
51
+ for (let i = 0; i < curve.length; i++) {
52
+ if (curve[i].time <= t)
53
+ leftIdx = i;
54
+ if (curve[i].time >= t && i < rightIdx)
55
+ rightIdx = i;
56
+ }
57
+ if (leftIdx === rightIdx)
58
+ return curve[leftIdx].value;
59
+ const left = curve[leftIdx];
60
+ const right = curve[rightIdx];
61
+ // Hermite interpolation
62
+ const dt = right.time - left.time;
63
+ const localT = (t - left.time) / dt;
64
+ const outTangent = (_a = left.outTangent) !== null && _a !== void 0 ? _a : 0;
65
+ const inTangent = (_b = right.inTangent) !== null && _b !== void 0 ? _b : 0;
66
+ const t2 = localT * localT;
67
+ const t3 = t2 * localT;
68
+ const h00 = 2 * t3 - 3 * t2 + 1;
69
+ const h10 = t3 - 2 * t2 + localT;
70
+ const h01 = -2 * t3 + 3 * t2;
71
+ const h11 = t3 - t2;
72
+ return h00 * left.value + h10 * dt * outTangent + h01 * right.value + h11 * dt * inTangent;
73
+ }
74
+ function evaluateVec3Distribution(dist, t, seed) {
75
+ return {
76
+ x: evaluateDistribution(dist.x, t, seed.x),
77
+ y: evaluateDistribution(dist.y, t, seed.y),
78
+ z: evaluateDistribution(dist.z, t, seed.z),
79
+ };
80
+ }
81
+ function evaluateGradient(gradient, t) {
82
+ // Interpolate color
83
+ let color = { r: 1, g: 1, b: 1 };
84
+ const colorKeys = gradient.colorKeys;
85
+ if (colorKeys.length > 0) {
86
+ let leftIdx = 0;
87
+ let rightIdx = colorKeys.length - 1;
88
+ for (let i = 0; i < colorKeys.length; i++) {
89
+ if (colorKeys[i].time <= t)
90
+ leftIdx = i;
91
+ if (colorKeys[i].time >= t && i < rightIdx)
92
+ rightIdx = i;
93
+ }
94
+ if (leftIdx === rightIdx) {
95
+ color = { ...colorKeys[leftIdx].color };
96
+ }
97
+ else {
98
+ const left = colorKeys[leftIdx];
99
+ const right = colorKeys[rightIdx];
100
+ const localT = (t - left.time) / (right.time - left.time);
101
+ color = {
102
+ r: left.color.r + localT * (right.color.r - left.color.r),
103
+ g: left.color.g + localT * (right.color.g - left.color.g),
104
+ b: left.color.b + localT * (right.color.b - left.color.b),
105
+ };
106
+ }
107
+ }
108
+ // Interpolate alpha
109
+ let alpha = 1;
110
+ const alphaKeys = gradient.alphaKeys;
111
+ if (alphaKeys.length > 0) {
112
+ let leftIdx = 0;
113
+ let rightIdx = alphaKeys.length - 1;
114
+ for (let i = 0; i < alphaKeys.length; i++) {
115
+ if (alphaKeys[i].time <= t)
116
+ leftIdx = i;
117
+ if (alphaKeys[i].time >= t && i < rightIdx)
118
+ rightIdx = i;
119
+ }
120
+ if (leftIdx === rightIdx) {
121
+ alpha = alphaKeys[leftIdx].alpha;
122
+ }
123
+ else {
124
+ const left = alphaKeys[leftIdx];
125
+ const right = alphaKeys[rightIdx];
126
+ const localT = (t - left.time) / (right.time - left.time);
127
+ alpha = left.alpha + localT * (right.alpha - left.alpha);
128
+ }
129
+ }
130
+ return { ...color, a: alpha };
131
+ }
132
+ // ─── Vector Operations ───────────────────────────────────────────────────────
133
+ function vec3Add(a, b) {
134
+ return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z };
135
+ }
136
+ function vec3Sub(a, b) {
137
+ return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
138
+ }
139
+ function vec3Mul(a, s) {
140
+ return { x: a.x * s, y: a.y * s, z: a.z * s };
141
+ }
142
+ function vec3MulVec(a, b) {
143
+ return { x: a.x * b.x, y: a.y * b.y, z: a.z * b.z };
144
+ }
145
+ function vec3Dot(a, b) {
146
+ return a.x * b.x + a.y * b.y + a.z * b.z;
147
+ }
148
+ function vec3Cross(a, b) {
149
+ return {
150
+ x: a.y * b.z - a.z * b.y,
151
+ y: a.z * b.x - a.x * b.z,
152
+ z: a.x * b.y - a.y * b.x,
153
+ };
154
+ }
155
+ function vec3Length(v) {
156
+ return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
157
+ }
158
+ function vec3Normalize(v) {
159
+ const len = vec3Length(v);
160
+ if (len === 0)
161
+ return { x: 0, y: 0, z: 0 };
162
+ return { x: v.x / len, y: v.y / len, z: v.z / len };
163
+ }
164
+ function vec3Lerp(a, b, t) {
165
+ return {
166
+ x: a.x + t * (b.x - a.x),
167
+ y: a.y + t * (b.y - a.y),
168
+ z: a.z + t * (b.z - a.z),
169
+ };
170
+ }
171
+ function vec3Random() {
172
+ return {
173
+ x: Math.random() * 2 - 1,
174
+ y: Math.random() * 2 - 1,
175
+ z: Math.random() * 2 - 1,
176
+ };
177
+ }
178
+ function vec3RandomOnSphere() {
179
+ const theta = Math.random() * Math.PI * 2;
180
+ const phi = Math.acos(Math.random() * 2 - 1);
181
+ return {
182
+ x: Math.sin(phi) * Math.cos(theta),
183
+ y: Math.sin(phi) * Math.sin(theta),
184
+ z: Math.cos(phi),
185
+ };
186
+ }
187
+ // ─── Random / Noise ──────────────────────────────────────────────────────────
188
+ function seededRandom(seed) {
189
+ const x = Math.sin(seed * 12.9898 + seed * 78.233) * 43758.5453;
190
+ return x - Math.floor(x);
191
+ }
192
+ function perlinNoise2D(x, y) {
193
+ // Simplified 2D Perlin noise
194
+ const xi = Math.floor(x);
195
+ const yi = Math.floor(y);
196
+ const xf = x - xi;
197
+ const yf = y - yi;
198
+ const fade = (t) => t * t * t * (t * (t * 6 - 15) + 10);
199
+ const grad = (hash, x, y) => {
200
+ const h = hash & 3;
201
+ const u = h < 2 ? x : y;
202
+ const v = h < 2 ? y : x;
203
+ return ((h & 1) ? -u : u) + ((h & 2) ? -2 * v : 2 * v);
204
+ };
205
+ const hash = (x, y) => {
206
+ const n = x * 374761393 + y * 668265263;
207
+ return ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) >>> 0;
208
+ };
209
+ const u = fade(xf);
210
+ const v = fade(yf);
211
+ const aa = hash(xi, yi);
212
+ const ab = hash(xi, yi + 1);
213
+ const ba = hash(xi + 1, yi);
214
+ const bb = hash(xi + 1, yi + 1);
215
+ const x1 = grad(aa, xf, yf) * (1 - u) + grad(ba, xf - 1, yf) * u;
216
+ const x2 = grad(ab, xf, yf - 1) * (1 - u) + grad(bb, xf - 1, yf - 1) * u;
217
+ return (x1 * (1 - v) + x2 * v + 1) / 2;
218
+ }
219
+ function fbmNoise(x, y, z, octaves, roughness) {
220
+ let value = 0;
221
+ let amplitude = 1;
222
+ let frequency = 1;
223
+ let maxValue = 0;
224
+ for (let i = 0; i < octaves; i++) {
225
+ value += amplitude * perlinNoise2D(x * frequency + z * 0.5, y * frequency);
226
+ maxValue += amplitude;
227
+ amplitude *= roughness;
228
+ frequency *= 2;
229
+ }
230
+ return value / maxValue;
231
+ }
232
+ // ─── Emitter Shape Sampling ──────────────────────────────────────────────────
233
+ function sampleEmitterShape(shape, seed) {
234
+ let position = { x: 0, y: 0, z: 0 };
235
+ let direction = { x: 0, y: 1, z: 0 };
236
+ switch (shape.type) {
237
+ case 'point':
238
+ position = { x: 0, y: 0, z: 0 };
239
+ direction = shape.randomDirection ? vec3RandomOnSphere() : { x: 0, y: 1, z: 0 };
240
+ break;
241
+ case 'sphere': {
242
+ const radius = evaluateDistribution(shape.radius, 0, seed.x);
243
+ const innerRadius = radius * (1 - shape.radiusThickness);
244
+ const r = shape.emitFromShell
245
+ ? radius
246
+ : innerRadius + seed.y * (radius - innerRadius);
247
+ const arc = (shape.arc * Math.PI) / 180;
248
+ const theta = seed.z * arc;
249
+ const phi = Math.acos(seed.x * 2 - 1);
250
+ position = {
251
+ x: r * Math.sin(phi) * Math.cos(theta),
252
+ y: r * Math.sin(phi) * Math.sin(theta),
253
+ z: r * Math.cos(phi),
254
+ };
255
+ direction = shape.sphericalDirection ? vec3Normalize(position) : { x: 0, y: 1, z: 0 };
256
+ break;
257
+ }
258
+ case 'hemisphere': {
259
+ const radius = evaluateDistribution(shape.radius, 0, seed.x);
260
+ const innerRadius = radius * (1 - shape.radiusThickness);
261
+ const r = shape.emitFromShell
262
+ ? radius
263
+ : innerRadius + seed.y * (radius - innerRadius);
264
+ const arc = (shape.arc * Math.PI) / 180;
265
+ const theta = seed.z * arc;
266
+ const phi = Math.acos(seed.x); // Only upper hemisphere
267
+ position = {
268
+ x: r * Math.sin(phi) * Math.cos(theta),
269
+ y: r * Math.cos(phi),
270
+ z: r * Math.sin(phi) * Math.sin(theta),
271
+ };
272
+ direction = shape.sphericalDirection ? vec3Normalize(position) : { x: 0, y: 1, z: 0 };
273
+ break;
274
+ }
275
+ case 'box': {
276
+ if (shape.emitFrom === 'volume') {
277
+ position = {
278
+ x: (seed.x - 0.5) * shape.size.x,
279
+ y: (seed.y - 0.5) * shape.size.y,
280
+ z: (seed.z - 0.5) * shape.size.z,
281
+ };
282
+ }
283
+ else if (shape.emitFrom === 'surface') {
284
+ const face = Math.floor(seed.x * 6);
285
+ const u = seed.y - 0.5;
286
+ const v = seed.z - 0.5;
287
+ switch (face) {
288
+ case 0:
289
+ position = { x: shape.size.x / 2, y: u * shape.size.y, z: v * shape.size.z };
290
+ break;
291
+ case 1:
292
+ position = { x: -shape.size.x / 2, y: u * shape.size.y, z: v * shape.size.z };
293
+ break;
294
+ case 2:
295
+ position = { x: u * shape.size.x, y: shape.size.y / 2, z: v * shape.size.z };
296
+ break;
297
+ case 3:
298
+ position = { x: u * shape.size.x, y: -shape.size.y / 2, z: v * shape.size.z };
299
+ break;
300
+ case 4:
301
+ position = { x: u * shape.size.x, y: v * shape.size.y, z: shape.size.z / 2 };
302
+ break;
303
+ default: position = { x: u * shape.size.x, y: v * shape.size.y, z: -shape.size.z / 2 };
304
+ }
305
+ }
306
+ direction = shape.randomDirection ? vec3RandomOnSphere() : { x: 0, y: 1, z: 0 };
307
+ break;
308
+ }
309
+ case 'cone': {
310
+ const radius = evaluateDistribution(shape.radius, 0, seed.x);
311
+ const arc = (shape.arc * Math.PI) / 180;
312
+ const theta = seed.z * arc;
313
+ const angle = (shape.angle * Math.PI) / 180;
314
+ const r = radius * seed.y;
315
+ const h = seed.x * shape.length;
316
+ const coneRadius = r + h * Math.tan(angle);
317
+ position = {
318
+ x: coneRadius * Math.cos(theta),
319
+ y: h,
320
+ z: coneRadius * Math.sin(theta),
321
+ };
322
+ direction = vec3Normalize({
323
+ x: Math.cos(theta) * Math.sin(angle),
324
+ y: Math.cos(angle),
325
+ z: Math.sin(theta) * Math.sin(angle),
326
+ });
327
+ break;
328
+ }
329
+ case 'circle': {
330
+ const radius = evaluateDistribution(shape.radius, 0, seed.x);
331
+ const innerRadius = radius * (1 - shape.radiusThickness);
332
+ const r = shape.emitFromShell ? radius : innerRadius + seed.y * (radius - innerRadius);
333
+ const arc = (shape.arc * Math.PI) / 180;
334
+ const theta = seed.z * arc;
335
+ position = {
336
+ x: r * Math.cos(theta),
337
+ y: 0,
338
+ z: r * Math.sin(theta),
339
+ };
340
+ direction = shape.randomDirection
341
+ ? vec3Normalize({ x: Math.random() - 0.5, y: 1, z: Math.random() - 0.5 })
342
+ : { x: 0, y: 1, z: 0 };
343
+ break;
344
+ }
345
+ case 'edge':
346
+ position = {
347
+ x: (seed.x - 0.5) * shape.length,
348
+ y: 0,
349
+ z: 0,
350
+ };
351
+ direction = { x: 0, y: 1, z: 0 };
352
+ break;
353
+ case 'rectangle':
354
+ position = {
355
+ x: (seed.x - 0.5) * shape.size.x,
356
+ y: 0,
357
+ z: (seed.z - 0.5) * shape.size.y,
358
+ };
359
+ direction = shape.randomDirection ? vec3RandomOnSphere() : { x: 0, y: 1, z: 0 };
360
+ break;
361
+ }
362
+ return { position, direction };
363
+ }
364
+ // ─── Force Application ───────────────────────────────────────────────────────
365
+ function applyForce(particle, force, dt, time) {
366
+ if (!force.enabled)
367
+ return { x: 0, y: 0, z: 0 };
368
+ const t = particle.age / particle.startLifetime;
369
+ const strength = evaluateDistribution(force.strength, t, particle.randomSeed);
370
+ switch (force.type) {
371
+ case 'gravity':
372
+ return vec3Mul(force.direction, strength * dt);
373
+ case 'wind': {
374
+ const turbulenceOffset = perlinNoise2D(particle.position.x * force.frequency + time, particle.position.z * force.frequency) * force.turbulence;
375
+ const windDir = vec3Add(force.direction, { x: turbulenceOffset, y: 0, z: turbulenceOffset });
376
+ return vec3Mul(vec3Normalize(windDir), strength * dt);
377
+ }
378
+ case 'vortex': {
379
+ const toCenter = vec3Sub(force.position, particle.position);
380
+ const dist = vec3Length(toCenter);
381
+ if (dist < 0.001 || dist > force.size)
382
+ return { x: 0, y: 0, z: 0 };
383
+ const tangent = vec3Normalize(vec3Cross(force.axis, toCenter));
384
+ const speed = evaluateDistribution(force.speed, t, particle.randomSeed);
385
+ const pull = evaluateDistribution(force.pullStrength, t, particle.randomSeed);
386
+ const tangentForce = vec3Mul(tangent, speed * strength * dt);
387
+ const pullForce = vec3Mul(vec3Normalize(toCenter), pull * strength * dt);
388
+ return vec3Add(tangentForce, pullForce);
389
+ }
390
+ case 'turbulence': {
391
+ const freqX = force.frequency.x;
392
+ const freqY = force.frequency.y;
393
+ const freqZ = force.frequency.z;
394
+ const scrollX = time * force.scrollSpeed.x;
395
+ const scrollY = time * force.scrollSpeed.y;
396
+ const scrollZ = time * force.scrollSpeed.z;
397
+ const noiseX = fbmNoise(particle.position.x * freqX + scrollX, particle.position.y * freqY, particle.position.z * freqZ, force.octaves, force.roughness) * 2 - 1;
398
+ const noiseY = fbmNoise(particle.position.x * freqX, particle.position.y * freqY + scrollY + 100, particle.position.z * freqZ, force.octaves, force.roughness) * 2 - 1;
399
+ const noiseZ = fbmNoise(particle.position.x * freqX, particle.position.y * freqY, particle.position.z * freqZ + scrollZ + 200, force.octaves, force.roughness) * 2 - 1;
400
+ return {
401
+ x: noiseX * evaluateDistribution(force.strengthX, t, particle.randomSeed) * dt,
402
+ y: noiseY * evaluateDistribution(force.strengthY, t, particle.randomSeed) * dt,
403
+ z: noiseZ * evaluateDistribution(force.strengthZ, t, particle.randomSeed) * dt,
404
+ };
405
+ }
406
+ case 'attractor': {
407
+ const toAttractor = vec3Sub(force.position, particle.position);
408
+ const dist = vec3Length(toAttractor);
409
+ if (dist < 0.001) {
410
+ if (force.killParticles) {
411
+ particle.age = particle.startLifetime; // Kill particle
412
+ }
413
+ return { x: 0, y: 0, z: 0 };
414
+ }
415
+ if (dist > force.radius)
416
+ return { x: 0, y: 0, z: 0 };
417
+ let falloff = 1;
418
+ switch (force.falloff) {
419
+ case 'linear':
420
+ falloff = 1 - dist / force.radius;
421
+ break;
422
+ case 'quadratic':
423
+ falloff = Math.pow(1 - dist / force.radius, 2);
424
+ break;
425
+ }
426
+ return vec3Mul(vec3Normalize(toAttractor), strength * falloff * dt);
427
+ }
428
+ case 'drag': {
429
+ const drag = evaluateDistribution(force.linearDrag, t, particle.randomSeed);
430
+ let multiplier = 1;
431
+ if (force.multiplyBySize) {
432
+ const size = (particle.size.x + particle.size.y + particle.size.z) / 3;
433
+ multiplier *= size;
434
+ }
435
+ if (force.multiplyByVelocity) {
436
+ multiplier *= vec3Length(particle.velocity);
437
+ }
438
+ return vec3Mul(particle.velocity, -drag * multiplier * dt);
439
+ }
440
+ default:
441
+ return { x: 0, y: 0, z: 0 };
442
+ }
443
+ }
444
+ // ─── System Creation ─────────────────────────────────────────────────────────
445
+ function createDefaultSystem(name = 'New Particle System') {
446
+ return {
447
+ id: generateSystemId(),
448
+ name,
449
+ version: 1,
450
+ // Main
451
+ duration: 5,
452
+ looping: true,
453
+ prewarm: false,
454
+ startDelay: { type: 'constant', value: 0 },
455
+ startLifetime: { type: 'random', min: 3, max: 5 },
456
+ startSpeed: { type: 'constant', value: 5 },
457
+ startSize3D: false,
458
+ startSize: { type: 'random', min: 0.5, max: 1 },
459
+ startRotation3D: false,
460
+ startRotation: { type: 'constant', value: 0 },
461
+ flipRotation: 0,
462
+ startColor: {
463
+ colorKeys: [{ time: 0, color: { r: 1, g: 1, b: 1 } }],
464
+ alphaKeys: [{ time: 0, alpha: 1 }],
465
+ },
466
+ gravityModifier: { type: 'constant', value: 0 },
467
+ simulationSpace: 'local',
468
+ simulationSpeed: 1,
469
+ deltaTimeType: 'scaled',
470
+ scalingMode: 'hierarchy',
471
+ playOnAwake: true,
472
+ // Emission
473
+ emission: {
474
+ enabled: true,
475
+ rateOverTime: { type: 'constant', value: 10 },
476
+ rateOverDistance: { type: 'constant', value: 0 },
477
+ bursts: [],
478
+ },
479
+ // Shape
480
+ shape: {
481
+ type: 'cone',
482
+ emitFromShell: false,
483
+ randomDirection: false,
484
+ sphericalDirection: false,
485
+ arcMode: 'random',
486
+ arcSpread: 0,
487
+ radius: { type: 'constant', value: 1 },
488
+ angle: 25,
489
+ length: 5,
490
+ radiusThickness: 1,
491
+ arc: 360,
492
+ emitFrom: 'base',
493
+ },
494
+ // Velocity over lifetime
495
+ velocityOverLifetime: {
496
+ enabled: false,
497
+ linear: {
498
+ x: { type: 'constant', value: 0 },
499
+ y: { type: 'constant', value: 0 },
500
+ z: { type: 'constant', value: 0 },
501
+ },
502
+ space: 'local',
503
+ orbital: {
504
+ x: { type: 'constant', value: 0 },
505
+ y: { type: 'constant', value: 0 },
506
+ z: { type: 'constant', value: 0 },
507
+ },
508
+ offset: {
509
+ x: { type: 'constant', value: 0 },
510
+ y: { type: 'constant', value: 0 },
511
+ z: { type: 'constant', value: 0 },
512
+ },
513
+ radial: { type: 'constant', value: 0 },
514
+ speedModifier: { type: 'constant', value: 1 },
515
+ },
516
+ // Limit velocity
517
+ limitVelocityOverLifetime: {
518
+ enabled: false,
519
+ separateAxes: false,
520
+ speed: { type: 'constant', value: 10 },
521
+ dampen: 0.1,
522
+ drag: { type: 'constant', value: 0 },
523
+ multiplyBySize: false,
524
+ space: 'local',
525
+ },
526
+ // Inherit velocity
527
+ inheritVelocity: {
528
+ enabled: false,
529
+ mode: 'initial',
530
+ curve: { type: 'constant', value: 0 },
531
+ },
532
+ // Lifetime by emitter speed
533
+ lifetimeByEmitterSpeed: {
534
+ enabled: false,
535
+ curve: DEFAULT_CURVE,
536
+ speedRange: { min: 0, max: 1 },
537
+ },
538
+ // Force over lifetime
539
+ forceOverLifetime: {
540
+ enabled: false,
541
+ force: {
542
+ x: { type: 'constant', value: 0 },
543
+ y: { type: 'constant', value: 0 },
544
+ z: { type: 'constant', value: 0 },
545
+ },
546
+ space: 'local',
547
+ randomized: false,
548
+ },
549
+ // Color over lifetime
550
+ colorOverLifetime: {
551
+ enabled: true,
552
+ color: {
553
+ colorKeys: [
554
+ { time: 0, color: { r: 1, g: 1, b: 1 } },
555
+ { time: 1, color: { r: 1, g: 1, b: 1 } },
556
+ ],
557
+ alphaKeys: [
558
+ { time: 0, alpha: 1 },
559
+ { time: 0.9, alpha: 1 },
560
+ { time: 1, alpha: 0 },
561
+ ],
562
+ },
563
+ },
564
+ // Color by speed
565
+ colorBySpeed: {
566
+ enabled: false,
567
+ color: DEFAULT_GRADIENT,
568
+ range: { min: 0, max: 1 },
569
+ },
570
+ // Size over lifetime
571
+ sizeOverLifetime: {
572
+ enabled: false,
573
+ separateAxes: false,
574
+ size: {
575
+ type: 'curve',
576
+ curve: [
577
+ { time: 0, value: 1 },
578
+ { time: 1, value: 0 },
579
+ ],
580
+ },
581
+ },
582
+ // Size by speed
583
+ sizeBySpeed: {
584
+ enabled: false,
585
+ separateAxes: false,
586
+ size: { type: 'constant', value: 1 },
587
+ range: { min: 0, max: 1 },
588
+ },
589
+ // Rotation over lifetime
590
+ rotationOverLifetime: {
591
+ enabled: false,
592
+ separateAxes: false,
593
+ angularVelocity: { type: 'constant', value: 0 },
594
+ },
595
+ // Rotation by speed
596
+ rotationBySpeed: {
597
+ enabled: false,
598
+ separateAxes: false,
599
+ angularVelocity: { type: 'constant', value: 0 },
600
+ range: { min: 0, max: 1 },
601
+ },
602
+ // External forces
603
+ externalForces: [],
604
+ // Noise
605
+ noise: {
606
+ enabled: false,
607
+ strength: { type: 'constant', value: 1 },
608
+ frequency: 0.5,
609
+ scrollSpeed: 0,
610
+ damping: true,
611
+ octaves: 1,
612
+ octaveMultiplier: 0.5,
613
+ octaveScale: 2,
614
+ positionAmount: {
615
+ x: { type: 'constant', value: 1 },
616
+ y: { type: 'constant', value: 1 },
617
+ z: { type: 'constant', value: 1 },
618
+ },
619
+ rotationAmount: {
620
+ x: { type: 'constant', value: 0 },
621
+ y: { type: 'constant', value: 0 },
622
+ z: { type: 'constant', value: 0 },
623
+ },
624
+ sizeAmount: { type: 'constant', value: 0 },
625
+ separateAxes: false,
626
+ remap: DEFAULT_CURVE,
627
+ remapEnabled: false,
628
+ },
629
+ // Collision
630
+ collision: {
631
+ enabled: false,
632
+ type: 'world',
633
+ mode: '3d',
634
+ dampen: { type: 'constant', value: 0 },
635
+ bounce: { type: 'constant', value: 1 },
636
+ lifetimeLoss: { type: 'constant', value: 0 },
637
+ minKillSpeed: 0,
638
+ maxKillSpeed: 10000,
639
+ radiusScale: 1,
640
+ sendCollisionMessages: false,
641
+ maxCollisionShapes: 256,
642
+ quality: 'medium',
643
+ },
644
+ // Triggers
645
+ triggers: {
646
+ enabled: false,
647
+ inside: 'ignore',
648
+ outside: 'ignore',
649
+ enter: 'ignore',
650
+ exit: 'ignore',
651
+ radiusScale: 1,
652
+ colliderQueryMode: 'disabled',
653
+ },
654
+ // Sub-emitters
655
+ subEmitters: [],
656
+ // Texture sheet animation
657
+ textureSheetAnimation: {
658
+ enabled: false,
659
+ mode: 'grid',
660
+ tilesX: 1,
661
+ tilesY: 1,
662
+ animation: 'whole_sheet',
663
+ timeMode: 'lifetime',
664
+ cycleCount: 1,
665
+ },
666
+ // Lights
667
+ lights: {
668
+ enabled: false,
669
+ ratio: 1,
670
+ randomDistribution: false,
671
+ useParticleColor: true,
672
+ sizeAffectsRange: false,
673
+ alphaAffectsIntensity: false,
674
+ range: { type: 'constant', value: 1 },
675
+ intensity: { type: 'constant', value: 1 },
676
+ maxLights: 20,
677
+ },
678
+ // Trails
679
+ trails: null,
680
+ // Renderer
681
+ renderer: {
682
+ type: 'billboard',
683
+ sortMode: 'none',
684
+ renderAlignment: 'view',
685
+ },
686
+ // Material
687
+ material: {
688
+ shading: 'unlit',
689
+ blendMode: 'additive',
690
+ colorMode: 'multiply',
691
+ flipBookBlending: false,
692
+ shadowCasting: 'off',
693
+ shadowReceiving: false,
694
+ cullMode: 'off',
695
+ sortingFudge: 0,
696
+ minParticleSize: 0,
697
+ maxParticleSize: 0.5,
698
+ pivot: { x: 0, y: 0, z: 0 },
699
+ customVertexStreams: [],
700
+ },
701
+ maxParticles: 1000,
702
+ gpuInstancing: false,
703
+ };
704
+ }
705
+ // ─── Presets ─────────────────────────────────────────────────────────────────
706
+ const PARTICLE_PRESETS = {
707
+ fire: () => ({
708
+ name: 'Fire',
709
+ duration: 1,
710
+ startLifetime: { type: 'random', min: 0.5, max: 1.5 },
711
+ startSpeed: { type: 'random', min: 1, max: 3 },
712
+ startSize: { type: 'random', min: 0.3, max: 0.8 },
713
+ gravityModifier: { type: 'constant', value: -0.5 },
714
+ emission: {
715
+ enabled: true,
716
+ rateOverTime: { type: 'constant', value: 50 },
717
+ rateOverDistance: { type: 'constant', value: 0 },
718
+ bursts: [],
719
+ },
720
+ colorOverLifetime: {
721
+ enabled: true,
722
+ color: {
723
+ colorKeys: [
724
+ { time: 0, color: { r: 1, g: 0.8, b: 0.2 } },
725
+ { time: 0.5, color: { r: 1, g: 0.4, b: 0 } },
726
+ { time: 1, color: { r: 0.3, g: 0, b: 0 } },
727
+ ],
728
+ alphaKeys: [
729
+ { time: 0, alpha: 0.8 },
730
+ { time: 1, alpha: 0 },
731
+ ],
732
+ },
733
+ },
734
+ sizeOverLifetime: {
735
+ enabled: true,
736
+ separateAxes: false,
737
+ size: { type: 'curve', curve: [{ time: 0, value: 0.5 }, { time: 0.5, value: 1 }, { time: 1, value: 0.2 }] },
738
+ },
739
+ material: {
740
+ shading: 'unlit',
741
+ blendMode: 'additive',
742
+ colorMode: 'multiply',
743
+ flipBookBlending: false,
744
+ shadowCasting: 'off',
745
+ shadowReceiving: false,
746
+ cullMode: 'off',
747
+ sortingFudge: 0,
748
+ minParticleSize: 0,
749
+ maxParticleSize: 0.5,
750
+ pivot: { x: 0, y: 0, z: 0 },
751
+ customVertexStreams: [],
752
+ },
753
+ }),
754
+ smoke: () => ({
755
+ name: 'Smoke',
756
+ duration: 3,
757
+ startLifetime: { type: 'random', min: 3, max: 5 },
758
+ startSpeed: { type: 'random', min: 0.5, max: 1.5 },
759
+ startSize: { type: 'random', min: 0.5, max: 1.5 },
760
+ gravityModifier: { type: 'constant', value: -0.1 },
761
+ emission: {
762
+ enabled: true,
763
+ rateOverTime: { type: 'constant', value: 10 },
764
+ rateOverDistance: { type: 'constant', value: 0 },
765
+ bursts: [],
766
+ },
767
+ colorOverLifetime: {
768
+ enabled: true,
769
+ color: {
770
+ colorKeys: [
771
+ { time: 0, color: { r: 0.5, g: 0.5, b: 0.5 } },
772
+ { time: 1, color: { r: 0.3, g: 0.3, b: 0.3 } },
773
+ ],
774
+ alphaKeys: [
775
+ { time: 0, alpha: 0.4 },
776
+ { time: 0.7, alpha: 0.2 },
777
+ { time: 1, alpha: 0 },
778
+ ],
779
+ },
780
+ },
781
+ sizeOverLifetime: {
782
+ enabled: true,
783
+ separateAxes: false,
784
+ size: { type: 'curve', curve: [{ time: 0, value: 0.5 }, { time: 1, value: 2 }] },
785
+ },
786
+ noise: {
787
+ enabled: true,
788
+ strength: { type: 'constant', value: 0.5 },
789
+ frequency: 0.5,
790
+ scrollSpeed: 0.2,
791
+ damping: true,
792
+ octaves: 2,
793
+ octaveMultiplier: 0.5,
794
+ octaveScale: 2,
795
+ positionAmount: {
796
+ x: { type: 'constant', value: 1 },
797
+ y: { type: 'constant', value: 0.5 },
798
+ z: { type: 'constant', value: 1 },
799
+ },
800
+ rotationAmount: {
801
+ x: { type: 'constant', value: 0 },
802
+ y: { type: 'constant', value: 0 },
803
+ z: { type: 'constant', value: 0 },
804
+ },
805
+ sizeAmount: { type: 'constant', value: 0 },
806
+ separateAxes: false,
807
+ remap: DEFAULT_CURVE,
808
+ remapEnabled: false,
809
+ },
810
+ material: {
811
+ shading: 'unlit',
812
+ blendMode: 'alpha',
813
+ colorMode: 'multiply',
814
+ flipBookBlending: false,
815
+ shadowCasting: 'off',
816
+ shadowReceiving: false,
817
+ cullMode: 'off',
818
+ sortingFudge: 0,
819
+ minParticleSize: 0,
820
+ maxParticleSize: 2,
821
+ pivot: { x: 0, y: 0, z: 0 },
822
+ customVertexStreams: [],
823
+ },
824
+ }),
825
+ sparks: () => ({
826
+ name: 'Sparks',
827
+ duration: 0.5,
828
+ looping: false,
829
+ startLifetime: { type: 'random', min: 0.3, max: 0.8 },
830
+ startSpeed: { type: 'random', min: 5, max: 15 },
831
+ startSize: { type: 'random', min: 0.02, max: 0.05 },
832
+ gravityModifier: { type: 'constant', value: 1 },
833
+ emission: {
834
+ enabled: true,
835
+ rateOverTime: { type: 'constant', value: 0 },
836
+ rateOverDistance: { type: 'constant', value: 0 },
837
+ bursts: [{ time: 0, count: { type: 'random', min: 20, max: 40 }, cycles: 1, interval: 0.1, probability: 1 }],
838
+ },
839
+ shape: {
840
+ type: 'sphere',
841
+ emitFromShell: true,
842
+ randomDirection: true,
843
+ sphericalDirection: true,
844
+ arcMode: 'random',
845
+ arcSpread: 0,
846
+ radius: { type: 'constant', value: 0.1 },
847
+ radiusThickness: 0,
848
+ arc: 360,
849
+ },
850
+ colorOverLifetime: {
851
+ enabled: true,
852
+ color: {
853
+ colorKeys: [
854
+ { time: 0, color: { r: 1, g: 0.9, b: 0.4 } },
855
+ { time: 0.5, color: { r: 1, g: 0.5, b: 0.1 } },
856
+ { time: 1, color: { r: 0.8, g: 0.2, b: 0 } },
857
+ ],
858
+ alphaKeys: [
859
+ { time: 0, alpha: 1 },
860
+ { time: 0.8, alpha: 1 },
861
+ { time: 1, alpha: 0 },
862
+ ],
863
+ },
864
+ },
865
+ renderer: {
866
+ type: 'stretched_billboard',
867
+ sortMode: 'none',
868
+ renderAlignment: 'velocity',
869
+ speedScale: 0.02,
870
+ lengthScale: 1,
871
+ faceCamera: true,
872
+ },
873
+ material: {
874
+ shading: 'unlit',
875
+ blendMode: 'additive',
876
+ colorMode: 'multiply',
877
+ flipBookBlending: false,
878
+ shadowCasting: 'off',
879
+ shadowReceiving: false,
880
+ cullMode: 'off',
881
+ sortingFudge: 0,
882
+ minParticleSize: 0,
883
+ maxParticleSize: 0.1,
884
+ pivot: { x: 0, y: 0, z: 0 },
885
+ customVertexStreams: [],
886
+ },
887
+ }),
888
+ snow: () => ({
889
+ name: 'Snow',
890
+ duration: 10,
891
+ startLifetime: { type: 'random', min: 10, max: 15 },
892
+ startSpeed: { type: 'random', min: 0.5, max: 1 },
893
+ startSize: { type: 'random', min: 0.05, max: 0.15 },
894
+ gravityModifier: { type: 'constant', value: 0.1 },
895
+ shape: {
896
+ type: 'box',
897
+ emitFromShell: false,
898
+ randomDirection: false,
899
+ sphericalDirection: false,
900
+ arcMode: 'random',
901
+ arcSpread: 0,
902
+ size: { x: 20, y: 0.1, z: 20 },
903
+ emitFrom: 'volume',
904
+ },
905
+ emission: {
906
+ enabled: true,
907
+ rateOverTime: { type: 'constant', value: 100 },
908
+ rateOverDistance: { type: 'constant', value: 0 },
909
+ bursts: [],
910
+ },
911
+ colorOverLifetime: {
912
+ enabled: true,
913
+ color: {
914
+ colorKeys: [{ time: 0, color: { r: 1, g: 1, b: 1 } }],
915
+ alphaKeys: [
916
+ { time: 0, alpha: 0 },
917
+ { time: 0.1, alpha: 0.8 },
918
+ { time: 0.9, alpha: 0.8 },
919
+ { time: 1, alpha: 0 },
920
+ ],
921
+ },
922
+ },
923
+ noise: {
924
+ enabled: true,
925
+ strength: { type: 'constant', value: 0.3 },
926
+ frequency: 0.3,
927
+ scrollSpeed: 0.1,
928
+ damping: false,
929
+ octaves: 2,
930
+ octaveMultiplier: 0.5,
931
+ octaveScale: 2,
932
+ positionAmount: {
933
+ x: { type: 'constant', value: 1 },
934
+ y: { type: 'constant', value: 0.2 },
935
+ z: { type: 'constant', value: 1 },
936
+ },
937
+ rotationAmount: {
938
+ x: { type: 'constant', value: 0 },
939
+ y: { type: 'constant', value: 0 },
940
+ z: { type: 'constant', value: 0 },
941
+ },
942
+ sizeAmount: { type: 'constant', value: 0 },
943
+ separateAxes: false,
944
+ remap: DEFAULT_CURVE,
945
+ remapEnabled: false,
946
+ },
947
+ material: {
948
+ shading: 'unlit',
949
+ blendMode: 'alpha',
950
+ colorMode: 'multiply',
951
+ flipBookBlending: false,
952
+ shadowCasting: 'off',
953
+ shadowReceiving: false,
954
+ cullMode: 'off',
955
+ sortingFudge: 0,
956
+ minParticleSize: 0,
957
+ maxParticleSize: 0.2,
958
+ pivot: { x: 0, y: 0, z: 0 },
959
+ customVertexStreams: [],
960
+ },
961
+ }),
962
+ rain: () => ({
963
+ name: 'Rain',
964
+ duration: 5,
965
+ startLifetime: { type: 'constant', value: 1 },
966
+ startSpeed: { type: 'random', min: 15, max: 20 },
967
+ startSize: { type: 'constant', value: 0.03 },
968
+ startRotation: { type: 'constant', value: 0 },
969
+ gravityModifier: { type: 'constant', value: 0 },
970
+ shape: {
971
+ type: 'box',
972
+ emitFromShell: false,
973
+ randomDirection: false,
974
+ sphericalDirection: false,
975
+ arcMode: 'random',
976
+ arcSpread: 0,
977
+ size: { x: 20, y: 0.1, z: 20 },
978
+ emitFrom: 'volume',
979
+ },
980
+ emission: {
981
+ enabled: true,
982
+ rateOverTime: { type: 'constant', value: 500 },
983
+ rateOverDistance: { type: 'constant', value: 0 },
984
+ bursts: [],
985
+ },
986
+ colorOverLifetime: {
987
+ enabled: true,
988
+ color: {
989
+ colorKeys: [{ time: 0, color: { r: 0.7, g: 0.8, b: 0.9 } }],
990
+ alphaKeys: [
991
+ { time: 0, alpha: 0 },
992
+ { time: 0.1, alpha: 0.5 },
993
+ { time: 0.9, alpha: 0.5 },
994
+ { time: 1, alpha: 0 },
995
+ ],
996
+ },
997
+ },
998
+ renderer: {
999
+ type: 'stretched_billboard',
1000
+ sortMode: 'none',
1001
+ renderAlignment: 'velocity',
1002
+ speedScale: 0.01,
1003
+ lengthScale: 3,
1004
+ faceCamera: true,
1005
+ },
1006
+ material: {
1007
+ shading: 'unlit',
1008
+ blendMode: 'alpha',
1009
+ colorMode: 'multiply',
1010
+ flipBookBlending: false,
1011
+ shadowCasting: 'off',
1012
+ shadowReceiving: false,
1013
+ cullMode: 'off',
1014
+ sortingFudge: 0,
1015
+ minParticleSize: 0,
1016
+ maxParticleSize: 0.1,
1017
+ pivot: { x: 0, y: 0, z: 0 },
1018
+ customVertexStreams: [],
1019
+ },
1020
+ }),
1021
+ };
1022
+ function applyPreset(preset) {
1023
+ const base = createDefaultSystem();
1024
+ const presetData = PARTICLE_PRESETS[preset]();
1025
+ return { ...base, ...presetData };
1026
+ }
1027
+ // ─── Import/Export ───────────────────────────────────────────────────────────
1028
+ function exportSystemsToJSON(systems) {
1029
+ return JSON.stringify(systems, null, 2);
1030
+ }
1031
+ function importSystemsFromJSON(json) {
1032
+ try {
1033
+ const data = JSON.parse(json);
1034
+ if (Array.isArray(data)) {
1035
+ return data;
1036
+ }
1037
+ if (data && typeof data === 'object' && data.id) {
1038
+ return [data];
1039
+ }
1040
+ return null;
1041
+ }
1042
+ catch (_a) {
1043
+ return null;
1044
+ }
1045
+ }
1046
+ function cloneSystem(system) {
1047
+ const cloned = JSON.parse(JSON.stringify(system));
1048
+ cloned.id = generateSystemId();
1049
+ cloned.name = `${system.name} (Copy)`;
1050
+ return cloned;
1051
+ }
1052
+
1053
+ export { PARTICLE_PRESETS, applyForce, applyPreset, cloneSystem, createDefaultSystem, evaluateCurve, evaluateDistribution, evaluateGradient, evaluateVec3Distribution, exportSystemsToJSON, fbmNoise, generateForceId, generateSubEmitterId, generateSystemId, importSystemsFromJSON, perlinNoise2D, sampleEmitterShape, seededRandom, vec3Add, vec3Cross, vec3Dot, vec3Length, vec3Lerp, vec3Mul, vec3MulVec, vec3Normalize, vec3Random, vec3RandomOnSphere, vec3Sub };
1054
+ //# sourceMappingURL=particleEditorUtils.js.map