@haiyue/extensions 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/animation/Animation2DComponent.d.ts +46 -0
  4. package/dist/animation/Animation2DExtensionRegistry.d.ts +27 -0
  5. package/dist/animation/Animation2DRenderSystem.d.ts +89 -0
  6. package/dist/animation/Animation2DRuntime.d.ts +99 -0
  7. package/dist/animation/Animation2DStateMachine.d.ts +13 -0
  8. package/dist/animation/Animation2DStateMachineComponent.d.ts +54 -0
  9. package/dist/animation/Animation2DStateMachineRuntime.d.ts +30 -0
  10. package/dist/animation/Animation2DStateMachineSystem.d.ts +14 -0
  11. package/dist/animation/Animation2DStateMachineVisualRuntime.d.ts +27 -0
  12. package/dist/animation/Animation2DSystem.d.ts +14 -0
  13. package/dist/animation/AnimationAssetLoader.d.ts +9 -0
  14. package/dist/animation/AnimationAudioClip.d.ts +26 -0
  15. package/dist/animation/AnimationPathTessellator.d.ts +52 -0
  16. package/dist/animation/AnimationTextRasterizer.d.ts +21 -0
  17. package/dist/animation/AnimationVisual2D.d.ts +68 -0
  18. package/dist/animation/HyaAnimation2DClipAdapter.d.ts +16 -0
  19. package/dist/animation/Particle2DDescriptorAdapter.d.ts +5 -0
  20. package/dist/animation/runtime/mixer/Action.d.ts +150 -0
  21. package/dist/animation/runtime/mixer/Mixer.d.ts +54 -0
  22. package/dist/animation/runtime/mixer/PoseBuffer.d.ts +23 -0
  23. package/dist/animation/runtime/mixer/Sampler.d.ts +17 -0
  24. package/dist/animation/runtime/mixer/Types.d.ts +101 -0
  25. package/dist/animation/runtime/mixer/index.d.ts +6 -0
  26. package/dist/animation-state-machine/AnimationStateMachine.d.ts +111 -0
  27. package/dist/animation-state-machine/AnimationStateMachineChannels.d.ts +23 -0
  28. package/dist/animation-state-machine/runtime/Animation2DStateMachineMixerAdapter.d.ts +51 -0
  29. package/dist/animation-state-machine/runtime/AnimationStateMachineBlendTreeWeights.d.ts +14 -0
  30. package/dist/animation-state-machine/runtime/AnimationStateMachineCompiler.d.ts +109 -0
  31. package/dist/animation-state-machine/runtime/AnimationStateMachineController.d.ts +103 -0
  32. package/dist/animation-state-machine/runtime/AnimationStateMachineMixerPort.d.ts +35 -0
  33. package/dist/animation-state-machine/runtime/index.d.ts +7 -0
  34. package/dist/animation.d.ts +15 -0
  35. package/dist/animation.js +1171 -0
  36. package/dist/animation3d/Animation3DAction.d.ts +46 -0
  37. package/dist/animation3d/Animation3DBinding.d.ts +85 -0
  38. package/dist/animation3d/Animation3DClip.d.ts +20 -0
  39. package/dist/animation3d/Animation3DError.d.ts +13 -0
  40. package/dist/animation3d/Animation3DMixer.d.ts +27 -0
  41. package/dist/animation3d/Animation3DPose.d.ts +40 -0
  42. package/dist/animation3d/Animation3DPoseBuffer.d.ts +24 -0
  43. package/dist/animation3d/Animation3DResource.d.ts +28 -0
  44. package/dist/animation3d/Animation3DStateMachine.d.ts +95 -0
  45. package/dist/animation3d/Animation3DStateMachineRuntime.d.ts +70 -0
  46. package/dist/animation3d/Animation3DTrack.d.ts +19 -0
  47. package/dist/animation3d/hya/HyaAnimation3DRuntime.d.ts +73 -0
  48. package/dist/animation3d/hya/HyaAnimation3DStateMachineRuntime.d.ts +65 -0
  49. package/dist/animation3d/hya/HyaAnimation3DTypes.d.ts +170 -0
  50. package/dist/animation3d/hya/index.d.ts +4 -0
  51. package/dist/animation3d/index.d.ts +14 -0
  52. package/dist/animation3d/runtime/integration/Animation3DStateMachineMixerAdapter.d.ts +81 -0
  53. package/dist/animation3d/runtime/integration/Animation3DStateMachineMixerIntegration.d.ts +39 -0
  54. package/dist/animation3d/runtime/integration/index.d.ts +2 -0
  55. package/dist/animation3d/runtime/mixer/Action.d.ts +150 -0
  56. package/dist/animation3d/runtime/mixer/Animation3DMixerRuntimeStore.d.ts +6 -0
  57. package/dist/animation3d/runtime/mixer/Mixer.d.ts +59 -0
  58. package/dist/animation3d/runtime/mixer/PoseBuffer.d.ts +1 -0
  59. package/dist/animation3d/runtime/mixer/TrackSampler.d.ts +24 -0
  60. package/dist/animation3d/runtime/mixer/index.d.ts +4 -0
  61. package/dist/animation3d/runtime/state-machine/Animation3DBlendTreeWeights.d.ts +1 -0
  62. package/dist/animation3d/runtime/state-machine/Animation3DStateMachineCompiler.d.ts +1 -0
  63. package/dist/animation3d/runtime/state-machine/Animation3DStateMachineController.d.ts +1 -0
  64. package/dist/animation3d/runtime/state-machine/Animation3DStateMachineMixerPort.d.ts +1 -0
  65. package/dist/animation3d/runtime/state-machine/index.d.ts +6 -0
  66. package/dist/animation3d.d.ts +5 -0
  67. package/dist/animation3d.js +1708 -0
  68. package/dist/benchmark.d.ts +30 -0
  69. package/dist/benchmark.js +119 -0
  70. package/dist/canvas-text/CanvasText2DRenderSystem.d.ts +24 -0
  71. package/dist/canvas-text/CanvasTextComponent.d.ts +18 -0
  72. package/dist/canvas-text.d.ts +4 -0
  73. package/dist/canvas-text.js +276 -0
  74. package/dist/chunks/2d-ui-animation-2d.generated-D4aHzFU2.js +4 -0
  75. package/dist/chunks/2d-ui-canvas-text-2d.generated-C0Eaej2m.js +4 -0
  76. package/dist/chunks/2d-ui-spine2d.generated-CUUi8xjl.js +4 -0
  77. package/dist/chunks/2d-ui-tilemap2d.generated-gebw4mX7.js +4 -0
  78. package/dist/chunks/Animation2DStateMachine-9IonguNa.js +3245 -0
  79. package/dist/chunks/AnimationStateMachineController-BIcAh-sT.js +1438 -0
  80. package/dist/chunks/GltfAnimation3DAdapter-Cu3rQQsW.js +2240 -0
  81. package/dist/chunks/GltfAnimationRuntime-CRHsLycN.js +631 -0
  82. package/dist/chunks/RenderSystem2DBase-DDT61dAu.js +227 -0
  83. package/dist/chunks/Spine2DRuntime-D9M9LAQR.js +3056 -0
  84. package/dist/chunks/SpineAssetParser-BSDmPge1.js +378 -0
  85. package/dist/chunks/gltfLoader-Dm22jLYB.js +2085 -0
  86. package/dist/chunks/pluginUtils-CvwroRMd.js +33 -0
  87. package/dist/experimental-gltf-worker.d.ts +4 -0
  88. package/dist/experimental-gltf-worker.js +138 -0
  89. package/dist/experimental-spine-worker.d.ts +4 -0
  90. package/dist/experimental-spine-worker.js +110 -0
  91. package/dist/gltf/GltfAccessorReader.d.ts +7 -0
  92. package/dist/gltf/GltfAnimation3DAdapter.d.ts +47 -0
  93. package/dist/gltf/GltfAnimationRuntime.d.ts +10 -0
  94. package/dist/gltf/GltfAssetWorkerClient.d.ts +20 -0
  95. package/dist/gltf/GltfCompatibilityReport.d.ts +70 -0
  96. package/dist/gltf/GltfConservativeBounds.d.ts +18 -0
  97. package/dist/gltf/GltfDracoDecoder.d.ts +4 -0
  98. package/dist/gltf/GltfExtensionAdapter.d.ts +54 -0
  99. package/dist/gltf/GltfLoaderContract.d.ts +249 -0
  100. package/dist/gltf/GltfLoaderErrors.d.ts +4 -0
  101. package/dist/gltf/GltfMaterialDescriptor.d.ts +23 -0
  102. package/dist/gltf/GltfMaterialEncoder.d.ts +20 -0
  103. package/dist/gltf/GltfMaterialLoader.d.ts +6 -0
  104. package/dist/gltf/GltfModelComponent.d.ts +72 -0
  105. package/dist/gltf/GltfModelSystem.d.ts +37 -0
  106. package/dist/gltf/GltfPlugin.d.ts +6 -0
  107. package/dist/gltf/GltfSchema.d.ts +225 -0
  108. package/dist/gltf/GltfUvSemanticPlanner.d.ts +33 -0
  109. package/dist/gltf/gltfLoader.d.ts +9 -0
  110. package/dist/gltf-animation3d.d.ts +3 -0
  111. package/dist/gltf-animation3d.js +8 -0
  112. package/dist/gltf-worker-runtime.d.ts +7 -0
  113. package/dist/gltf-worker-runtime.js +6981 -0
  114. package/dist/gltf.d.ts +12 -0
  115. package/dist/gltf.js +517 -0
  116. package/dist/grid/Grid2DComponent.d.ts +25 -0
  117. package/dist/grid.d.ts +2 -0
  118. package/dist/grid.js +56 -0
  119. package/dist/hya-state-machine.d.ts +7 -0
  120. package/dist/hya-state-machine.js +2934 -0
  121. package/dist/index.d.ts +2 -0
  122. package/dist/index.js +7 -0
  123. package/dist/internal/2d-ui-shader-artifact.js +870 -0
  124. package/dist/plugins/pluginUtils.d.ts +20 -0
  125. package/dist/plugins.d.ts +6 -0
  126. package/dist/shaders/generated/2d-ui-artifact.generated.d.ts +658 -0
  127. package/dist/spine/Spine2DComponent.d.ts +116 -0
  128. package/dist/spine/Spine2DGpuRenderer.d.ts +65 -0
  129. package/dist/spine/Spine2DRenderSystem.d.ts +27 -0
  130. package/dist/spine/Spine2DRuntime.d.ts +103 -0
  131. package/dist/spine/SpineAnimationBoneTimelines.d.ts +8 -0
  132. package/dist/spine/SpineAssetParser.d.ts +18 -0
  133. package/dist/spine/SpineAssetWorkerClient.d.ts +19 -0
  134. package/dist/spine/SpineAssetWorkerContract.d.ts +6 -0
  135. package/dist/spine/SpineAtlasParser.d.ts +21 -0
  136. package/dist/spine/SpineFloatBuilder.d.ts +15 -0
  137. package/dist/spine/SpinePathConstraintSolver.d.ts +32 -0
  138. package/dist/spine/SpinePlugin.d.ts +8 -0
  139. package/dist/spine/SpineSkeletonRuntime.d.ts +185 -0
  140. package/dist/spine/SpineSlotAnimation.d.ts +17 -0
  141. package/dist/spine/SpineTimelineRuntime.d.ts +25 -0
  142. package/dist/spine/SpineVertexBuilder.d.ts +61 -0
  143. package/dist/spine.d.ts +7 -0
  144. package/dist/spine.js +578 -0
  145. package/dist/tilemap/Tilemap2DComponent.d.ts +110 -0
  146. package/dist/tilemap/Tilemap2DRenderSystem.d.ts +11 -0
  147. package/dist/tilemap/Tilemap2DRenderer.d.ts +24 -0
  148. package/dist/tilemap/TilemapPlugin.d.ts +8 -0
  149. package/dist/tilemap.d.ts +6 -0
  150. package/dist/tilemap.js +535 -0
  151. package/dist/tween/Tween2DComponent.d.ts +70 -0
  152. package/dist/tween/Tween2DSystem.d.ts +12 -0
  153. package/dist/tween.d.ts +4 -0
  154. package/dist/tween.js +138 -0
  155. package/dist/utils/RenderSystem2DBase.d.ts +42 -0
  156. package/dist/utils/arrayAccess.d.ts +4 -0
  157. package/dist/utils/camera2d.d.ts +12 -0
  158. package/dist/utils/render2dGpu.d.ts +32 -0
  159. package/package.json +124 -0
  160. package/src/animation/Animation2DComponent.ts +135 -0
  161. package/src/animation/Animation2DExtensionRegistry.ts +52 -0
  162. package/src/animation/Animation2DRenderSystem.ts +1026 -0
  163. package/src/animation/Animation2DRuntime.ts +1326 -0
  164. package/src/animation/Animation2DStateMachine.ts +39 -0
  165. package/src/animation/Animation2DStateMachineComponent.ts +228 -0
  166. package/src/animation/Animation2DStateMachineRuntime.ts +141 -0
  167. package/src/animation/Animation2DStateMachineSystem.ts +60 -0
  168. package/src/animation/Animation2DStateMachineVisualRuntime.ts +282 -0
  169. package/src/animation/Animation2DSystem.ts +72 -0
  170. package/src/animation/AnimationAssetLoader.ts +84 -0
  171. package/src/animation/AnimationAudioClip.ts +140 -0
  172. package/src/animation/AnimationPathTessellator.ts +691 -0
  173. package/src/animation/AnimationTextRasterizer.ts +340 -0
  174. package/src/animation/AnimationVisual2D.ts +153 -0
  175. package/src/animation/HyaAnimation2DClipAdapter.ts +845 -0
  176. package/src/animation/Particle2DDescriptorAdapter.ts +35 -0
  177. package/src/animation/runtime/mixer/Action.ts +876 -0
  178. package/src/animation/runtime/mixer/Mixer.ts +505 -0
  179. package/src/animation/runtime/mixer/PoseBuffer.ts +105 -0
  180. package/src/animation/runtime/mixer/Sampler.ts +252 -0
  181. package/src/animation/runtime/mixer/Types.ts +138 -0
  182. package/src/animation/runtime/mixer/index.ts +31 -0
  183. package/src/animation-state-machine/AnimationStateMachine.ts +146 -0
  184. package/src/animation-state-machine/AnimationStateMachineChannels.ts +116 -0
  185. package/src/animation-state-machine/runtime/Animation2DStateMachineMixerAdapter.ts +197 -0
  186. package/src/animation-state-machine/runtime/AnimationStateMachineBlendTreeWeights.ts +503 -0
  187. package/src/animation-state-machine/runtime/AnimationStateMachineCompiler.ts +763 -0
  188. package/src/animation-state-machine/runtime/AnimationStateMachineController.ts +987 -0
  189. package/src/animation-state-machine/runtime/AnimationStateMachineMixerPort.ts +54 -0
  190. package/src/animation-state-machine/runtime/index.ts +47 -0
  191. package/src/animation.ts +33 -0
  192. package/src/animation3d/Animation3DAction.ts +73 -0
  193. package/src/animation3d/Animation3DBinding.ts +116 -0
  194. package/src/animation3d/Animation3DClip.ts +22 -0
  195. package/src/animation3d/Animation3DError.ts +34 -0
  196. package/src/animation3d/Animation3DMixer.ts +82 -0
  197. package/src/animation3d/Animation3DPose.ts +100 -0
  198. package/src/animation3d/Animation3DPoseBuffer.ts +85 -0
  199. package/src/animation3d/Animation3DResource.ts +49 -0
  200. package/src/animation3d/Animation3DStateMachine.ts +126 -0
  201. package/src/animation3d/Animation3DStateMachineRuntime.ts +191 -0
  202. package/src/animation3d/Animation3DTrack.ts +29 -0
  203. package/src/animation3d/hya/HyaAnimation3DRuntime.ts +756 -0
  204. package/src/animation3d/hya/HyaAnimation3DStateMachineRuntime.ts +618 -0
  205. package/src/animation3d/hya/HyaAnimation3DTypes.ts +168 -0
  206. package/src/animation3d/hya/index.ts +10 -0
  207. package/src/animation3d/index.ts +76 -0
  208. package/src/animation3d/runtime/integration/Animation3DStateMachineMixerAdapter.ts +325 -0
  209. package/src/animation3d/runtime/integration/Animation3DStateMachineMixerIntegration.ts +158 -0
  210. package/src/animation3d/runtime/integration/index.ts +9 -0
  211. package/src/animation3d/runtime/mixer/Action.ts +833 -0
  212. package/src/animation3d/runtime/mixer/Animation3DMixerRuntimeStore.ts +26 -0
  213. package/src/animation3d/runtime/mixer/Mixer.ts +562 -0
  214. package/src/animation3d/runtime/mixer/PoseBuffer.ts +1 -0
  215. package/src/animation3d/runtime/mixer/TrackSampler.ts +399 -0
  216. package/src/animation3d/runtime/mixer/index.ts +8 -0
  217. package/src/animation3d/runtime/state-machine/Animation3DBlendTreeWeights.ts +4 -0
  218. package/src/animation3d/runtime/state-machine/Animation3DStateMachineCompiler.ts +1 -0
  219. package/src/animation3d/runtime/state-machine/Animation3DStateMachineController.ts +1 -0
  220. package/src/animation3d/runtime/state-machine/Animation3DStateMachineMixerPort.ts +1 -0
  221. package/src/animation3d/runtime/state-machine/index.ts +45 -0
  222. package/src/animation3d.ts +5 -0
  223. package/src/benchmark.ts +168 -0
  224. package/src/canvas-text/CanvasText2DRenderSystem.ts +292 -0
  225. package/src/canvas-text/CanvasTextComponent.ts +47 -0
  226. package/src/canvas-text.ts +4 -0
  227. package/src/experimental-gltf-worker.ts +18 -0
  228. package/src/experimental-spine-worker.ts +9 -0
  229. package/src/gltf/GltfAccessorReader.ts +271 -0
  230. package/src/gltf/GltfAnimation3DAdapter.ts +680 -0
  231. package/src/gltf/GltfAnimationRuntime.ts +392 -0
  232. package/src/gltf/GltfAssetWorkerClient.ts +136 -0
  233. package/src/gltf/GltfCompatibilityReport.ts +214 -0
  234. package/src/gltf/GltfConservativeBounds.ts +219 -0
  235. package/src/gltf/GltfDracoDecoder.ts +251 -0
  236. package/src/gltf/GltfExtensionAdapter.ts +340 -0
  237. package/src/gltf/GltfLoaderContract.ts +333 -0
  238. package/src/gltf/GltfLoaderErrors.ts +59 -0
  239. package/src/gltf/GltfMaterialDescriptor.ts +134 -0
  240. package/src/gltf/GltfMaterialEncoder.ts +173 -0
  241. package/src/gltf/GltfMaterialLoader.ts +293 -0
  242. package/src/gltf/GltfModelComponent.ts +115 -0
  243. package/src/gltf/GltfModelSystem.ts +208 -0
  244. package/src/gltf/GltfPlugin.ts +84 -0
  245. package/src/gltf/GltfSchema.ts +229 -0
  246. package/src/gltf/GltfUvSemanticPlanner.ts +166 -0
  247. package/src/gltf/gltfLoader.ts +910 -0
  248. package/src/gltf-animation3d.ts +10 -0
  249. package/src/gltf-worker-runtime.ts +10 -0
  250. package/src/gltf.ts +56 -0
  251. package/src/grid/Grid2DComponent.ts +69 -0
  252. package/src/grid.ts +2 -0
  253. package/src/hya-state-machine.ts +19 -0
  254. package/src/index.ts +2 -0
  255. package/src/plugins/pluginUtils.ts +50 -0
  256. package/src/plugins.ts +6 -0
  257. package/src/shaders/generated/2d-ui-animation-2d.generated.wgsl +242 -0
  258. package/src/shaders/generated/2d-ui-artifact.generated.ts +867 -0
  259. package/src/shaders/generated/2d-ui-canvas-text-2d.generated.wgsl +38 -0
  260. package/src/shaders/generated/2d-ui-spine2d.generated.wgsl +41 -0
  261. package/src/shaders/generated/2d-ui-tilemap2d.generated.wgsl +36 -0
  262. package/src/spine/Spine2DComponent.ts +168 -0
  263. package/src/spine/Spine2DGpuRenderer.ts +449 -0
  264. package/src/spine/Spine2DRenderSystem.ts +199 -0
  265. package/src/spine/Spine2DRuntime.ts +1529 -0
  266. package/src/spine/SpineAnimationBoneTimelines.ts +30 -0
  267. package/src/spine/SpineAssetParser.ts +20 -0
  268. package/src/spine/SpineAssetWorkerClient.ts +112 -0
  269. package/src/spine/SpineAssetWorkerContract.ts +4 -0
  270. package/src/spine/SpineAtlasParser.ts +127 -0
  271. package/src/spine/SpineFloatBuilder.ts +66 -0
  272. package/src/spine/SpinePathConstraintSolver.ts +582 -0
  273. package/src/spine/SpinePlugin.ts +76 -0
  274. package/src/spine/SpineSkeletonRuntime.ts +521 -0
  275. package/src/spine/SpineSlotAnimation.ts +195 -0
  276. package/src/spine/SpineTimelineRuntime.ts +453 -0
  277. package/src/spine/SpineVertexBuilder.ts +686 -0
  278. package/src/spine.ts +7 -0
  279. package/src/tilemap/Tilemap2DComponent.ts +232 -0
  280. package/src/tilemap/Tilemap2DRenderSystem.ts +58 -0
  281. package/src/tilemap/Tilemap2DRenderer.ts +289 -0
  282. package/src/tilemap/TilemapPlugin.ts +75 -0
  283. package/src/tilemap.ts +6 -0
  284. package/src/tween/Tween2DComponent.ts +83 -0
  285. package/src/tween/Tween2DSystem.ts +78 -0
  286. package/src/tween.ts +4 -0
  287. package/src/types/earcut.d.ts +7 -0
  288. package/src/types/wgsl.d.ts +4 -0
  289. package/src/utils/RenderSystem2DBase.ts +164 -0
  290. package/src/utils/arrayAccess.ts +17 -0
  291. package/src/utils/camera2d.ts +68 -0
  292. package/src/utils/render2dGpu.ts +132 -0
  293. package/test/fixtures/gltf/animation-characterization.gltf +344 -0
@@ -0,0 +1,618 @@
1
+ import { ParticleEmitter3D } from '@haiyue/engine/components';
2
+ import type { AnimationStateMachineDefinition } from '../../animation-state-machine/AnimationStateMachine.js';
3
+ import {
4
+ AnimationStateMachineController,
5
+ compileAnimationStateMachine,
6
+ runAnimationStateMachineControllerUpdateTransaction,
7
+ type AnimationStateMachineActionOptions,
8
+ type AnimationStateMachineMixerPort,
9
+ } from '../../animation-state-machine/runtime/index.js';
10
+ import type { Animation3DClip } from '../Animation3DClip.js';
11
+ import type { Animation3DPose } from '../Animation3DPose.js';
12
+ import { Animation3DPoseBuffer } from '../Animation3DPoseBuffer.js';
13
+ import type { Animation3DStateMachineDefinition } from '../Animation3DStateMachine.js';
14
+ import {
15
+ Animation3DStateMachineMixerAdapter,
16
+ type Animation3DStateMachineActionRuntimeHandle,
17
+ } from '../runtime/integration/Animation3DStateMachineMixerAdapter.js';
18
+ import { Animation3DMixerRuntime } from '../runtime/mixer/Mixer.js';
19
+
20
+ const TIME_EPSILON = 1e-9;
21
+
22
+ export interface HyaAnimation3DStateMachinePartition {
23
+ readonly id: string;
24
+ readonly mixer: Animation3DMixerRuntime;
25
+ readonly clips: ReadonlyMap<string, Animation3DClip>;
26
+ /** Maps authored HYA binding ids to a model runtime's source binding ids. */
27
+ readonly bindingIds?: ReadonlyMap<string, string>;
28
+ readonly pose?: Animation3DPoseBuffer;
29
+ readonly apply: (pose: Animation3DPose) => void;
30
+ }
31
+
32
+ export interface HyaAnimation3DParticleCue {
33
+ readonly key: string;
34
+ readonly emitter: ParticleEmitter3D;
35
+ readonly start: number;
36
+ readonly end: number;
37
+ }
38
+
39
+ export interface HyaAnimation3DStateMachineRuntimeOptions {
40
+ readonly definition: AnimationStateMachineDefinition | Animation3DStateMachineDefinition;
41
+ readonly clips: readonly Animation3DClip[];
42
+ readonly partitions: readonly HyaAnimation3DStateMachinePartition[];
43
+ readonly particleCues?: ReadonlyMap<string, readonly HyaAnimation3DParticleCue[]>;
44
+ }
45
+
46
+ interface PartitionRuntime {
47
+ readonly id: string;
48
+ readonly mixer: Animation3DMixerRuntime;
49
+ readonly adapter: Animation3DStateMachineMixerAdapter;
50
+ readonly clips: ReadonlyMap<string, Animation3DClip>;
51
+ readonly bindingIds: ReadonlyMap<string, string> | undefined;
52
+ readonly pose: Animation3DPoseBuffer;
53
+ readonly apply: (pose: Animation3DPose) => void;
54
+ previousTime: number;
55
+ frameCompleted: boolean;
56
+ }
57
+
58
+ interface ParticleCommand {
59
+ readonly key: string;
60
+ readonly kind: 'start' | 'stop';
61
+ }
62
+
63
+ interface GroupCheckpoint {
64
+ readonly targetTime: number;
65
+ readonly traversal: number;
66
+ readonly playing: boolean;
67
+ readonly activeCueKeys: ReadonlySet<string>;
68
+ }
69
+
70
+ export class HyaAnimation3DStateMachineActionHandle {
71
+ readonly children: readonly Animation3DStateMachineActionRuntimeHandle[];
72
+ readonly activeCueKeys = new Set<string>();
73
+ targetTime = 0;
74
+ traversal = 0;
75
+ playing = false;
76
+ destroyed = false;
77
+ pendingDestroy = false;
78
+
79
+ constructor(
80
+ readonly id: string,
81
+ readonly clipId: string,
82
+ readonly duration: number,
83
+ readonly loop: AnimationStateMachineActionOptions['loop'],
84
+ children: readonly Animation3DStateMachineActionRuntimeHandle[],
85
+ ) {
86
+ this.children = Object.freeze([...children]);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * One controller-facing port spanning the scene mixer and every loaded model
92
+ * mixer. It is the only state-machine clock; child adapters only synchronize
93
+ * existing mixer actions and participate in the same rollback boundary.
94
+ */
95
+ class HyaAnimation3DStateMachineMixerPort implements
96
+ AnimationStateMachineMixerPort<HyaAnimation3DStateMachineActionHandle> {
97
+ private readonly _clips = new Map<string, Animation3DClip>();
98
+ private readonly _particles = new Map<string, ParticleEmitter3D>();
99
+ private readonly _owners = new Map<string, string[]>();
100
+ private readonly _handles = new Set<HyaAnimation3DStateMachineActionHandle>();
101
+ private readonly _transactionCreated: HyaAnimation3DStateMachineActionHandle[] = [];
102
+ private readonly _transactionPendingDestroy: HyaAnimation3DStateMachineActionHandle[] = [];
103
+ private readonly _transactionCheckpoints = new Map<HyaAnimation3DStateMachineActionHandle, GroupCheckpoint>();
104
+ private readonly _transactionCommands: ParticleCommand[] = [];
105
+ private _transactionOwners = new Map<string, string[]>();
106
+ private _nextHandleId = 1;
107
+ private _transactionNextHandleId = 1;
108
+ private _transactionActive = false;
109
+ private _destroyed = false;
110
+
111
+ constructor(
112
+ readonly partitions: readonly PartitionRuntime[],
113
+ clips: readonly Animation3DClip[],
114
+ private readonly _particleCues: ReadonlyMap<string, readonly HyaAnimation3DParticleCue[]>,
115
+ ) {
116
+ for (const clip of clips) this._clips.set(clip.id, clip);
117
+ for (const cues of _particleCues.values()) {
118
+ for (const cue of cues) this._particles.set(cue.key, cue.emitter);
119
+ }
120
+ }
121
+
122
+ get liveActionCount(): number { return this._handles.size; }
123
+
124
+ get sideEffectOwnerCount(): number {
125
+ let count = 0;
126
+ for (const owners of this._owners.values()) count += owners.length;
127
+ return count;
128
+ }
129
+
130
+ createAction(
131
+ clipId: string,
132
+ options: AnimationStateMachineActionOptions,
133
+ ): HyaAnimation3DStateMachineActionHandle {
134
+ this._requireActive();
135
+ const clip = this._clips.get(clipId);
136
+ if (!clip) throw new RangeError(`Unknown HYA Animation3D state-machine clip "${clipId}".`);
137
+ const children: Animation3DStateMachineActionRuntimeHandle[] = [];
138
+ try {
139
+ for (const partition of this.partitions) {
140
+ if (partition.clips.has(clipId)) {
141
+ children.push(partition.adapter.createAction(
142
+ clipId,
143
+ translateActionOptions(options, partition.bindingIds),
144
+ ));
145
+ }
146
+ }
147
+ } catch (error) {
148
+ let childIndex = 0;
149
+ for (const partition of this.partitions) {
150
+ if (!partition.clips.has(clipId) || childIndex >= children.length) continue;
151
+ partition.adapter.destroyAction(children[childIndex++]!);
152
+ }
153
+ throw error;
154
+ }
155
+ const handle = new HyaAnimation3DStateMachineActionHandle(
156
+ `hya-animation3d-state-machine:${this._nextHandleId++}`,
157
+ clipId,
158
+ clip.duration,
159
+ options.loop,
160
+ children,
161
+ );
162
+ this._handles.add(handle);
163
+ if (this._transactionActive) this._transactionCreated.push(handle);
164
+ return handle;
165
+ }
166
+
167
+ play(handle: HyaAnimation3DStateMachineActionHandle): void {
168
+ this._requireHandle(handle);
169
+ forEachChild(this.partitions, handle, (adapter, child) => adapter.play(child));
170
+ if (handle.playing) return;
171
+ handle.playing = true;
172
+ this._syncParticleCues(handle, true);
173
+ }
174
+
175
+ stop(handle: HyaAnimation3DStateMachineActionHandle): void {
176
+ this._requireHandle(handle);
177
+ forEachChild(this.partitions, handle, (adapter, child) => adapter.stop(child));
178
+ if (!handle.playing) return;
179
+ handle.playing = false;
180
+ this._releaseAllParticleCues(handle);
181
+ }
182
+
183
+ fade(
184
+ handle: HyaAnimation3DStateMachineActionHandle,
185
+ targetWeight: number,
186
+ durationSeconds: number,
187
+ ): void {
188
+ this._requireHandle(handle);
189
+ forEachChild(this.partitions, handle, (adapter, child) => adapter.fade(child, targetWeight, durationSeconds));
190
+ }
191
+
192
+ setWeight(handle: HyaAnimation3DStateMachineActionHandle, weight: number): void {
193
+ this._requireHandle(handle);
194
+ forEachChild(this.partitions, handle, (adapter, child) => adapter.setWeight(child, weight));
195
+ }
196
+
197
+ setTime(handle: HyaAnimation3DStateMachineActionHandle, timeSeconds: number): void {
198
+ this._requireHandle(handle);
199
+ forEachChild(this.partitions, handle, (adapter, child) => adapter.setTime(child, timeSeconds));
200
+ const previousTraversal = handle.traversal;
201
+ const position = playbackPosition(timeSeconds, handle.duration, handle.loop);
202
+ handle.targetTime = timeSeconds;
203
+ handle.traversal = position.traversal;
204
+ if (!handle.playing) return;
205
+ this._syncParticleCues(handle, false, position.localTime);
206
+ if (position.traversal !== previousTraversal) this._restartDominantParticleCues(handle);
207
+ }
208
+
209
+ setTimeScale(handle: HyaAnimation3DStateMachineActionHandle, timeScale: number): void {
210
+ this._requireHandle(handle);
211
+ forEachChild(this.partitions, handle, (adapter, child) => adapter.setTimeScale(child, timeScale));
212
+ }
213
+
214
+ destroyAction(handle: HyaAnimation3DStateMachineActionHandle): void {
215
+ if (handle.destroyed) return;
216
+ this._requireHandle(handle);
217
+ if (handle.playing) {
218
+ handle.playing = false;
219
+ this._releaseAllParticleCues(handle);
220
+ }
221
+ forEachChild(this.partitions, handle, (adapter, child) => adapter.destroyAction(child));
222
+ if (this._transactionActive) {
223
+ if (!handle.pendingDestroy) {
224
+ handle.pendingDestroy = true;
225
+ this._transactionPendingDestroy.push(handle);
226
+ }
227
+ return;
228
+ }
229
+ this._destroyGroupNow(handle);
230
+ }
231
+
232
+ beginControllerTransaction(): void {
233
+ this._requireActive();
234
+ if (this._transactionActive) throw new Error('HYA Animation3D state-machine transaction is already active.');
235
+ this._transactionActive = true;
236
+ this._transactionCreated.length = 0;
237
+ this._transactionPendingDestroy.length = 0;
238
+ this._transactionCheckpoints.clear();
239
+ this._transactionCommands.length = 0;
240
+ this._transactionOwners = cloneOwners(this._owners);
241
+ this._transactionNextHandleId = this._nextHandleId;
242
+ for (const handle of this._handles) {
243
+ this._transactionCheckpoints.set(handle, {
244
+ targetTime: handle.targetTime,
245
+ traversal: handle.traversal,
246
+ playing: handle.playing,
247
+ activeCueKeys: new Set(handle.activeCueKeys),
248
+ });
249
+ }
250
+ const begun: Animation3DStateMachineMixerAdapter[] = [];
251
+ try {
252
+ for (const partition of this.partitions) {
253
+ partition.adapter.beginControllerTransaction();
254
+ begun.push(partition.adapter);
255
+ }
256
+ } catch (error) {
257
+ for (let index = begun.length - 1; index >= 0; index--) begun[index]!.rollbackControllerTransaction();
258
+ this._transactionActive = false;
259
+ this._clearTransaction();
260
+ throw error;
261
+ }
262
+ }
263
+
264
+ commitControllerTransaction(): void {
265
+ if (!this._transactionActive) throw new Error('HYA Animation3D state-machine has no active transaction.');
266
+ for (const partition of this.partitions) partition.adapter.commitControllerTransaction();
267
+ this._transactionActive = false;
268
+ for (const command of this._transactionCommands) this._applyParticleCommand(command);
269
+ for (const handle of this._transactionPendingDestroy) this._destroyGroupNow(handle);
270
+ this._clearTransaction();
271
+ }
272
+
273
+ rollbackControllerTransaction(): void {
274
+ if (!this._transactionActive) return;
275
+ this._transactionActive = false;
276
+ for (let index = this.partitions.length - 1; index >= 0; index--) {
277
+ this.partitions[index]!.adapter.rollbackControllerTransaction();
278
+ }
279
+ for (const handle of this._transactionCreated) {
280
+ handle.destroyed = true;
281
+ this._handles.delete(handle);
282
+ }
283
+ for (const [handle, checkpoint] of this._transactionCheckpoints) {
284
+ if (handle.destroyed) continue;
285
+ handle.targetTime = checkpoint.targetTime;
286
+ handle.traversal = checkpoint.traversal;
287
+ handle.playing = checkpoint.playing;
288
+ handle.pendingDestroy = false;
289
+ handle.activeCueKeys.clear();
290
+ for (const key of checkpoint.activeCueKeys) handle.activeCueKeys.add(key);
291
+ }
292
+ this._owners.clear();
293
+ for (const [key, owners] of this._transactionOwners) this._owners.set(key, [...owners]);
294
+ this._nextHandleId = this._transactionNextHandleId;
295
+ this._clearTransaction();
296
+ }
297
+
298
+ destroy(): void {
299
+ if (this._destroyed) return;
300
+ this.rollbackControllerTransaction();
301
+ for (const handle of [...this._handles]) {
302
+ if (handle.playing) this.stop(handle);
303
+ forEachChild(this.partitions, handle, (adapter, child) => adapter.destroyAction(child));
304
+ this._destroyGroupNow(handle);
305
+ }
306
+ for (const partition of this.partitions) partition.adapter.destroy();
307
+ for (const emitter of this._particles.values()) stopParticle(emitter);
308
+ this._owners.clear();
309
+ this._destroyed = true;
310
+ }
311
+
312
+ private _syncParticleCues(
313
+ handle: HyaAnimation3DStateMachineActionHandle,
314
+ entering: boolean,
315
+ localTime = playbackPosition(handle.targetTime, handle.duration, handle.loop).localTime,
316
+ ): void {
317
+ for (const cue of this._particleCues.get(handle.clipId) ?? []) {
318
+ const active = particleCueActive(cue, localTime, handle.duration);
319
+ const owned = handle.activeCueKeys.has(cue.key);
320
+ if (active && !owned) this._acquireParticleCue(handle, cue);
321
+ else if (!active && owned) this._releaseParticleCue(handle, cue.key);
322
+ else if (entering && active && owned) this._recordParticleCommand({ key: cue.key, kind: 'start' });
323
+ }
324
+ }
325
+
326
+ private _acquireParticleCue(
327
+ handle: HyaAnimation3DStateMachineActionHandle,
328
+ cue: HyaAnimation3DParticleCue,
329
+ ): void {
330
+ handle.activeCueKeys.add(cue.key);
331
+ const owners = this._owners.get(cue.key) ?? [];
332
+ if (!this._owners.has(cue.key)) this._owners.set(cue.key, owners);
333
+ const existing = owners.indexOf(handle.id);
334
+ if (existing >= 0) owners.splice(existing, 1);
335
+ owners.push(handle.id);
336
+ this._recordParticleCommand({ key: cue.key, kind: 'start' });
337
+ }
338
+
339
+ private _releaseParticleCue(handle: HyaAnimation3DStateMachineActionHandle, key: string): void {
340
+ handle.activeCueKeys.delete(key);
341
+ const owners = this._owners.get(key);
342
+ if (!owners) return;
343
+ const index = owners.indexOf(handle.id);
344
+ const wasDominant = index === owners.length - 1;
345
+ if (index >= 0) owners.splice(index, 1);
346
+ if (owners.length === 0) {
347
+ this._owners.delete(key);
348
+ this._recordParticleCommand({ key, kind: 'stop' });
349
+ } else if (wasDominant) this._recordParticleCommand({ key, kind: 'start' });
350
+ }
351
+
352
+ private _releaseAllParticleCues(handle: HyaAnimation3DStateMachineActionHandle): void {
353
+ for (const key of [...handle.activeCueKeys]) this._releaseParticleCue(handle, key);
354
+ }
355
+
356
+ private _restartDominantParticleCues(handle: HyaAnimation3DStateMachineActionHandle): void {
357
+ for (const key of handle.activeCueKeys) {
358
+ const owners = this._owners.get(key);
359
+ if (owners?.[owners.length - 1] === handle.id) this._recordParticleCommand({ key, kind: 'start' });
360
+ }
361
+ }
362
+
363
+ private _recordParticleCommand(command: ParticleCommand): void {
364
+ if (this._transactionActive) this._transactionCommands.push(command);
365
+ else this._applyParticleCommand(command);
366
+ }
367
+
368
+ private _applyParticleCommand(command: ParticleCommand): void {
369
+ const emitter = this._particles.get(command.key);
370
+ if (!emitter) return;
371
+ if (command.kind === 'stop') stopParticle(emitter);
372
+ else {
373
+ emitter.emitting = true;
374
+ emitter.restart(true);
375
+ emitter.playing = true;
376
+ }
377
+ }
378
+
379
+ private _destroyGroupNow(handle: HyaAnimation3DStateMachineActionHandle): void {
380
+ handle.playing = false;
381
+ handle.pendingDestroy = false;
382
+ handle.destroyed = true;
383
+ handle.activeCueKeys.clear();
384
+ this._handles.delete(handle);
385
+ }
386
+
387
+ private _clearTransaction(): void {
388
+ this._transactionCreated.length = 0;
389
+ this._transactionPendingDestroy.length = 0;
390
+ this._transactionCheckpoints.clear();
391
+ this._transactionCommands.length = 0;
392
+ this._transactionOwners.clear();
393
+ }
394
+
395
+ private _requireHandle(handle: HyaAnimation3DStateMachineActionHandle): void {
396
+ this._requireActive();
397
+ if (handle.destroyed || !this._handles.has(handle)) {
398
+ throw new Error('HYA Animation3D state-machine action is no longer valid.');
399
+ }
400
+ }
401
+
402
+ private _requireActive(): void {
403
+ if (this._destroyed) throw new Error('HYA Animation3D state-machine mixer port has been destroyed.');
404
+ }
405
+ }
406
+
407
+ /** Coordinates native scene, glTF model and Particle3D channels with one controller transaction. */
408
+ export class HyaAnimation3DStateMachineRuntime {
409
+ readonly controller!: AnimationStateMachineController<HyaAnimation3DStateMachineActionHandle>;
410
+ private readonly _partitions: readonly PartitionRuntime[];
411
+ private readonly _port: HyaAnimation3DStateMachineMixerPort;
412
+ private _time = 0;
413
+ private _destroyed = false;
414
+
415
+ constructor(options: HyaAnimation3DStateMachineRuntimeOptions) {
416
+ this._partitions = Object.freeze(options.partitions.map(partition => {
417
+ const resolver = { resolve: (clipId: string): Animation3DClip | null => partition.clips.get(clipId) ?? null };
418
+ return {
419
+ id: partition.id,
420
+ mixer: partition.mixer,
421
+ adapter: new Animation3DStateMachineMixerAdapter(partition.mixer, resolver),
422
+ clips: partition.clips,
423
+ bindingIds: partition.bindingIds,
424
+ pose: partition.pose ?? new Animation3DPoseBuffer(),
425
+ apply: partition.apply,
426
+ previousTime: 0,
427
+ frameCompleted: false,
428
+ } satisfies PartitionRuntime;
429
+ }));
430
+ this._port = new HyaAnimation3DStateMachineMixerPort(
431
+ this._partitions,
432
+ options.clips,
433
+ options.particleCues ?? new Map(),
434
+ );
435
+ try {
436
+ this.controller = new AnimationStateMachineController(
437
+ compileAnimationStateMachine(options.definition),
438
+ this._port,
439
+ );
440
+ this.evaluate();
441
+ } catch (error) {
442
+ this.controller?.destroy();
443
+ this._port.destroy();
444
+ this._destroyed = true;
445
+ throw error;
446
+ }
447
+ }
448
+
449
+ get time(): number { return this._time; }
450
+ get liveActionCount(): number { return this._port.liveActionCount; }
451
+ get liveBindingCount(): number {
452
+ let count = 0;
453
+ for (const partition of this._partitions) count += partition.mixer.liveBindingCount;
454
+ return count;
455
+ }
456
+ get sideEffectOwnerCount(): number { return this._port.sideEffectOwnerCount; }
457
+
458
+ update(deltaSeconds: number): void {
459
+ this._requireActive();
460
+ if (!Number.isFinite(deltaSeconds) || deltaSeconds < 0) {
461
+ throw new RangeError('HYA Animation3D state-machine deltaSeconds must be finite and non-negative.');
462
+ }
463
+ this._runFrame(deltaSeconds);
464
+ }
465
+
466
+ evaluate(): void {
467
+ this._requireActive();
468
+ this._runFrame(0);
469
+ }
470
+
471
+ reset(): void {
472
+ this._requireActive();
473
+ this.controller.reset();
474
+ this._time = 0;
475
+ this.evaluate();
476
+ }
477
+
478
+ destroy(): void {
479
+ if (this._destroyed) return;
480
+ this.controller.destroy();
481
+ this._port.destroy();
482
+ this._destroyed = true;
483
+ }
484
+
485
+ private _runFrame(deltaSeconds: number): void {
486
+ const nextTime = this._time + deltaSeconds;
487
+ this._openFrames(nextTime);
488
+ let poses: readonly Animation3DPose[];
489
+ try {
490
+ poses = runAnimationStateMachineControllerUpdateTransaction(
491
+ this.controller,
492
+ deltaSeconds,
493
+ () => {
494
+ for (const partition of this._partitions) partition.adapter.beginFrame(partition.pose);
495
+ },
496
+ () => {
497
+ for (const partition of this._partitions) partition.adapter.endFrame(partition.pose);
498
+ const result: Animation3DPose[] = [];
499
+ for (const partition of this._partitions) {
500
+ result.push(partition.mixer.endSynchronizedFrame(partition.pose));
501
+ partition.frameCompleted = true;
502
+ }
503
+ return result;
504
+ },
505
+ );
506
+ } catch (error) {
507
+ this._cancelFrames();
508
+ throw error;
509
+ }
510
+ for (const partition of this._partitions) partition.frameCompleted = false;
511
+ this._time = nextTime;
512
+ for (let index = 0; index < this._partitions.length; index++) {
513
+ this._partitions[index]!.apply(poses[index]!);
514
+ }
515
+ }
516
+
517
+ private _openFrames(time: number): void {
518
+ const opened: PartitionRuntime[] = [];
519
+ try {
520
+ for (const partition of this._partitions) {
521
+ partition.previousTime = partition.mixer.time;
522
+ partition.frameCompleted = false;
523
+ partition.mixer.beginSynchronizedFrame(time, partition.pose);
524
+ opened.push(partition);
525
+ }
526
+ } catch (error) {
527
+ for (let index = opened.length - 1; index >= 0; index--) {
528
+ opened[index]!.mixer.cancelSynchronizedFrame(opened[index]!.pose);
529
+ }
530
+ throw error;
531
+ }
532
+ }
533
+
534
+ private _cancelFrames(): void {
535
+ for (const partition of this._partitions) partition.adapter.cancelFrame(partition.pose);
536
+ for (let index = this._partitions.length - 1; index >= 0; index--) {
537
+ const partition = this._partitions[index]!;
538
+ if (partition.frameCompleted) {
539
+ partition.mixer.rollbackCompletedSynchronizedFrame(partition.previousTime);
540
+ partition.frameCompleted = false;
541
+ } else partition.mixer.cancelSynchronizedFrame(partition.pose);
542
+ }
543
+ }
544
+
545
+ private _requireActive(): void {
546
+ if (this._destroyed) throw new Error('HYA Animation3D state-machine runtime has been destroyed.');
547
+ }
548
+ }
549
+
550
+ function forEachChild(
551
+ partitions: readonly PartitionRuntime[],
552
+ handle: HyaAnimation3DStateMachineActionHandle,
553
+ visit: (adapter: Animation3DStateMachineMixerAdapter, child: Animation3DStateMachineActionRuntimeHandle) => void,
554
+ ): void {
555
+ let childIndex = 0;
556
+ for (const partition of partitions) {
557
+ if (!partition.clips.has(handle.clipId)) continue;
558
+ visit(partition.adapter, handle.children[childIndex++]!);
559
+ }
560
+ }
561
+
562
+ function cloneOwners(source: ReadonlyMap<string, readonly string[]>): Map<string, string[]> {
563
+ const result = new Map<string, string[]>();
564
+ for (const [key, owners] of source) result.set(key, [...owners]);
565
+ return result;
566
+ }
567
+
568
+ function translateActionOptions(
569
+ options: AnimationStateMachineActionOptions,
570
+ bindingIds: ReadonlyMap<string, string> | undefined,
571
+ ): AnimationStateMachineActionOptions {
572
+ if (!options.mask || !bindingIds || bindingIds.size === 0) return options;
573
+ const translate = (ids: readonly string[] | undefined): readonly string[] | undefined => {
574
+ if (!ids) return undefined;
575
+ return Object.freeze(ids.map(id => bindingIds.get(id) ?? id));
576
+ };
577
+ const include = translate(options.mask.include);
578
+ const exclude = translate(options.mask.exclude);
579
+ return {
580
+ ...options,
581
+ mask: Object.freeze({
582
+ ...(include === undefined ? {} : { include }),
583
+ ...(exclude === undefined ? {} : { exclude }),
584
+ }),
585
+ };
586
+ }
587
+
588
+ function playbackPosition(
589
+ time: number,
590
+ duration: number,
591
+ loop: AnimationStateMachineActionOptions['loop'],
592
+ ): { readonly localTime: number; readonly traversal: number } {
593
+ if (duration <= TIME_EPSILON || loop === 'once') {
594
+ return { localTime: Math.min(duration, Math.max(0, time)), traversal: 0 };
595
+ }
596
+ const traversal = Math.floor(time / duration);
597
+ let localTime = time - traversal * duration;
598
+ if (localTime < 0) localTime += duration;
599
+ if (loop === 'ping-pong' && Math.abs(traversal % 2) === 1) localTime = duration - localTime;
600
+ return { localTime, traversal };
601
+ }
602
+
603
+ function particleCueActive(
604
+ cue: HyaAnimation3DParticleCue,
605
+ time: number,
606
+ duration: number,
607
+ ): boolean {
608
+ const atTerminalEnd = Math.abs(cue.end - duration) <= TIME_EPSILON
609
+ && Math.abs(time - duration) <= TIME_EPSILON;
610
+ return time >= cue.start - TIME_EPSILON
611
+ && (time < cue.end - TIME_EPSILON || atTerminalEnd);
612
+ }
613
+
614
+ function stopParticle(emitter: ParticleEmitter3D): void {
615
+ emitter.playing = false;
616
+ emitter.emitting = false;
617
+ emitter.clear();
618
+ }