@fps-games/vfx 0.1.1 → 0.3.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 (250) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +41 -0
  3. package/dist/effects/coin-burst/index.js +6 -2
  4. package/dist/effects/comet-trail-flame/index.js +3 -1
  5. package/dist/effects/debris-smoke/index.js +1 -0
  6. package/dist/effects/electric-arc/index.js +1 -0
  7. package/dist/effects/energy-shield/index.js +1 -0
  8. package/dist/effects/explosion/index.js +3 -2
  9. package/dist/effects/hit-flash/index.js +12 -4
  10. package/dist/effects/laser-beam/index.js +14 -2
  11. package/dist/effects/lumber_vehicle_upgrade_aura/index.js +1 -0
  12. package/dist/effects/monster-hit/index.js +2 -1
  13. package/dist/effects/player-upgrade/index.js +5 -3
  14. package/dist/effects/pulse-laser-volley/index.js +14 -4
  15. package/dist/effects/thruster-flame/index.js +3 -1
  16. package/dist/effects/treasure-attention/index.d.ts +54 -0
  17. package/dist/effects/treasure-attention/index.js +535 -0
  18. package/dist/effects/ufo-thread-flame/index.js +1 -0
  19. package/dist/effects/vehicle-upgrade/index.js +4 -1
  20. package/dist/effects/weapon-trail/index.js +8 -3
  21. package/dist/effects/wood_crusher_sawdust/index.d.ts +4 -0
  22. package/dist/effects/wood_cutter_sawdust/index.d.ts +4 -0
  23. package/dist/index.js +85 -17
  24. package/dist/types.d.ts +80 -0
  25. package/index.json +19 -1
  26. package/package.json +9 -4
  27. package/packages/effects/treasure-attention/vfx-params.json +49 -0
  28. package/tooling/vfx-cli.mjs +8 -1
  29. package/docs/AUTHORING_EFFECTS.md +0 -297
  30. package/docs/VFX_LIBRARY_ARCHITECTURE_chatgpt.md +0 -1580
  31. package/docs/VFX_LIBRARY_EXECUTION_PLAN.md +0 -229
  32. package/docs/VFX_LIBRARY_PLAN.md +0 -546
  33. package/docs/vfx-ab/RESULTS.md +0 -35
  34. package/docs/vfx-ab/lumber-order/vehicle-exhaust-smoke/after.png +0 -0
  35. package/docs/vfx-ab/lumber-order/vehicle-exhaust-smoke/before.png +0 -0
  36. package/docs/vfx-ab/lumber-truck/vehicle-exhaust-smoke/after.png +0 -0
  37. package/docs/vfx-ab/lumber-truck/vehicle-exhaust-smoke/before.png +0 -0
  38. package/docs/vfx-integration_chatgpt.md +0 -462
  39. package/docs/weapon-trail-visualization.html +0 -241
  40. package/packages/EffectPackageService.ts +0 -240
  41. package/packages/SceneVfxService.ts +0 -96
  42. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/runtime/simpleMeshEffect.v1.js +0 -333
  43. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/shaders/mesh-effect-preview.frag.glsl +0 -25
  44. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/shaders/mesh-effect-preview.vert.glsl +0 -11
  45. package/packages/build.mesh.yellow_build_upgrade_indicator/assets/textures/effect_jianbian.png +0 -0
  46. package/packages/build.mesh.yellow_build_upgrade_indicator/effect.json +0 -927
  47. package/packages/build.mesh.yellow_build_upgrade_indicator/media/contact-sheet.png +0 -0
  48. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.gif +0 -0
  49. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.mp4 +0 -0
  50. package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.webm +0 -0
  51. package/packages/build.mesh.yellow_build_upgrade_indicator/media/thumbnail.png +0 -0
  52. package/packages/build.mesh.yellow_build_upgrade_indicator/metadata/description.json +0 -197
  53. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/03-effect.original.json +0 -895
  54. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/03-normalized-vfx-to-babylon-report.json +0 -241
  55. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/04-preview-overrides.json +0 -542
  56. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/04-preview-tuning-report.json +0 -69
  57. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/asset-map.json +0 -125
  58. package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/lineage.json +0 -44
  59. package/packages/build.particle.golden_upgrade_starburst/assets/runtime/particleEmitter.v1.js +0 -210
  60. package/packages/build.particle.golden_upgrade_starburst/assets/textures/effect_guangdian.png +0 -0
  61. package/packages/build.particle.golden_upgrade_starburst/effect.json +0 -880
  62. package/packages/build.particle.golden_upgrade_starburst/media/contact-sheet.png +0 -0
  63. package/packages/build.particle.golden_upgrade_starburst/media/preview.gif +0 -0
  64. package/packages/build.particle.golden_upgrade_starburst/media/preview.mp4 +0 -0
  65. package/packages/build.particle.golden_upgrade_starburst/media/preview.webm +0 -0
  66. package/packages/build.particle.golden_upgrade_starburst/media/thumbnail.png +0 -0
  67. package/packages/build.particle.golden_upgrade_starburst/metadata/description.json +0 -161
  68. package/packages/build.particle.golden_upgrade_starburst/provenance/03-effect.original.json +0 -762
  69. package/packages/build.particle.golden_upgrade_starburst/provenance/03-normalized-vfx-to-babylon-report.json +0 -260
  70. package/packages/build.particle.golden_upgrade_starburst/provenance/04-preview-overrides.json +0 -436
  71. package/packages/build.particle.golden_upgrade_starburst/provenance/04-preview-tuning-report.json +0 -40
  72. package/packages/build.particle.golden_upgrade_starburst/provenance/asset-map.json +0 -115
  73. package/packages/build.particle.golden_upgrade_starburst/provenance/lineage.json +0 -40
  74. package/packages/build.particle.orange_upgrade_transform_sparks/assets/runtime/particleEmitter.v1.js +0 -210
  75. package/packages/build.particle.orange_upgrade_transform_sparks/assets/textures/b5b27ab1-e740-4398-b407-848fc2b2c897.png +0 -0
  76. package/packages/build.particle.orange_upgrade_transform_sparks/effect.json +0 -887
  77. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.gif +0 -0
  78. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.mp4 +0 -0
  79. package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.webm +0 -0
  80. package/packages/build.particle.orange_upgrade_transform_sparks/media/thumbnail.png +0 -0
  81. package/packages/build.particle.orange_upgrade_transform_sparks/metadata/description.json +0 -179
  82. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/03-effect.original.json +0 -859
  83. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/03-normalized-vfx-to-babylon-report.json +0 -260
  84. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/04-preview-overrides.json +0 -478
  85. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/04-preview-tuning-report.json +0 -40
  86. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/asset-map.json +0 -114
  87. package/packages/build.particle.orange_upgrade_transform_sparks/provenance/lineage.json +0 -41
  88. package/packages/build.particle.wood_chip_dust_burst/assets/runtime/particleEmitter.v1.js +0 -210
  89. package/packages/build.particle.wood_chip_dust_burst/assets/textures/effect_yanwu4.png +0 -0
  90. package/packages/build.particle.wood_chip_dust_burst/assets/textures/suipian3.png +0 -0
  91. package/packages/build.particle.wood_chip_dust_burst/effect.json +0 -1400
  92. package/packages/build.particle.wood_chip_dust_burst/media/contact-sheet.png +0 -0
  93. package/packages/build.particle.wood_chip_dust_burst/media/preview.gif +0 -0
  94. package/packages/build.particle.wood_chip_dust_burst/media/preview.mp4 +0 -0
  95. package/packages/build.particle.wood_chip_dust_burst/media/preview.webm +0 -0
  96. package/packages/build.particle.wood_chip_dust_burst/media/thumbnail.png +0 -0
  97. package/packages/build.particle.wood_chip_dust_burst/metadata/description.json +0 -173
  98. package/packages/build.particle.wood_chip_dust_burst/provenance/03-effect.original.json +0 -1277
  99. package/packages/build.particle.wood_chip_dust_burst/provenance/03-normalized-vfx-to-babylon-report.json +0 -329
  100. package/packages/build.particle.wood_chip_dust_burst/provenance/04-preview-overrides.json +0 -749
  101. package/packages/build.particle.wood_chip_dust_burst/provenance/04-preview-tuning-report.json +0 -60
  102. package/packages/build.particle.wood_chip_dust_burst/provenance/asset-map.json +0 -146
  103. package/packages/build.particle.wood_chip_dust_burst/provenance/lineage.json +0 -69
  104. package/packages/effects/building-upgrade/effect.json +0 -6
  105. package/packages/effects/building-upgrade/index.ts +0 -387
  106. package/packages/effects/character-entrance/effect.json +0 -6
  107. package/packages/effects/character-entrance/index.ts +0 -308
  108. package/packages/effects/coin-burst/index.ts +0 -43
  109. package/packages/effects/comet-trail-flame/assets/textures/trail_5x4.webp +0 -0
  110. package/packages/effects/comet-trail-flame/index.ts +0 -106
  111. package/packages/effects/conveyor-upgrade/effect.json +0 -6
  112. package/packages/effects/conveyor-upgrade/index.ts +0 -401
  113. package/packages/effects/debris-smoke/assets/models/rock_debris.glb +0 -0
  114. package/packages/effects/debris-smoke/assets/textures/smoke22.webp +0 -0
  115. package/packages/effects/debris-smoke/index.ts +0 -101
  116. package/packages/effects/door_open_gold_flakes/assets/runtime/goldFlakes.v1.js +0 -397
  117. package/packages/effects/door_open_gold_flakes/door_open_gold_flakes.json +0 -498
  118. package/packages/effects/door_open_gold_flakes/index.ts +0 -11
  119. package/packages/effects/electric-arc/assets/textures/spark_4x4.webp +0 -0
  120. package/packages/effects/electric-arc/index.ts +0 -62
  121. package/packages/effects/energy-shield/effect.json +0 -7
  122. package/packages/effects/energy-shield/index.ts +0 -431
  123. package/packages/effects/explosion/assets/textures/baozha29.webp +0 -0
  124. package/packages/effects/explosion/assets/textures/glow01.webp +0 -0
  125. package/packages/effects/explosion/assets/textures/lizi01.webp +0 -0
  126. package/packages/effects/explosion/assets/textures/xulie_lizibaozha02_5x5.webp +0 -0
  127. package/packages/effects/explosion/index.ts +0 -114
  128. package/packages/effects/ground-tile-upgrade/effect.json +0 -6
  129. package/packages/effects/ground-tile-upgrade/index.ts +0 -410
  130. package/packages/effects/hit-flash/index.ts +0 -69
  131. package/packages/effects/laser-beam/assets/textures/laser_glow.webp +0 -0
  132. package/packages/effects/laser-beam/assets/textures/laser_noise.webp +0 -0
  133. package/packages/effects/laser-beam/index.ts +0 -6
  134. package/packages/effects/laser-beam-factory.ts +0 -135
  135. package/packages/effects/lumber-cart-suction/effect.json +0 -6
  136. package/packages/effects/lumber-cart-suction/index.ts +0 -399
  137. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb +0 -0
  138. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb.versions/.active +0 -1
  139. package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb.versions/1781589063890_upload.glb +0 -0
  140. package/packages/effects/lumber-debris-scatter/assets/runtime/solidParticleScatter.v1.js +0 -236
  141. package/packages/effects/lumber-debris-scatter/effect.json +0 -228
  142. package/packages/effects/lumber-debris-scatter/index.ts +0 -780
  143. package/packages/effects/lumber_vehicle_gear_spin/assets/runtime/gearSpin.v1.js +0 -202
  144. package/packages/effects/lumber_vehicle_gear_spin/index.ts +0 -11
  145. package/packages/effects/lumber_vehicle_gear_spin/lumber_vehicle_gear_spin.json +0 -338
  146. package/packages/effects/lumber_vehicle_upgrade_aura/assets/runtime/upgradeAura.v1.js +0 -353
  147. package/packages/effects/lumber_vehicle_upgrade_aura/index.ts +0 -11
  148. package/packages/effects/lumber_vehicle_upgrade_aura/lumber_vehicle_upgrade_aura.json +0 -365
  149. package/packages/effects/lumber_vehicle_wood_chips/assets/runtime/solidParticleScatter.v1.js +0 -310
  150. package/packages/effects/lumber_vehicle_wood_chips/index.ts +0 -11
  151. package/packages/effects/lumber_vehicle_wood_chips/lumber_vehicle_wood_chips.json +0 -335
  152. package/packages/effects/monster-hit/index.ts +0 -128
  153. package/packages/effects/player-upgrade/effect.json +0 -6
  154. package/packages/effects/player-upgrade/index.ts +0 -635
  155. package/packages/effects/pulse-laser-volley/assets/textures/bolt_guangshu.webp +0 -0
  156. package/packages/effects/pulse-laser-volley/index.ts +0 -204
  157. package/packages/effects/resource-collect-sparkles/effect.json +0 -6
  158. package/packages/effects/resource-collect-sparkles/index.ts +0 -254
  159. package/packages/effects/run-footstep-dust/assets/runtime/runFootstepDust.v1.js +0 -215
  160. package/packages/effects/run-footstep-dust/effect.json +0 -183
  161. package/packages/effects/run-footstep-dust/index.ts +0 -377
  162. package/packages/effects/space-shared.ts +0 -120
  163. package/packages/effects/thruster-flame/index.ts +0 -305
  164. package/packages/effects/tree-chop-dust/assets/textures/tree_chop_dust_atlas.png +0 -0
  165. package/packages/effects/tree-chop-dust/effect.json +0 -8
  166. package/packages/effects/tree-chop-dust/index.ts +0 -202
  167. package/packages/effects/tree-leaf-chop-burst/assets/runtime/particleEmitter.v1.js +0 -303
  168. package/packages/effects/tree-leaf-chop-burst/assets/textures/tree_leaf_chop_burst_leaf_atlas.png +0 -0
  169. package/packages/effects/tree-leaf-chop-burst/effect.json +0 -498
  170. package/packages/effects/tree-leaf-chop-burst/index.ts +0 -294
  171. package/packages/effects/tree_chop_dust_burst/assets/runtime/particleEmitter.v1.js +0 -318
  172. package/packages/effects/tree_chop_dust_burst/assets/textures/tree_chop_dust_burst_dust.png +0 -0
  173. package/packages/effects/tree_chop_dust_burst/index.ts +0 -13
  174. package/packages/effects/tree_chop_dust_burst/tree_chop_dust_burst.json +0 -412
  175. package/packages/effects/ufo-thread-flame/assets/textures/flame_flow.webp +0 -0
  176. package/packages/effects/ufo-thread-flame/assets/textures/flame_spike.webp +0 -0
  177. package/packages/effects/ufo-thread-flame/index.ts +0 -152
  178. package/packages/effects/vehicle-exhaust-smoke/assets/textures/vehicle_exhaust_smoke_atlas.png +0 -0
  179. package/packages/effects/vehicle-exhaust-smoke/effect.json +0 -8
  180. package/packages/effects/vehicle-exhaust-smoke/index.ts +0 -298
  181. package/packages/effects/vehicle-upgrade/effect.json +0 -6
  182. package/packages/effects/vehicle-upgrade/index.ts +0 -401
  183. package/packages/effects/weapon-trail/assets/textures/trail_wedge.png +0 -0
  184. package/packages/effects/weapon-trail/index.ts +0 -313
  185. package/packages/effects/wood-crusher-sawdust/assets/runtime/particleEmitter.v1.js +0 -312
  186. package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_dust.png +0 -0
  187. package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_fiber.png +0 -0
  188. package/packages/effects/wood-crusher-sawdust/index.ts +0 -43
  189. package/packages/effects/wood-crusher-sawdust/wood_crusher_sawdust.json +0 -592
  190. package/packages/effects/wood-cutter-sawdust/assets/runtime/particleEmitter.v1.js +0 -312
  191. package/packages/effects/wood-cutter-sawdust/assets/textures/wood_cutter_sawdust_chip.png +0 -0
  192. package/packages/effects/wood-cutter-sawdust/assets/textures/wood_cutter_sawdust_dust.png +0 -0
  193. package/packages/effects/wood-cutter-sawdust/index.ts +0 -43
  194. package/packages/effects/wood-cutter-sawdust/wood_cutter_sawdust.json +0 -592
  195. package/packages/effects/wood-sawdust-effect-factory.ts +0 -194
  196. package/packages/effects/wood_crusher_sawdust/assets/runtime/particleEmitter.v1.js +0 -318
  197. package/packages/effects/wood_crusher_sawdust/assets/textures/wood_crusher_sawdust_dust.png +0 -0
  198. package/packages/effects/wood_crusher_sawdust/assets/textures/wood_crusher_sawdust_fiber.png +0 -0
  199. package/packages/effects/wood_crusher_sawdust/index.ts +0 -29
  200. package/packages/effects/wood_crusher_sawdust/wood_crusher_sawdust.json +0 -592
  201. package/packages/effects/wood_cutter_sawdust/assets/runtime/particleEmitter.v1.js +0 -318
  202. package/packages/effects/wood_cutter_sawdust/assets/textures/wood_cutter_sawdust_chip.png +0 -0
  203. package/packages/effects/wood_cutter_sawdust/assets/textures/wood_cutter_sawdust_dust.png +0 -0
  204. package/packages/effects/wood_cutter_sawdust/index.ts +0 -29
  205. package/packages/effects/wood_cutter_sawdust/wood_cutter_sawdust.json +0 -592
  206. package/packages/effects/wood_peel_material_test/assets/runtime/woodPeelMaterialTest.v1.js +0 -196
  207. package/packages/effects/wood_peel_material_test/index.ts +0 -11
  208. package/packages/effects/wood_peel_material_test/wood_peel_material_test.json +0 -136
  209. package/packages/env.particle.forest_edge_fog/assets/runtime/particleEmitter.v1.js +0 -210
  210. package/packages/env.particle.forest_edge_fog/assets/textures/f12a23c4-b924-4322-a260-3d982428f1e8.png +0 -0
  211. package/packages/env.particle.forest_edge_fog/assets/textures/yuan.png +0 -0
  212. package/packages/env.particle.forest_edge_fog/effect.json +0 -854
  213. package/packages/env.particle.forest_edge_fog/media/preview.gif +0 -0
  214. package/packages/env.particle.forest_edge_fog/media/preview.mp4 +0 -0
  215. package/packages/env.particle.forest_edge_fog/media/preview.webm +0 -0
  216. package/packages/env.particle.forest_edge_fog/media/thumbnail.png +0 -0
  217. package/packages/env.particle.forest_edge_fog/metadata/description.json +0 -148
  218. package/packages/env.particle.forest_edge_fog/provenance/03-effect.original.json +0 -827
  219. package/packages/env.particle.forest_edge_fog/provenance/03-normalized-vfx-to-babylon-report.json +0 -196
  220. package/packages/env.particle.forest_edge_fog/provenance/04-preview-overrides.json +0 -432
  221. package/packages/env.particle.forest_edge_fog/provenance/04-preview-tuning-report.json +0 -28
  222. package/packages/env.particle.forest_edge_fog/provenance/asset-map.json +0 -124
  223. package/packages/env.particle.forest_edge_fog/provenance/lineage.json +0 -41
  224. package/packages/generation.ts +0 -14
  225. package/packages/index.ts +0 -14
  226. package/packages/legacy-particle-runtime.ts +0 -113
  227. package/packages/manifestRuntimePackage.ts +0 -519
  228. package/packages/registry.ts +0 -85
  229. package/packages/rendering.ts +0 -13
  230. package/packages/resource.particle.viscous_oil_suction/assets/runtime/top_disk_suction/particleEmitter.v1.js +0 -424
  231. package/packages/resource.particle.viscous_oil_suction/assets/textures/Glow ad.png +0 -0
  232. package/packages/resource.particle.viscous_oil_suction/effect.json +0 -1254
  233. package/packages/resource.particle.viscous_oil_suction/media/preview.gif +0 -0
  234. package/packages/resource.particle.viscous_oil_suction/media/preview.mp4 +0 -0
  235. package/packages/resource.particle.viscous_oil_suction/media/preview.webm +0 -0
  236. package/packages/resource.particle.viscous_oil_suction/media/thumbnail.png +0 -0
  237. package/packages/resource.particle.viscous_oil_suction/metadata/description.json +0 -122
  238. package/packages/resource.particle.viscous_oil_suction/provenance/03-effect.original.json +0 -1051
  239. package/packages/resource.particle.viscous_oil_suction/provenance/03-normalized-vfx-to-babylon-report.json +0 -200
  240. package/packages/resource.particle.viscous_oil_suction/provenance/04-preview-overrides.json +0 -581
  241. package/packages/resource.particle.viscous_oil_suction/provenance/04-preview-tuning-report.json +0 -28
  242. package/packages/resource.particle.viscous_oil_suction/provenance/asset-map.json +0 -111
  243. package/packages/resource.particle.viscous_oil_suction/provenance/lineage.json +0 -45
  244. package/packages/types.ts +0 -107
  245. package/tooling/check-dist-runtime.mjs +0 -166
  246. package/tooling/generate-index.mjs +0 -55
  247. package/tooling/test-guardrails.mjs +0 -157
  248. package/tooling/validate-boundary.mjs +0 -92
  249. package/tooling/validate-effects.mjs +0 -304
  250. package/tooling/validate.mjs +0 -53
@@ -1,399 +0,0 @@
1
- import { Color3, Color4 } from '@babylonjs/core/Maths/math.color';
2
- import { Matrix, Vector3 } from '@babylonjs/core/Maths/math.vector';
3
- import { Constants } from '@babylonjs/core/Engines/constants';
4
- import { DynamicTexture } from '@babylonjs/core/Materials/Textures/dynamicTexture';
5
- import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
6
- import { CreateTube } from '@babylonjs/core/Meshes/Builders/tubeBuilder';
7
- import { ParticleSystem } from '@babylonjs/core/Particles/particleSystem';
8
- import type { Mesh } from '@babylonjs/core/Meshes/mesh';
9
- import type { Particle } from '@babylonjs/core/Particles/particle';
10
- import type { Scene } from '@babylonjs/core/scene';
11
- import type { TransformNode } from '@babylonjs/core/Meshes/transformNode';
12
- import type { VfxColorValue, VfxEffectPackage, VfxParamValues } from '../../types';
13
- import manifest from './effect.json';
14
-
15
- const LUMBER_CART_SUCTION_RENDERING_GROUP_ID = 0;
16
-
17
- interface LumberCartSuctionParams extends VfxParamValues {
18
- lifetime: number;
19
- scale: number;
20
- initialHeight: number;
21
- color: VfxColorValue;
22
- opacity: number;
23
- ringCount: number;
24
- forwardOffset: number;
25
- suctionLength: number;
26
- startRadius: number;
27
- endRadius: number;
28
- ringWidth: number;
29
- suctionSpeed: number;
30
- pulse: number;
31
- flyingParticleCount: number;
32
- flyingParticleSize: number;
33
- flyingParticleSpeed: number;
34
- flyingParticleDrift: number;
35
- flyingParticleOuterOffset: number;
36
- flyingParticleOpacity: number;
37
- directionX: number;
38
- directionZ: number;
39
- }
40
-
41
- interface RingBasis {
42
- forward: Vector3;
43
- side: Vector3;
44
- up: Vector3;
45
- mouth: Vector3;
46
- length: number;
47
- startRadius: number;
48
- endRadius: number;
49
- ringWidth: number;
50
- }
51
-
52
- const DEFAULT_PARAMS: LumberCartSuctionParams = {
53
- lifetime: 1.8,
54
- scale: 1,
55
- initialHeight: 0.18,
56
- color: { r: 1, g: 0.86, b: 0.42 },
57
- opacity: 0.95,
58
- ringCount: 5,
59
- forwardOffset: 0.4,
60
- suctionLength: 2.1,
61
- startRadius: 0.62,
62
- endRadius: 0.08,
63
- ringWidth: 0.035,
64
- suctionSpeed: 1.45,
65
- pulse: 0.18,
66
- flyingParticleCount: 42,
67
- flyingParticleSize: 0.045,
68
- flyingParticleSpeed: 4.2,
69
- flyingParticleDrift: 0.22,
70
- flyingParticleOuterOffset: 0.28,
71
- flyingParticleOpacity: 0.74,
72
- directionX: 1,
73
- directionZ: 0,
74
- };
75
-
76
- export const lumberCartSuctionEffectPackage: VfxEffectPackage<LumberCartSuctionParams> = {
77
- id: 'lumber-cart-suction',
78
- nameZh: '收集车吸入圆环',
79
- manifest,
80
- defaultParams: DEFAULT_PARAMS,
81
- debugAnchor: 'playerRight',
82
- debugParams: [
83
- { key: 'lifetime', label: '生命周期', kind: 'lifetime', min: 0.08, step: 0.01 },
84
- { key: 'scale', label: '整体缩放', kind: 'scale', min: 0.05, max: 6, step: 0.01 },
85
- { key: 'initialHeight', label: '初始高度', kind: 'number', min: -0.5, max: 3, step: 0.01 },
86
- { key: 'color', label: '圆环颜色', kind: 'color' },
87
- { key: 'opacity', label: '透明度', kind: 'opacity', min: 0, max: 1, step: 0.01 },
88
- { key: 'ringCount', label: '圆环数量', kind: 'number', min: 1, max: 16, step: 1 },
89
- { key: 'forwardOffset', label: '车前偏移', kind: 'number', min: -1, max: 4, step: 0.01 },
90
- { key: 'suctionLength', label: '吸入长度', kind: 'number', min: 0.1, max: 8, step: 0.01 },
91
- { key: 'startRadius', label: '外圈半径', kind: 'number', min: 0.02, max: 4, step: 0.01 },
92
- { key: 'endRadius', label: '入口半径', kind: 'number', min: 0.01, max: 2, step: 0.01 },
93
- { key: 'ringWidth', label: '圆环粗细', kind: 'number', min: 0.005, max: 0.25, step: 0.001 },
94
- { key: 'suctionSpeed', label: '吸入速度', kind: 'number', min: 0, max: 8, step: 0.01 },
95
- { key: 'pulse', label: '收缩脉冲', kind: 'number', min: 0, max: 1, step: 0.01 },
96
- { key: 'flyingParticleCount', label: '飞行粒子数量', kind: 'number', min: 0, max: 180, step: 1 },
97
- { key: 'flyingParticleSize', label: '飞行粒子大小', kind: 'number', min: 0.005, max: 0.3, step: 0.001 },
98
- { key: 'flyingParticleSpeed', label: '飞行粒子速度', kind: 'number', min: 0, max: 16, step: 0.01 },
99
- { key: 'flyingParticleDrift', label: '飞行粒子散布', kind: 'number', min: 0, max: 1.5, step: 0.01 },
100
- { key: 'flyingParticleOuterOffset', label: '飞行粒子外偏移', kind: 'number', min: -1, max: 2, step: 0.01 },
101
- { key: 'flyingParticleOpacity', label: '飞行粒子透明度', kind: 'opacity', min: 0, max: 1, step: 0.01 },
102
- { key: 'directionX', label: '方向 X', kind: 'number', min: -1, max: 1, step: 0.01 },
103
- { key: 'directionZ', label: '方向 Z', kind: 'number', min: -1, max: 1, step: 0.01 },
104
- ],
105
- create({ scene, root, position, params }) {
106
- root.position.copyFrom(position).addInPlace(new Vector3(0, params.initialHeight, 0));
107
- const rings = createSuctionRings(scene, root, params);
108
- let disposed = false;
109
-
110
- rings.start();
111
-
112
- return {
113
- root,
114
- dispose() {
115
- disposeAll();
116
- },
117
- };
118
-
119
- function disposeAll(): void {
120
- if (disposed) return;
121
- disposed = true;
122
- rings.dispose();
123
- root.dispose();
124
- }
125
- },
126
- };
127
-
128
- function createSuctionRings(scene: Scene, root: TransformNode, params: LumberCartSuctionParams): { start(): void; dispose(): void } {
129
- const basis = createRingBasis(params);
130
- const ringCount = Math.max(1, Math.round(params.ringCount));
131
- const material = createRingMaterial(scene, params);
132
- const particles = createFlyingParticleSystem(scene, root, params, basis);
133
- const rings: Mesh[] = [];
134
- let age = 0;
135
- let observer: ReturnType<Scene['onBeforeRenderObservable']['add']> | null = null;
136
- let disposed = false;
137
-
138
- for (let index = 0; index < ringCount; index += 1) {
139
- const progress = index / ringCount;
140
- const ring = CreateTube(`lumber-cart-suction.ring.${index}`, {
141
- path: buildRingPath(basis, progress, params.pulse),
142
- radius: basis.ringWidth,
143
- tessellation: 8,
144
- updatable: true,
145
- }, scene);
146
- ring.parent = root;
147
- ring.material = material;
148
- ring.renderingGroupId = LUMBER_CART_SUCTION_RENDERING_GROUP_ID;
149
- ring.isPickable = false;
150
- rings.push(ring);
151
- }
152
-
153
- return { start, dispose };
154
-
155
- function start(): void {
156
- if (observer) return;
157
- particles.start();
158
- observer = scene.onBeforeRenderObservable.add(() => {
159
- if (disposed) return;
160
- const deltaSeconds = Math.min(0.05, scene.getEngine().getDeltaTime() / 1000);
161
- age += deltaSeconds;
162
-
163
- for (let index = 0; index < rings.length; index += 1) {
164
- const progress = fract(age * Math.max(0, params.suctionSpeed) + index / rings.length);
165
- CreateTube(`lumber-cart-suction.ring.${index}`, {
166
- path: buildRingPath(basis, progress, params.pulse),
167
- radius: basis.ringWidth,
168
- tessellation: 8,
169
- instance: rings[index],
170
- }, scene);
171
- }
172
-
173
- const pulse = Math.sin(age * Math.max(0.1, params.suctionSpeed) * Math.PI * 2) * Math.max(0, params.pulse);
174
- material.alpha = clamp01(params.opacity * (0.88 + pulse * 0.12));
175
- });
176
- }
177
-
178
- function dispose(): void {
179
- if (disposed) return;
180
- disposed = true;
181
- if (observer) scene.onBeforeRenderObservable.remove(observer);
182
- observer = null;
183
- for (const ring of rings) ring.dispose();
184
- particles.stop();
185
- particles.dispose(false);
186
- material.dispose();
187
- }
188
- }
189
-
190
- function createRingBasis(params: LumberCartSuctionParams): RingBasis {
191
- const scale = Math.max(0.01, params.scale);
192
- const forward = new Vector3(params.directionX, 0, params.directionZ);
193
- if (forward.lengthSquared() < 0.0001) forward.set(1, 0, 0);
194
- forward.normalize();
195
-
196
- return {
197
- forward,
198
- side: new Vector3(-forward.z, 0, forward.x),
199
- up: Vector3.Up(),
200
- mouth: forward.scale(params.forwardOffset * scale),
201
- length: Math.max(0.01, params.suctionLength * scale),
202
- startRadius: Math.max(0.001, params.startRadius * scale),
203
- endRadius: Math.max(0.001, params.endRadius * scale),
204
- ringWidth: Math.max(0.002, params.ringWidth * scale),
205
- };
206
- }
207
-
208
- function buildRingPath(basis: RingBasis, progress: number, pulse: number): Vector3[] {
209
- const points: Vector3[] = [];
210
- const segments = 72;
211
- const eased = easeInCubic(clamp01(progress));
212
- const distance = basis.length * (1 - eased);
213
- const pulseScale = 1 + Math.sin(progress * Math.PI * 2) * Math.max(0, pulse) * 0.12;
214
- const radius = lerp(basis.endRadius, basis.startRadius, 1 - eased) * pulseScale;
215
- const center = basis.mouth.add(basis.forward.scale(distance));
216
-
217
- for (let index = 0; index <= segments; index += 1) {
218
- const angle = (index / segments) * Math.PI * 2;
219
- points.push(
220
- center
221
- .add(basis.side.scale(Math.cos(angle) * radius))
222
- .add(basis.up.scale(Math.sin(angle) * radius)),
223
- );
224
- }
225
-
226
- return points;
227
- }
228
-
229
- function createRingMaterial(scene: Scene, params: LumberCartSuctionParams): StandardMaterial {
230
- const material = new StandardMaterial('lumber-cart-suction.ring.material', scene);
231
- const color = toColor3(params.color);
232
- material.diffuseColor = color;
233
- material.emissiveColor = color.scale(1.35);
234
- material.alpha = params.opacity;
235
- material.disableLighting = true;
236
- material.backFaceCulling = false;
237
- material.alphaMode = Constants.ALPHA_ADD;
238
- return material;
239
- }
240
-
241
- function createFlyingParticleSystem(
242
- scene: Scene,
243
- root: TransformNode,
244
- params: LumberCartSuctionParams,
245
- basis: RingBasis,
246
- ): ParticleSystem {
247
- const scale = Math.max(0.01, params.scale);
248
- const count = Math.max(0, Math.round(params.flyingParticleCount));
249
- const speed = Math.max(0.01, params.flyingParticleSpeed * scale);
250
- const particleLife = clampNumber(basis.length / speed, 0.18, 0.85);
251
- const system = new ParticleSystem('lumber-cart-suction.flying-particles', Math.max(8, count * 2), scene);
252
- const size = Math.max(0.001, params.flyingParticleSize * scale);
253
- const color = params.color;
254
- const opacity = clamp01(params.flyingParticleOpacity * params.opacity);
255
-
256
- system.isLocal = false;
257
- system.renderingGroupId = LUMBER_CART_SUCTION_RENDERING_GROUP_ID;
258
- system.particleTexture = getFlyingParticleTexture(scene);
259
- system.emitter = root as any;
260
- system.emitRate = count / particleLife;
261
- system.updateSpeed = 1 / 60;
262
- system.minLifeTime = particleLife * 0.72;
263
- system.maxLifeTime = particleLife * 1.12;
264
- system.minSize = size * 0.72;
265
- system.maxSize = size * 1.28;
266
- system.minAngularSpeed = -4;
267
- system.maxAngularSpeed = 4;
268
- system.minEmitPower = 1;
269
- system.maxEmitPower = 1;
270
- system.gravity = Vector3.Zero();
271
- system.blendMode = ParticleSystem.BLENDMODE_ADD;
272
- system.color1 = tintColor(color, 1.5, opacity * 0.78);
273
- system.color2 = tintColor(color, 2.1, opacity);
274
- system.colorDead = tintColor(color, 0.8, 0);
275
- system.addColorGradient(0, tintColor(color, 1.1, 0));
276
- system.addColorGradient(0.2, tintColor(color, 2.2, opacity));
277
- system.addColorGradient(0.72, tintColor(color, 1.55, opacity * 0.68));
278
- system.addColorGradient(1, tintColor(color, 0.8, 0));
279
- system.addSizeGradient(0, size * 0.46, size * 0.72);
280
- system.addSizeGradient(0.22, size * 0.9, size * 1.28);
281
- system.addSizeGradient(1, size * 0.18, size * 0.36);
282
-
283
- system.startPositionFunction = (worldMatrix: Matrix, positionToUpdate: Vector3, _particle: Particle, isLocal: boolean) => {
284
- const local = sampleFlyingParticlePosition(basis, params);
285
- if (isLocal) positionToUpdate.copyFrom(local);
286
- else Vector3.TransformCoordinatesToRef(local, worldMatrix, positionToUpdate);
287
- };
288
- system.startDirectionFunction = (worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle, isLocal: boolean) => {
289
- const id = flyingParticleDirectionSequenceIndex;
290
- flyingParticleDirectionSequenceIndex += 1;
291
- const localPosition = particle.position.clone();
292
- const localTarget = basis.mouth.add(
293
- basis.side.scale((sequence01(id, 3) - 0.5) * basis.endRadius * 0.6),
294
- ).addInPlace(
295
- basis.up.scale((sequence01(id, 4) - 0.5) * basis.endRadius * 0.5),
296
- );
297
- const target = isLocal ? localTarget : Vector3.TransformCoordinates(localTarget, worldMatrix);
298
- const direction = target.subtract(localPosition);
299
- if (direction.lengthSquared() < 0.0001) {
300
- const fallback = basis.forward.scale(-1);
301
- if (isLocal) direction.copyFrom(fallback);
302
- else Vector3.TransformNormalToRef(fallback, worldMatrix, direction);
303
- }
304
- else direction.normalize();
305
-
306
- const drift = Math.max(0, params.flyingParticleDrift) * scale;
307
- const localTangent = basis.side
308
- .scale((sequence01(id, 5) - 0.5) * drift)
309
- .addInPlace(basis.up.scale((sequence01(id, 6) - 0.5) * drift * 0.65));
310
- const tangent = isLocal ? localTangent : Vector3.TransformNormal(localTangent, worldMatrix);
311
- direction.scaleInPlace(speed * lerp(0.78, 1.28, sequence01(id, 7))).addInPlace(tangent);
312
- directionToUpdate.copyFrom(direction);
313
- };
314
-
315
- return system;
316
- }
317
-
318
- function sampleFlyingParticlePosition(basis: RingBasis, params: LumberCartSuctionParams): Vector3 {
319
- const id = flyingParticleSequenceIndex;
320
- flyingParticleSequenceIndex += 1;
321
- const scale = Math.max(0.01, params.scale);
322
- const outerOffset = params.flyingParticleOuterOffset * scale;
323
- const distanceRatio = lerp(0.68, 1.02, sequence01(id, 0));
324
- const radiusRatio = lerp(0.38, 0.92, Math.sqrt(sequence01(id, 1)));
325
- const angle = sequence01(id, 2) * Math.PI * 2;
326
- const radius = lerp(basis.endRadius, basis.startRadius, clamp01(distanceRatio)) * radiusRatio;
327
- return basis.mouth
328
- .add(basis.forward.scale(basis.length * distanceRatio + outerOffset))
329
- .addInPlace(basis.side.scale(Math.cos(angle) * radius))
330
- .addInPlace(basis.up.scale(Math.sin(angle) * radius));
331
- }
332
-
333
- function getFlyingParticleTexture(scene: Scene): DynamicTexture {
334
- const key = '__vfx_lumber_cart_suction_particle_texture';
335
- const metadata = scene.metadata && typeof scene.metadata === 'object' ? scene.metadata as Record<string, unknown> : {};
336
- scene.metadata = metadata;
337
- const existing = metadata[key] as DynamicTexture | undefined;
338
- if (existing) return existing;
339
-
340
- const texture = new DynamicTexture('lumber_cart_suction_particle_texture', { width: 64, height: 64 }, scene, true);
341
- const context = texture.getContext() as unknown as CanvasRenderingContext2D;
342
- context.clearRect(0, 0, 64, 64);
343
- const dot = context.createRadialGradient(32, 32, 1, 32, 32, 30);
344
- dot.addColorStop(0, 'rgba(255, 255, 255, 1)');
345
- dot.addColorStop(0.48, 'rgba(255, 255, 255, 0.92)');
346
- dot.addColorStop(0.78, 'rgba(255, 255, 255, 0.42)');
347
- dot.addColorStop(1, 'rgba(255, 255, 255, 0)');
348
- context.fillStyle = dot;
349
- context.beginPath();
350
- context.arc(32, 32, 30, 0, Math.PI * 2);
351
- context.fill();
352
- texture.hasAlpha = true;
353
- texture.update(false);
354
- metadata[key] = texture;
355
- return texture;
356
- }
357
-
358
- function toColor3(color: VfxColorValue): Color3 {
359
- return new Color3(clamp01(color.r), clamp01(color.g), clamp01(color.b));
360
- }
361
-
362
- function tintColor(color: VfxColorValue, brightness: number, alpha: number): Color4 {
363
- return new Color4(
364
- Math.max(0, color.r * brightness),
365
- Math.max(0, color.g * brightness),
366
- Math.max(0, color.b * brightness),
367
- clamp01(alpha),
368
- );
369
- }
370
-
371
- function lerp(min: number, max: number, t: number): number {
372
- return min + (max - min) * t;
373
- }
374
-
375
- function clamp01(value: number): number {
376
- return Math.min(1, Math.max(0, value));
377
- }
378
-
379
- function clampNumber(value: number, min: number, max: number): number {
380
- return Math.min(max, Math.max(min, value));
381
- }
382
-
383
- function fract(value: number): number {
384
- return value - Math.floor(value);
385
- }
386
-
387
- function easeInCubic(value: number): number {
388
- const t = clamp01(value);
389
- return t * t * t;
390
- }
391
-
392
- function sequence01(index: number, axis: number): number {
393
- if (index === 0) return 0.5;
394
- const strides = [0.618033988749895, 0.414213562373095, 0.732050807568877, 0.324717957244746, 0.754877666246693, 0.569840290998053];
395
- return fract(0.5 + index * strides[axis % strides.length] + axis * 0.173205080756888);
396
- }
397
-
398
- let flyingParticleSequenceIndex = 0;
399
- let flyingParticleDirectionSequenceIndex = 2048;
@@ -1,236 +0,0 @@
1
- export const runtimeId = "solidParticleScatter/v1";
2
-
3
- export const runtimeContract = {
4
- runtimeId,
5
- kind: "solidParticleScatter",
6
- fields: {
7
- count: "Number of lumber pieces emitted per burst.",
8
- scatterMode: "vertical drops pieces downward; radial throws pieces from the emitter center outward.",
9
- outwardAngleDegrees: "Radial scatter fan angle in degrees, used only when scatterMode is radial.",
10
- scatterGravity: "Higher values pull pieces down sooner and reduce hang time.",
11
- rotationStrength: "Deterministic random rotation around each lumber piece center, not around the emitter.",
12
- effectScale: "Overall spatial scale for piece size, travel distance, and spawn height.",
13
- },
14
- };
15
-
16
- function num(value, fallback = 0) {
17
- const next = Number(value);
18
- return Number.isFinite(next) ? next : fallback;
19
- }
20
-
21
- function clamp(value, min, max) {
22
- return Math.min(max, Math.max(min, value));
23
- }
24
-
25
- function fract(value) {
26
- return value - Math.floor(value);
27
- }
28
-
29
- function sequence01(index, axis) {
30
- if (index === 0) return 0.5;
31
- const strides = [0.618033988749895, 0.414213562373095, 0.732050807568877, 0.324717957244746, 0.754877666246693, 0.569840290998053];
32
- return fract(0.5 + index * strides[axis % strides.length] + axis * 0.173205080756888);
33
- }
34
-
35
- function bounceOut(t) {
36
- const n1 = 7.5625;
37
- const d1 = 2.75;
38
- if (t < 1 / d1) return n1 * t * t;
39
- if (t < 2 / d1) {
40
- const next = t - 1.5 / d1;
41
- return n1 * next * next + 0.75;
42
- }
43
- if (t < 2.5 / d1) {
44
- const next = t - 2.25 / d1;
45
- return n1 * next * next + 0.9375;
46
- }
47
- const next = t - 2.625 / d1;
48
- return n1 * next * next + 0.984375;
49
- }
50
-
51
- function lerp(start, end, t) {
52
- return start + (end - start) * t;
53
- }
54
-
55
- function readVec(BABYLON, value, fallback = new BABYLON.Vector3(0, 0, 0)) {
56
- if (!value) return fallback;
57
- return new BABYLON.Vector3(num(value.x, fallback.x), num(value.y, fallback.y), num(value.z, fallback.z));
58
- }
59
-
60
- function splitUrl(url) {
61
- const slashIndex = url.lastIndexOf("/");
62
- if (slashIndex < 0) return { rootUrl: "", fileName: url };
63
- return { rootUrl: url.slice(0, slashIndex + 1), fileName: url.slice(slashIndex + 1) };
64
- }
65
-
66
- function createFallbackSourceMesh(BABYLON, scene, name) {
67
- const source = BABYLON.MeshBuilder.CreateBox(name, { width: 0.22, height: 0.16, depth: 0.82 }, scene);
68
- source.isVisible = false;
69
- source.setEnabled(false);
70
- return source;
71
- }
72
-
73
- async function loadSourceMesh(BABYLON, scene, emitter, urlForPackagePath) {
74
- const modelPath = emitter.modelPath;
75
- if (!modelPath || !BABYLON.SceneLoader) {
76
- return createFallbackSourceMesh(BABYLON, scene, `${emitter.name || "lumber_debris_scatter"}Source`);
77
- }
78
-
79
- try {
80
- const modelUrl = urlForPackagePath ? urlForPackagePath(modelPath) : modelPath;
81
- const { rootUrl, fileName } = splitUrl(modelUrl);
82
- const result = await BABYLON.SceneLoader.ImportMeshAsync("", rootUrl, fileName, scene);
83
- const meshes = result.meshes.filter((mesh) => mesh instanceof BABYLON.Mesh && mesh.getTotalVertices() > 0);
84
- if (!meshes.length) throw new Error(`No mesh was found in ${modelPath}.`);
85
- const source = meshes.length === 1 ? meshes[0] : BABYLON.Mesh.MergeMeshes(meshes, true, true, undefined, false, true);
86
- if (!source) throw new Error(`Unable to merge meshes from ${modelPath}.`);
87
- source.name = `${emitter.name || "lumber_debris_scatter"}ModelSource`;
88
- source.isVisible = false;
89
- source.setEnabled(false);
90
- source.refreshBoundingInfo();
91
- const size = source.getBoundingInfo().boundingBox.extendSize.scale(2);
92
- const maxDimension = Math.max(size.x, size.y, size.z, 0.0001);
93
- source.scaling.scaleInPlace(0.82 / maxDimension);
94
- source.rotationQuaternion = null;
95
- source.refreshBoundingInfo();
96
- return source;
97
- } catch (error) {
98
- console.warn("Unable to load lumber debris model; using box fallback.", error);
99
- return createFallbackSourceMesh(BABYLON, scene, `${emitter.name || "lumber_debris_scatter"}Source`);
100
- }
101
- }
102
-
103
- function createState(BABYLON, index, count, origin, emitter) {
104
- const effectScale = Math.max(0.01, num(emitter.effectScale, 1));
105
- const resourceScale = Math.max(0.01, num(emitter.resourceScale, 1));
106
- const speedScale = Math.max(0.1, num(emitter.speedScale, 1));
107
- const duration = Math.max(0.08, num(emitter.duration, 0.74)) / speedScale;
108
- const interval = Math.max(0, num(emitter.intervalMs, 18)) / 1000;
109
- const mode = emitter.scatterMode === "vertical" ? "vertical" : "radial";
110
- const spread = Math.max(0, num(emitter.spread, 0.88)) * effectScale;
111
- const gravity = Math.max(0.05, num(emitter.scatterGravity, 1));
112
- const rotationStrength = Math.max(0, num(emitter.rotationStrength, 1));
113
- const outwardAngleRadians = clamp(num(emitter.outwardAngleDegrees, 240), 0, 360) * (Math.PI / 180);
114
- const emissionDirection = readVec(BABYLON, emitter.emissionDirection, new BABYLON.Vector3(1, 0, 0));
115
-
116
- const angleSeed = sequence01(index, 0);
117
- const radiusSeed = Math.sqrt(sequence01(index, 1));
118
- const sideSeed = sequence01(index, 2) < 0.5 ? -1 : 1;
119
- emissionDirection.y = 0;
120
- if (emissionDirection.lengthSquared() < 0.0001) emissionDirection.set(1, 0, 0);
121
- emissionDirection.normalize();
122
-
123
- const baseAngle = Math.atan2(emissionDirection.z, emissionDirection.x);
124
- const radialAngle = mode === "radial" ? baseAngle + (angleSeed - 0.5) * outwardAngleRadians : angleSeed * Math.PI * 2;
125
- const scatterRadius = spread * (0.35 + radiusSeed * 0.65);
126
- const localX = Math.cos(radialAngle) * scatterRadius;
127
- const localZ = Math.sin(radialAngle) * scatterRadius;
128
- const startPos = mode === "radial"
129
- ? new BABYLON.Vector3(origin.x + emissionDirection.x * spread * 0.08, origin.y + 0.34 * effectScale, origin.z + emissionDirection.z * spread * 0.08)
130
- : new BABYLON.Vector3(origin.x + localX * 0.52, origin.y + (0.72 + sequence01(index, 3) * 0.24) * effectScale, origin.z + localZ * 0.52);
131
- const targetPos = mode === "radial"
132
- ? new BABYLON.Vector3(origin.x + localX, origin.y, origin.z + localZ)
133
- : new BABYLON.Vector3(origin.x + localX * 0.52, origin.y, origin.z + localZ * 0.52);
134
- const rotationBase = Math.PI * 2 * rotationStrength;
135
-
136
- return {
137
- active: index < count,
138
- startPos,
139
- targetPos,
140
- startRotation: new BABYLON.Vector3((sequence01(index, 4) - 0.5) * rotationBase, (sequence01(index, 5) - 0.5) * rotationBase, (sequence01(index, 6) - 0.5) * rotationBase),
141
- rotationDelta: new BABYLON.Vector3(sideSeed * (2.6 + sequence01(index, 7) * 2.2) * rotationStrength, (sequence01(index, 8) - 0.5) * 7.2 * rotationStrength, sideSeed * (1.7 + sequence01(index, 9) * 2.8) * rotationStrength),
142
- duration: duration * (0.88 + sequence01(index, 10) * 0.18),
143
- elapsed: -index * interval,
144
- gravity,
145
- scale: resourceScale * effectScale * (0.68 + sequence01(index, 11) * 0.24),
146
- };
147
- }
148
-
149
- export async function createSolidParticleScatterRuntime({ BABYLON, scene, emitter, urlForPackagePath, shouldStart = false }) {
150
- if (!BABYLON) throw new Error("createSolidParticleScatterRuntime requires BABYLON.");
151
- if (!scene) throw new Error("createSolidParticleScatterRuntime requires scene.");
152
- if (!emitter) throw new Error("createSolidParticleScatterRuntime requires emitter.");
153
-
154
- const count = Math.max(0, Math.round(num(emitter.count ?? emitter.particleCount, 18)));
155
- const capacity = Math.max(8, Math.round(num(emitter.capacity, count)));
156
- const origin = readVec(BABYLON, emitter.origin, BABYLON.Vector3.Zero());
157
- const sps = new BABYLON.SolidParticleSystem(emitter.name || "lumber_debris_scatter", scene, { updatable: true, isPickable: false });
158
- const source = await loadSourceMesh(BABYLON, scene, emitter, urlForPackagePath);
159
- sps.addShape(source, capacity);
160
- source.dispose();
161
-
162
- const mesh = sps.buildMesh();
163
- mesh.isPickable = false;
164
- mesh.alwaysSelectAsActiveMesh = true;
165
- const material = new BABYLON.StandardMaterial(`${emitter.name || "lumber_debris_scatter"}Material`, scene);
166
- material.diffuseColor = new BABYLON.Color3(0.52, 0.31, 0.13);
167
- material.emissiveColor = new BABYLON.Color3(0.08, 0.045, 0.02);
168
- material.specularColor = BABYLON.Color3.Black();
169
- mesh.material = material;
170
-
171
- const states = Array.from({ length: capacity }, (_, index) => createState(BABYLON, index, count, origin, emitter));
172
- let activeCount = Math.min(count, capacity);
173
- let elapsed = 0;
174
- let started = false;
175
- const disposeAfter = Math.max(...states.map((state) => Math.max(0, -state.elapsed) + state.duration), 0) + 0.35;
176
-
177
- sps.updateParticle = (particle) => {
178
- const state = states[particle.idx];
179
- if (!state?.active || state.elapsed < 0) {
180
- particle.position.set(0, -9999, 0);
181
- particle.scaling.set(0, 0, 0);
182
- return particle;
183
- }
184
- const t = clamp(state.elapsed / state.duration, 0, 1);
185
- const horizontalT = bounceOut(t);
186
- const gravityT = 1 - Math.pow(1 - t, 1 + state.gravity * 1.15);
187
- const lift = Math.sin(Math.PI * t) * (0.32 / Math.max(0.25, state.gravity));
188
- particle.position.set(lerp(state.startPos.x, state.targetPos.x, horizontalT), lerp(state.startPos.y, state.targetPos.y, gravityT) + lift, lerp(state.startPos.z, state.targetPos.z, horizontalT));
189
- particle.rotation.set(state.startRotation.x + state.rotationDelta.x * t, state.startRotation.y + state.rotationDelta.y * t, state.startRotation.z + state.rotationDelta.z * t);
190
- particle.scaling.set(state.scale, state.scale, state.scale);
191
- return particle;
192
- };
193
- sps.setParticles();
194
-
195
- let observer = null;
196
- const start = () => {
197
- if (started) return;
198
- started = true;
199
- observer = scene.onBeforeRenderObservable.add(() => {
200
- const deltaTime = scene.getEngine().getDeltaTime() / 1000;
201
- elapsed += deltaTime;
202
- for (const state of states) {
203
- if (!state.active) continue;
204
- state.elapsed += deltaTime;
205
- if (state.elapsed >= state.duration) {
206
- state.active = false;
207
- activeCount = Math.max(0, activeCount - 1);
208
- }
209
- }
210
- sps.setParticles();
211
- if (activeCount <= 0 && elapsed >= disposeAfter) preview.dispose();
212
- });
213
- };
214
-
215
- const preview = {
216
- start,
217
- stop: () => {
218
- if (observer) scene.onBeforeRenderObservable.remove(observer);
219
- observer = null;
220
- started = false;
221
- },
222
- dispose: () => {
223
- if (observer) scene.onBeforeRenderObservable.remove(observer);
224
- observer = null;
225
- sps.dispose();
226
- material.dispose();
227
- },
228
- isStarted: () => started,
229
- duration: disposeAfter,
230
- mesh,
231
- sps,
232
- };
233
-
234
- if (shouldStart) start();
235
- return preview;
236
- }