@nice2dev/ui-3d 1.0.0 → 1.0.2

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