@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,2934 @@
1
+ import { hyaStateMachineChannelCapability, HYA_STATE_MACHINE_EXTENSION_ID, parseHyaStateMachineExtension, ANIMATION_VECTOR_SHAPE_EXTENSION_ID, parseAnimation } from '@haiyue/animation-spec';
2
+ export { HYA_STATE_MACHINE_CHANNEL_REGISTRY, hyaStateMachineChannelCapability } from '@haiyue/animation-spec';
3
+ import { Component, System } from '@haiyue/engine';
4
+ import { UniqueCheckType, ComponentLifecycleFlags } from '@haiyue/engine/ecs';
5
+ import { r as runAnimationStateMachineControllerUpdateTransaction } from './chunks/AnimationStateMachineController-BIcAh-sT.js';
6
+ import { A as Animation2DRuntime, c as createAnimation2DStateMachineController } from './chunks/Animation2DStateMachine-9IonguNa.js';
7
+ import '@haiyue/engine/geometry';
8
+ import '@haiyue/engine/components';
9
+
10
+ class Animation2DStateMachineActionRuntimeHandle {
11
+ duration;
12
+ clipId;
13
+ action;
14
+ targetTime = 0;
15
+ playing = false;
16
+ destroyed = false;
17
+ pendingDestroy = false;
18
+ _checkpoint;
19
+ _checkpointTargetTime = 0;
20
+ _checkpointPlaying = false;
21
+ constructor(clipId, action) {
22
+ this.duration = action.duration;
23
+ this.clipId = clipId;
24
+ this.action = action;
25
+ this._checkpoint = action.createSynchronizedState();
26
+ }
27
+ captureTransactionState() {
28
+ this._checkpointTargetTime = this.targetTime;
29
+ this._checkpointPlaying = this.playing;
30
+ this.action.captureSynchronizedState(this._checkpoint);
31
+ }
32
+ restoreTransactionState() {
33
+ this.targetTime = this._checkpointTargetTime;
34
+ this.playing = this._checkpointPlaying;
35
+ this.pendingDestroy = false;
36
+ this.action.restoreSynchronizedState(this._checkpoint);
37
+ }
38
+ }
39
+ /**
40
+ * Transactional state-machine owner for the existing 2D mixer. It never
41
+ * samples tracks itself: absolute controller playheads are synchronized into
42
+ * the mixer actions, and the caller evaluates the ordinary shared pose buffer.
43
+ */
44
+ class Animation2DStateMachineMixerAdapter {
45
+ mixer;
46
+ _handles = new Set();
47
+ _transactionHandles = [];
48
+ _transactionCreated = [];
49
+ _transactionPendingDestroy = [];
50
+ _transactionActive = false;
51
+ _destroyed = false;
52
+ constructor(mixer) {
53
+ this.mixer = mixer;
54
+ }
55
+ get liveActionCount() { return this._handles.size; }
56
+ get liveBindingCount() { return this.mixer.liveBindingCount; }
57
+ createAction(clipId, options) {
58
+ this._requireActive();
59
+ const handle = new Animation2DStateMachineActionRuntimeHandle(clipId, this.mixer.createAction(clipId, options));
60
+ this._handles.add(handle);
61
+ if (this._transactionActive)
62
+ this._transactionCreated.push(handle);
63
+ return handle;
64
+ }
65
+ play(handle) {
66
+ this._requireHandle(handle);
67
+ this.mixer.play(handle.action);
68
+ handle.playing = true;
69
+ }
70
+ stop(handle) {
71
+ this._requireHandle(handle);
72
+ this.mixer.stop(handle.action);
73
+ handle.playing = false;
74
+ }
75
+ fade(handle, _targetWeight, _durationSeconds) {
76
+ this._requireHandle(handle);
77
+ // The controller is the unique transition clock and applies exact weights.
78
+ }
79
+ setWeight(handle, weight) {
80
+ this._requireHandle(handle);
81
+ this.mixer.setWeight(handle.action, weight);
82
+ }
83
+ setTime(handle, timeSeconds) {
84
+ this._requireHandle(handle);
85
+ handle.targetTime = timeSeconds;
86
+ this.mixer.setTime(handle.action, timeSeconds);
87
+ }
88
+ setTimeScale(handle, timeScale) {
89
+ this._requireHandle(handle);
90
+ this.mixer.setTimeScale(handle.action, timeScale);
91
+ }
92
+ destroyAction(handle) {
93
+ if (handle.destroyed)
94
+ return;
95
+ this._requireHandle(handle);
96
+ if (this._transactionActive) {
97
+ if (!handle.pendingDestroy) {
98
+ handle.pendingDestroy = true;
99
+ this._transactionPendingDestroy.push(handle);
100
+ }
101
+ return;
102
+ }
103
+ this._destroyHandleNow(handle);
104
+ }
105
+ beginControllerTransaction() {
106
+ this._requireActive();
107
+ if (this._transactionActive) {
108
+ throw new Error('Animation2D state-machine adapter transaction is already active.');
109
+ }
110
+ this._transactionActive = true;
111
+ this._transactionHandles.length = 0;
112
+ this._transactionCreated.length = 0;
113
+ this._transactionPendingDestroy.length = 0;
114
+ for (const handle of this._handles) {
115
+ handle.captureTransactionState();
116
+ this._transactionHandles.push(handle);
117
+ }
118
+ }
119
+ commitControllerTransaction() {
120
+ if (!this._transactionActive) {
121
+ throw new Error('Animation2D state-machine adapter has no active transaction.');
122
+ }
123
+ this._transactionActive = false;
124
+ for (const handle of this._transactionPendingDestroy)
125
+ this._destroyHandleNow(handle);
126
+ this._clearTransaction();
127
+ }
128
+ rollbackControllerTransaction() {
129
+ if (!this._transactionActive)
130
+ return;
131
+ this._transactionActive = false;
132
+ for (let index = this._transactionCreated.length - 1; index >= 0; index--) {
133
+ this._destroyHandleNow(this._transactionCreated[index]);
134
+ }
135
+ for (const handle of this._transactionHandles) {
136
+ if (!handle.destroyed)
137
+ handle.restoreTransactionState();
138
+ }
139
+ this._clearTransaction();
140
+ }
141
+ destroy() {
142
+ if (this._destroyed)
143
+ return;
144
+ this.rollbackControllerTransaction();
145
+ for (const handle of [...this._handles])
146
+ this._destroyHandleNow(handle);
147
+ this._destroyed = true;
148
+ }
149
+ _destroyHandleNow(handle) {
150
+ if (handle.destroyed)
151
+ return;
152
+ this.mixer.removeAction(handle.action);
153
+ handle.playing = false;
154
+ handle.pendingDestroy = false;
155
+ handle.destroyed = true;
156
+ this._handles.delete(handle);
157
+ }
158
+ _clearTransaction() {
159
+ this._transactionHandles.length = 0;
160
+ this._transactionCreated.length = 0;
161
+ this._transactionPendingDestroy.length = 0;
162
+ }
163
+ _requireHandle(handle) {
164
+ this._requireActive();
165
+ if (handle.destroyed || !this._handles.has(handle)) {
166
+ throw new Error('Animation2D state-machine action belongs to another adapter or was destroyed.');
167
+ }
168
+ }
169
+ _requireActive() {
170
+ if (this._destroyed)
171
+ throw new Error('Animation2D state-machine adapter has been destroyed.');
172
+ }
173
+ }
174
+
175
+ class AnimationStateMachineChannelError extends Error {
176
+ diagnostic;
177
+ name = 'AnimationStateMachineChannelError';
178
+ constructor(diagnostic) {
179
+ super(`${diagnostic.message} (${diagnostic.path})`);
180
+ this.diagnostic = diagnostic;
181
+ }
182
+ }
183
+ /**
184
+ * Audio owns one HTMLMediaElement playhead and is intentionally not amplitude
185
+ * mixed. It is executable only when the graph can never overlap two motions.
186
+ */
187
+ function audioStateMachineCompatibilityDiagnostic(definition, path = '$.stateMachine') {
188
+ const capability = hyaStateMachineChannelCapability('audio');
189
+ if (definition.layers.length !== 1) {
190
+ return audioRangeDiagnostic(`${path}.layers`, 'Audio requires exactly one state-machine layer.');
191
+ }
192
+ const layer = definition.layers[0];
193
+ if ((layer.weight ?? 1) !== 1 || (layer.blendMode ?? 'override') !== 'override') {
194
+ return audioRangeDiagnostic(`${path}.layers[0]`, 'Audio requires one full-weight override layer because media amplitude is not pose-mixable.');
195
+ }
196
+ for (let stateIndex = 0; stateIndex < layer.states.length; stateIndex++) {
197
+ if (motionContainsBlendTree(layer.states[stateIndex].motion)) {
198
+ return audioRangeDiagnostic(`${path}.layers[0].states[${stateIndex}].motion`, 'Audio cannot run inside a Blend Tree because two media playheads would overlap.');
199
+ }
200
+ }
201
+ for (let transitionIndex = 0; transitionIndex < layer.transitions.length; transitionIndex++) {
202
+ if (layer.transitions[transitionIndex].duration > 0) {
203
+ return audioRangeDiagnostic(`${path}.layers[0].transitions[${transitionIndex}].duration`, 'Audio transitions must be immediate; non-zero cross-fades are an unmixable media range.');
204
+ }
205
+ }
206
+ if (capability.transition !== 'immediate-only') {
207
+ throw new Error('The canonical audio channel contract is internally inconsistent.');
208
+ }
209
+ return null;
210
+ }
211
+ function assertAudioStateMachineCompatible(definition, path) {
212
+ const diagnostic = audioStateMachineCompatibilityDiagnostic(definition, path);
213
+ if (diagnostic)
214
+ throw new AnimationStateMachineChannelError(diagnostic);
215
+ }
216
+ function audioAutoplayRejectedDiagnostic(path, reason) {
217
+ const suffix = reason instanceof Error && reason.message
218
+ ? ` Browser reason: ${reason.message}`
219
+ : '';
220
+ return Object.freeze({
221
+ code: 'W_STATE_MACHINE_CHANNEL_AUDIO_AUTOPLAY_REJECTED',
222
+ severity: 'warning',
223
+ channelId: 'audio',
224
+ path,
225
+ message: `Audio play() was rejected; the playhead remains paused until a later user-authorized resume.${suffix}`,
226
+ });
227
+ }
228
+ function audioRangeDiagnostic(path, message) {
229
+ return Object.freeze({
230
+ code: 'E_STATE_MACHINE_CHANNEL_AUDIO_UNMIXABLE_RANGE',
231
+ severity: 'error',
232
+ channelId: 'audio',
233
+ path,
234
+ message,
235
+ });
236
+ }
237
+ function motionContainsBlendTree(motion) {
238
+ return motion.kind !== 'clip';
239
+ }
240
+
241
+ // HYA v2 keeps track times in Float32 while clip ranges remain JSON numbers.
242
+ // At ordinary multi-second timestamps the Float32 ULP is already larger than
243
+ // 1e-7, so boundary comparisons must absorb that representation difference.
244
+ const TIME_EPSILON = 1e-6;
245
+ /** Returns the canonical built-in state-machine payload from a parsed HYA document. */
246
+ function getHyaStateMachineExtension(animation) {
247
+ const value = animation.extensions[HYA_STATE_MACHINE_EXTENSION_ID];
248
+ if (value === undefined) {
249
+ throw new RangeError(`HYA document does not contain the built-in "${HYA_STATE_MACHINE_EXTENSION_ID}" extension.`);
250
+ }
251
+ return parseHyaStateMachineExtension(value, animation.duration);
252
+ }
253
+ /**
254
+ * Adapts named ranges from one HYA composition into source-independent 2D
255
+ * clips. The returned clips share no scene objects; one pose is applied to one
256
+ * Animation2DRuntime hierarchy by the state-machine runtime.
257
+ */
258
+ function createHyaAnimation2DClips(animation, extension = getHyaStateMachineExtension(animation)) {
259
+ assertChannelCompatibleDocument(animation, extension);
260
+ const nodesById = new Map(animation.nodes.map(node => [node.id, node]));
261
+ return Object.freeze(extension.clips.map(range => createClip(animation, range, nodesById)));
262
+ }
263
+ function createClip(animation, range, nodesById) {
264
+ const runtimeDuration = Math.fround(range.duration);
265
+ const tracks = [];
266
+ const effects = [];
267
+ for (let index = 0; index < animation.tracks.length; index++) {
268
+ const source = animation.tracks[index];
269
+ const node = nodesById.get(source.node);
270
+ tracks.push(sliceNumericTrack(source, node, range, runtimeDuration, index));
271
+ }
272
+ for (let index = 0; index < animation.nodes.length; index++) {
273
+ const node = animation.nodes[index];
274
+ tracks.push(createVisibilityTrack(node, range, animation.duration, index));
275
+ appendComponentChannels(node, index, range, runtimeDuration, tracks, effects, animation.duration);
276
+ }
277
+ return Object.freeze({
278
+ format: 'haiyue-animation2d-clip@1',
279
+ id: range.id,
280
+ name: range.name ?? range.id,
281
+ duration: runtimeDuration,
282
+ tracks: Object.freeze(tracks),
283
+ ...(effects.length === 0 ? {} : { effects: Object.freeze(effects) }),
284
+ });
285
+ }
286
+ function appendComponentChannels(node, nodeIndex, range, runtimeDuration, tracks, effects, compositionDuration) {
287
+ let particleSlot = 0;
288
+ let audioSlot = 0;
289
+ const components = node.components ?? [];
290
+ for (let componentIndex = 0; componentIndex < components.length; componentIndex++) {
291
+ const component = components[componentIndex];
292
+ const path = `$.nodes[${nodeIndex}].components[${componentIndex}]`;
293
+ if (component.type === 'sprite2d') {
294
+ const sprite = component;
295
+ if (!sprite.uvRectTrack)
296
+ continue;
297
+ tracks.push(sliceStepVectorTrack(sprite.uvRectTrack, range, runtimeDuration, `${range.id}:component:${nodeIndex}:${componentIndex}:sprite-uv`, Object.freeze({
298
+ id: `${node.id}.components.${componentIndex}.sprite.uv-rect`,
299
+ targetId: node.id,
300
+ path: `components.${componentIndex}.sprite.uv-rect`,
301
+ strategy: 'discrete',
302
+ defaultValue: Object.freeze([...(sprite.uvRect ?? [0, 0, 1, 1])]),
303
+ })));
304
+ }
305
+ else if (component.type === ANIMATION_VECTOR_SHAPE_EXTENSION_ID) {
306
+ const vector = component;
307
+ if (!vector.morph)
308
+ continue;
309
+ tracks.push(sliceComponentNumericTrack(vector.morph, range, runtimeDuration, `${range.id}:component:${nodeIndex}:${componentIndex}:vector-morph`, Object.freeze({
310
+ id: `${node.id}.components.${componentIndex}.vector.morph`,
311
+ targetId: node.id,
312
+ path: `components.${componentIndex}.vector.morph`,
313
+ strategy: 'continuous',
314
+ valueSize: vector.morph.valueSize,
315
+ defaultValue: vector.morphRelative
316
+ ? new Float32Array(vector.morph.valueSize)
317
+ : Object.freeze(Array.from(vector.values)),
318
+ }), `${path}.morph`));
319
+ }
320
+ else if (component.type === 'org.haiyue.vector-path-morph@1') {
321
+ const morph = component;
322
+ tracks.push(sliceComponentNumericTrack(morph, range, runtimeDuration, `${range.id}:component:${nodeIndex}:${componentIndex}:path-morph`, Object.freeze({
323
+ id: `${node.id}.components.${componentIndex}.path.morph`,
324
+ targetId: node.id,
325
+ path: `components.${componentIndex}.path.morph`,
326
+ strategy: 'continuous',
327
+ valueSize: morph.valueSize,
328
+ defaultValue: Object.freeze(Array.from(morph.values).slice(0, morph.valueSize)),
329
+ }), path));
330
+ }
331
+ if (component.type === 'particle2d') {
332
+ appendEffectCue('particle', component, particleSlot++, node, path, range, effects, compositionDuration);
333
+ }
334
+ else if (component.type === 'audio') {
335
+ appendEffectCue('audio', component, audioSlot++, node, path, range, effects, compositionDuration);
336
+ }
337
+ }
338
+ }
339
+ function appendEffectCue(kind, component, slot, node, sourcePath, range, effects, compositionDuration) {
340
+ const nodeStart = node.start ?? 0;
341
+ const nodeEnd = nodeStart + (node.duration ?? compositionDuration - nodeStart);
342
+ const start = Math.max(nodeStart, range.start);
343
+ const end = Math.min(nodeEnd, range.start + range.duration);
344
+ if (end <= start + TIME_EPSILON)
345
+ return;
346
+ const payload = Object.freeze({
347
+ targetId: node.id,
348
+ slot,
349
+ sourcePath,
350
+ component,
351
+ });
352
+ effects.push(Object.freeze({
353
+ id: `${range.id}:${kind}:${node.id}:${slot}`,
354
+ kind,
355
+ start: Math.max(0, Math.fround(start - range.start)),
356
+ end: Math.min(Math.fround(range.duration), Math.fround(end - range.start)),
357
+ loopBehavior: kind === 'particle' ? 'restart' : 'continue',
358
+ payload,
359
+ }));
360
+ }
361
+ function sliceNumericTrack(source, node, range, runtimeDuration, sourceIndex) {
362
+ const globalTimes = collectTrackTimes(source, range);
363
+ const times = new Float32Array(globalTimes.length);
364
+ const values = new Float32Array(globalTimes.length * source.valueSize);
365
+ for (let key = 0; key < globalTimes.length; key++) {
366
+ times[key] = key === 0
367
+ ? 0
368
+ : key === globalTimes.length - 1
369
+ ? runtimeDuration
370
+ : Math.min(runtimeDuration, Math.max(0, Math.fround(globalTimes[key] - range.start)));
371
+ sampleTrack(source, globalTimes[key], values, key * source.valueSize);
372
+ }
373
+ assertStrictFloatTimes(times, `${range.id}:${source.node}:${source.property}`);
374
+ const binding = createNumericBinding(source, node);
375
+ const interpolation = source.times.length < 2 ? 'step' : source.interpolation;
376
+ const easings = interpolation === 'cubic-bezier'
377
+ ? sliceEasings(source, globalTimes)
378
+ : undefined;
379
+ const spatialTangents = source.spatialTangents
380
+ ? sliceSpatialTangents(source, globalTimes)
381
+ : undefined;
382
+ return Object.freeze({
383
+ id: `${range.id}:track:${sourceIndex}`,
384
+ binding,
385
+ interpolation,
386
+ times,
387
+ values,
388
+ ...(easings ? { easings } : {}),
389
+ ...(spatialTangents ? { spatialTangents } : {}),
390
+ });
391
+ }
392
+ function createNumericBinding(track, node) {
393
+ const transform = node.transform;
394
+ const path = track.property === 'opacity'
395
+ ? 'opacity'
396
+ : `transform.${track.property}`;
397
+ const defaultValue = track.property === 'position'
398
+ ? transform?.position ?? [0, 0]
399
+ : track.property === 'scale'
400
+ ? transform?.scale ?? [1, 1]
401
+ : track.property === 'rotation'
402
+ ? [transform?.rotation ?? 0]
403
+ : [transform?.opacity ?? 1];
404
+ return Object.freeze({
405
+ id: `${node.id}.${path}`,
406
+ targetId: node.id,
407
+ path,
408
+ strategy: track.property === 'rotation' ? 'rotation' : 'continuous',
409
+ valueSize: track.valueSize,
410
+ defaultValue: Object.freeze(Array.from(defaultValue)),
411
+ });
412
+ }
413
+ function sliceComponentNumericTrack(source, range, runtimeDuration, id, binding, debugPath) {
414
+ const track = vectorTrackAsParsedTrack(source, debugPath);
415
+ const globalTimes = collectTrackTimes(track, range);
416
+ const times = new Float32Array(globalTimes.length);
417
+ const values = new Float32Array(globalTimes.length * source.valueSize);
418
+ for (let key = 0; key < globalTimes.length; key++) {
419
+ times[key] = key === 0
420
+ ? 0
421
+ : key === globalTimes.length - 1
422
+ ? runtimeDuration
423
+ : Math.min(runtimeDuration, Math.max(0, Math.fround(globalTimes[key] - range.start)));
424
+ sampleTrack(track, globalTimes[key], values, key * source.valueSize);
425
+ }
426
+ assertStrictFloatTimes(times, id);
427
+ const interpolation = source.times.length < 2 ? 'step' : source.interpolation;
428
+ const easings = interpolation === 'cubic-bezier'
429
+ ? sliceEasings(track, globalTimes)
430
+ : undefined;
431
+ return Object.freeze({
432
+ id,
433
+ binding,
434
+ interpolation,
435
+ times,
436
+ values,
437
+ ...(easings ? { easings } : {}),
438
+ });
439
+ }
440
+ function sliceStepVectorTrack(source, range, runtimeDuration, id, binding) {
441
+ const globalTimes = collectVectorTrackTimes(source, range);
442
+ const times = new Float32Array(globalTimes.length);
443
+ const values = [];
444
+ for (let key = 0; key < globalTimes.length; key++) {
445
+ times[key] = key === 0
446
+ ? 0
447
+ : key === globalTimes.length - 1
448
+ ? runtimeDuration
449
+ : Math.min(runtimeDuration, Math.max(0, Math.fround(globalTimes[key] - range.start)));
450
+ const frame = findArraySegment(source.times, globalTimes[key]);
451
+ const offset = frame * source.valueSize;
452
+ const sampled = new Float32Array(source.valueSize);
453
+ for (let component = 0; component < source.valueSize; component++) {
454
+ sampled[component] = source.values[offset + component] ?? 0;
455
+ }
456
+ values.push(sampled);
457
+ }
458
+ assertStrictFloatTimes(times, id);
459
+ return Object.freeze({
460
+ id,
461
+ binding,
462
+ interpolation: 'step',
463
+ times,
464
+ values: Object.freeze(values),
465
+ });
466
+ }
467
+ function collectVectorTrackTimes(track, range) {
468
+ const end = range.start + range.duration;
469
+ const result = [range.start];
470
+ for (let index = 0; index < track.times.length; index++) {
471
+ const time = track.times[index];
472
+ if (time > range.start + TIME_EPSILON && time < end - TIME_EPSILON)
473
+ result.push(time);
474
+ }
475
+ result.push(end);
476
+ return result;
477
+ }
478
+ function vectorTrackAsParsedTrack(source, debugPath) {
479
+ return {
480
+ node: debugPath,
481
+ property: 'opacity',
482
+ interpolation: source.interpolation,
483
+ times: source.times instanceof Float32Array ? source.times : Float32Array.from(source.times),
484
+ values: source.values instanceof Float32Array ? source.values : Float32Array.from(source.values),
485
+ valueSize: source.valueSize,
486
+ ...(source.easings === undefined ? {} : {
487
+ easings: source.easings instanceof Float32Array
488
+ ? source.easings
489
+ : Float32Array.from(source.easings),
490
+ }),
491
+ };
492
+ }
493
+ function findArraySegment(times, time) {
494
+ if (times.length <= 1 || time <= times[0])
495
+ return 0;
496
+ if (time >= times[times.length - 1])
497
+ return times.length - 1;
498
+ let low = 0;
499
+ let high = times.length - 1;
500
+ while (low + 1 < high) {
501
+ const middle = (low + high) >>> 1;
502
+ if (times[middle] <= time)
503
+ low = middle;
504
+ else
505
+ high = middle;
506
+ }
507
+ return low;
508
+ }
509
+ function createVisibilityTrack(node, range, compositionDuration, nodeIndex) {
510
+ const nodeStart = node.start ?? 0;
511
+ const nodeEnd = nodeStart + (node.duration ?? compositionDuration - nodeStart);
512
+ const globalTimes = [range.start];
513
+ if (nodeStart > range.start + TIME_EPSILON && nodeStart < range.start + range.duration - TIME_EPSILON) {
514
+ globalTimes.push(nodeStart);
515
+ }
516
+ if (nodeEnd > range.start + TIME_EPSILON && nodeEnd < range.start + range.duration - TIME_EPSILON) {
517
+ globalTimes.push(nodeEnd);
518
+ }
519
+ globalTimes.sort((left, right) => left - right);
520
+ const times = new Float32Array(globalTimes.length);
521
+ const values = new Array(globalTimes.length);
522
+ for (let index = 0; index < globalTimes.length; index++) {
523
+ const globalTime = globalTimes[index];
524
+ times[index] = Math.fround(globalTime - range.start);
525
+ values[index] = globalTime >= nodeStart - TIME_EPSILON && globalTime < nodeEnd - TIME_EPSILON;
526
+ }
527
+ assertStrictFloatTimes(times, `${range.id}:${node.id}:visibility`);
528
+ return Object.freeze({
529
+ id: `${range.id}:visibility:${nodeIndex}`,
530
+ binding: Object.freeze({
531
+ id: `${node.id}.visibility`,
532
+ targetId: node.id,
533
+ path: 'visibility',
534
+ strategy: 'discrete',
535
+ defaultValue: true,
536
+ }),
537
+ interpolation: 'step',
538
+ times,
539
+ values: Object.freeze(values),
540
+ });
541
+ }
542
+ function collectTrackTimes(track, range) {
543
+ const end = range.start + range.duration;
544
+ const result = [range.start];
545
+ for (let index = 0; index < track.times.length; index++) {
546
+ const time = track.times[index];
547
+ if (time > range.start + TIME_EPSILON && time < end - TIME_EPSILON)
548
+ result.push(time);
549
+ }
550
+ result.push(end);
551
+ return result;
552
+ }
553
+ function sampleTrack(track, time, output, outputOffset) {
554
+ const segment = findSegment(track.times, time);
555
+ const startOffset = segment * track.valueSize;
556
+ const endOffset = Math.min(segment + 1, track.times.length - 1) * track.valueSize;
557
+ let progress = 0;
558
+ if (segment < track.times.length - 1 && track.interpolation !== 'step') {
559
+ const start = track.times[segment];
560
+ const end = track.times[segment + 1];
561
+ const linear = Math.min(1, Math.max(0, (time - start) / Math.max(TIME_EPSILON, end - start)));
562
+ progress = track.interpolation === 'cubic-bezier'
563
+ ? cubicBezierYForX(track.easings, segment, linear)
564
+ : linear;
565
+ }
566
+ if (track.spatialTangents && segment < track.times.length - 1) {
567
+ const tangentOffset = segment * 4;
568
+ for (let component = 0; component < 2; component++) {
569
+ const start = track.values[startOffset + component];
570
+ const end = track.values[endOffset + component];
571
+ output[outputOffset + component] = spatialCoordinate(progress, start, start + track.spatialTangents[tangentOffset + component], end + track.spatialTangents[tangentOffset + 2 + component], end);
572
+ }
573
+ return;
574
+ }
575
+ for (let component = 0; component < track.valueSize; component++) {
576
+ const start = track.values[startOffset + component];
577
+ output[outputOffset + component] = start
578
+ + (track.values[endOffset + component] - start) * progress;
579
+ }
580
+ }
581
+ function sliceSpatialTangents(track, globalTimes) {
582
+ const result = new Float32Array(Math.max(0, globalTimes.length - 1) * 4);
583
+ for (let interval = 0; interval < globalTimes.length - 1; interval++) {
584
+ const startTime = globalTimes[interval];
585
+ const endTime = globalTimes[interval + 1];
586
+ const midpoint = (startTime + endTime) * 0.5;
587
+ const sourceSegment = findSegment(track.times, midpoint);
588
+ if (sourceSegment >= track.times.length - 1)
589
+ continue;
590
+ const sourceStartTime = track.times[sourceSegment];
591
+ const sourceEndTime = track.times[sourceSegment + 1];
592
+ if (endTime <= sourceStartTime + TIME_EPSILON || startTime >= sourceEndTime - TIME_EPSILON)
593
+ continue;
594
+ const linearStart = clamp01((startTime - sourceStartTime) / (sourceEndTime - sourceStartTime));
595
+ const linearEnd = clamp01((endTime - sourceStartTime) / (sourceEndTime - sourceStartTime));
596
+ const progressStart = track.interpolation === 'cubic-bezier'
597
+ ? cubicBezierYForX(track.easings, sourceSegment, linearStart)
598
+ : linearStart;
599
+ const progressEnd = track.interpolation === 'cubic-bezier'
600
+ ? cubicBezierYForX(track.easings, sourceSegment, linearEnd)
601
+ : linearEnd;
602
+ const valueOffset = sourceSegment * 2;
603
+ const tangentOffset = sourceSegment * 4;
604
+ const delta = progressEnd - progressStart;
605
+ for (let component = 0; component < 2; component++) {
606
+ const start = track.values[valueOffset + component];
607
+ const end = track.values[valueOffset + 2 + component];
608
+ const control1 = start + track.spatialTangents[tangentOffset + component];
609
+ const control2 = end + track.spatialTangents[tangentOffset + 2 + component];
610
+ const derivativeStart = spatialDerivative(progressStart, start, control1, control2, end);
611
+ const derivativeEnd = spatialDerivative(progressEnd, start, control1, control2, end);
612
+ result[interval * 4 + component] = derivativeStart * delta / 3;
613
+ result[interval * 4 + 2 + component] = -derivativeEnd * delta / 3;
614
+ }
615
+ }
616
+ return result;
617
+ }
618
+ function spatialCoordinate(value, start, first, second, end) {
619
+ const inverse = 1 - value;
620
+ return inverse * inverse * inverse * start
621
+ + 3 * inverse * inverse * value * first
622
+ + 3 * inverse * value * value * second
623
+ + value * value * value * end;
624
+ }
625
+ function spatialDerivative(value, start, first, second, end) {
626
+ const inverse = 1 - value;
627
+ return 3 * inverse * inverse * (first - start)
628
+ + 6 * inverse * value * (second - first)
629
+ + 3 * value * value * (end - second);
630
+ }
631
+ function sliceEasings(track, globalTimes) {
632
+ const result = new Float32Array(Math.max(0, globalTimes.length - 1) * 4);
633
+ for (let interval = 0; interval < globalTimes.length - 1; interval++) {
634
+ const startTime = globalTimes[interval];
635
+ const endTime = globalTimes[interval + 1];
636
+ const firstSourceTime = track.times[0];
637
+ const lastSourceTime = track.times[track.times.length - 1];
638
+ if (track.times.length < 2
639
+ || endTime <= firstSourceTime + TIME_EPSILON
640
+ || startTime >= lastSourceTime - TIME_EPSILON) {
641
+ result.set([1 / 3, 1 / 3, 2 / 3, 2 / 3], interval * 4);
642
+ continue;
643
+ }
644
+ const sourceSegment = findSegment(track.times, (startTime + endTime) * 0.5);
645
+ const sourceStart = track.times[sourceSegment];
646
+ const sourceEnd = track.times[sourceSegment + 1];
647
+ const duration = sourceEnd - sourceStart;
648
+ const xStart = Math.min(1, Math.max(0, (startTime - sourceStart) / duration));
649
+ const xEnd = Math.min(1, Math.max(0, (endTime - sourceStart) / duration));
650
+ const easingOffset = sourceSegment * 4;
651
+ const subcurve = sliceCubicBezier(track.easings[easingOffset], track.easings[easingOffset + 1], track.easings[easingOffset + 2], track.easings[easingOffset + 3], xStart, xEnd, track, sourceSegment);
652
+ result.set(subcurve, interval * 4);
653
+ }
654
+ return result;
655
+ }
656
+ function sliceCubicBezier(x1, y1, x2, y2, xStart, xEnd, track, sourceSegment) {
657
+ if (xStart <= TIME_EPSILON && xEnd >= 1 - TIME_EPSILON)
658
+ return [x1, y1, x2, y2];
659
+ const t0 = solveCubicX(xStart, x1, x2);
660
+ const t1 = solveCubicX(xEnd, x1, x2);
661
+ const start = cubicPoint(t0, x1, y1, x2, y2);
662
+ const end = cubicPoint(t1, x1, y1, x2, y2);
663
+ const delta = t1 - t0;
664
+ const startDerivative = cubicPointDerivative(t0, x1, y1, x2, y2);
665
+ const endDerivative = cubicPointDerivative(t1, x1, y1, x2, y2);
666
+ const control1 = [start[0] + startDerivative[0] * delta / 3, start[1] + startDerivative[1] * delta / 3];
667
+ const control2 = [end[0] - endDerivative[0] * delta / 3, end[1] - endDerivative[1] * delta / 3];
668
+ const width = end[0] - start[0];
669
+ const height = end[1] - start[1];
670
+ if (Math.abs(height) <= 1e-8) {
671
+ if (sourceSegmentHasMotion(track, sourceSegment)) {
672
+ throw new RangeError(`HYA cubic track "${track.node}.${track.property}" cannot be range-sliced at a zero-progress easing interval without losing fidelity.`);
673
+ }
674
+ return [1 / 3, 1 / 3, 2 / 3, 2 / 3];
675
+ }
676
+ return [
677
+ clamp01((control1[0] - start[0]) / width),
678
+ (control1[1] - start[1]) / height,
679
+ clamp01((control2[0] - start[0]) / width),
680
+ (control2[1] - start[1]) / height,
681
+ ];
682
+ }
683
+ function sourceSegmentHasMotion(track, segment) {
684
+ const start = segment * track.valueSize;
685
+ const end = (segment + 1) * track.valueSize;
686
+ for (let component = 0; component < track.valueSize; component++) {
687
+ if (Math.abs(track.values[start + component] - track.values[end + component]) > 1e-8)
688
+ return true;
689
+ }
690
+ return false;
691
+ }
692
+ function findSegment(times, time) {
693
+ if (times.length <= 1 || time <= times[0])
694
+ return 0;
695
+ if (time >= times[times.length - 1])
696
+ return times.length - 1;
697
+ let low = 0;
698
+ let high = times.length - 1;
699
+ while (low + 1 < high) {
700
+ const middle = (low + high) >>> 1;
701
+ if (times[middle] <= time)
702
+ low = middle;
703
+ else
704
+ high = middle;
705
+ }
706
+ return low;
707
+ }
708
+ function cubicBezierYForX(easings, segment, x) {
709
+ const offset = segment * 4;
710
+ const x1 = easings[offset];
711
+ const y1 = easings[offset + 1];
712
+ const x2 = easings[offset + 2];
713
+ const y2 = easings[offset + 3];
714
+ return cubicCoordinate$1(solveCubicX(x, x1, x2), y1, y2);
715
+ }
716
+ function solveCubicX(x, x1, x2) {
717
+ let parameter = x;
718
+ for (let iteration = 0; iteration < 6; iteration++) {
719
+ const estimate = cubicCoordinate$1(parameter, x1, x2) - x;
720
+ const derivative = cubicDerivative$1(parameter, x1, x2);
721
+ if (Math.abs(derivative) < 1e-7)
722
+ break;
723
+ parameter = clamp01(parameter - estimate / derivative);
724
+ }
725
+ let low = 0;
726
+ let high = 1;
727
+ for (let iteration = 0; iteration < 18; iteration++) {
728
+ const estimate = cubicCoordinate$1(parameter, x1, x2);
729
+ if (Math.abs(estimate - x) < 1e-8)
730
+ break;
731
+ if (estimate < x)
732
+ low = parameter;
733
+ else
734
+ high = parameter;
735
+ parameter = (low + high) * 0.5;
736
+ }
737
+ return parameter;
738
+ }
739
+ function cubicPoint(value, x1, y1, x2, y2) {
740
+ return [cubicCoordinate$1(value, x1, x2), cubicCoordinate$1(value, y1, y2)];
741
+ }
742
+ function cubicPointDerivative(value, x1, y1, x2, y2) {
743
+ return [cubicDerivative$1(value, x1, x2), cubicDerivative$1(value, y1, y2)];
744
+ }
745
+ function cubicCoordinate$1(value, first, second) {
746
+ const inverse = 1 - value;
747
+ return 3 * inverse * inverse * value * first
748
+ + 3 * inverse * value * value * second
749
+ + value * value * value;
750
+ }
751
+ function cubicDerivative$1(value, first, second) {
752
+ const inverse = 1 - value;
753
+ return 3 * inverse * inverse * first
754
+ + 6 * inverse * value * (second - first)
755
+ + 3 * value * value * (1 - second);
756
+ }
757
+ function assertChannelCompatibleDocument(animation, extension) {
758
+ let hasAudio = false;
759
+ for (let nodeIndex = 0; nodeIndex < animation.nodes.length; nodeIndex++) {
760
+ const node = animation.nodes[nodeIndex];
761
+ const components = node.components ?? [];
762
+ for (let componentIndex = 0; componentIndex < components.length; componentIndex++) {
763
+ const component = components[componentIndex];
764
+ const path = `$.nodes[${nodeIndex}].components[${componentIndex}]`;
765
+ if (component.type === 'audio')
766
+ hasAudio = true;
767
+ const unsupported = unsupportedInlineChannel(component, path);
768
+ if (unsupported)
769
+ throw new AnimationStateMachineChannelError(unsupported);
770
+ }
771
+ for (let effectIndex = 0; effectIndex < (node.effects?.length ?? 0); effectIndex++) {
772
+ const effect = node.effects[effectIndex];
773
+ if (Object.keys(effect).some(key => key.endsWith('Track'))) {
774
+ throw new AnimationStateMachineChannelError(unsupportedDiagnostic('visual-effect', `$.nodes[${nodeIndex}].effects[${effectIndex}]`, 'Animated visual-effect properties are not yet writable from the shared pose buffer.'));
775
+ }
776
+ }
777
+ const composites = node.composite
778
+ ? ('layers' in node.composite ? node.composite.layers : [node.composite])
779
+ : [];
780
+ for (let compositeIndex = 0; compositeIndex < composites.length; compositeIndex++) {
781
+ if (composites[compositeIndex].expansionTrack) {
782
+ throw new AnimationStateMachineChannelError(unsupportedDiagnostic('composite-expansion', `$.nodes[${nodeIndex}].composite.layers[${compositeIndex}].expansionTrack`, 'Animated composite expansion is not yet writable from the shared pose buffer.'));
783
+ }
784
+ }
785
+ }
786
+ if (hasAudio) {
787
+ assertAudioStateMachineCompatible(extension.stateMachine, `$.extensions.${HYA_STATE_MACHINE_EXTENSION_ID}.stateMachine`);
788
+ }
789
+ }
790
+ function unsupportedInlineChannel(component, path) {
791
+ if (component.type === 'shape2d'
792
+ || component.type === 'path2d'
793
+ || component.type === 'sprite2d'
794
+ || component.type === 'particle2d'
795
+ || component.type === 'audio'
796
+ || component.type === 'org.haiyue.vector-stroke@1'
797
+ || component.type === 'org.haiyue.vector-path-morph@1')
798
+ return null;
799
+ if (component.type === ANIMATION_VECTOR_SHAPE_EXTENSION_ID) {
800
+ const vector = component;
801
+ if (vectorHasUnsupportedInlineTrack(vector)) {
802
+ return unsupportedDiagnostic('vector-paint', path, 'Vector paint and modifier tracks are not yet writable from the shared pose buffer; vector morph is supported.');
803
+ }
804
+ return null;
805
+ }
806
+ if (component.type === 'text2d') {
807
+ const text = component;
808
+ const documents = text.documents;
809
+ const animators = text.animators;
810
+ if ((documents?.length ?? 0) > 1 || animators?.some(recordHasNestedTrack)) {
811
+ return unsupportedDiagnostic('text-animator', path, 'Animated text documents and animator tracks are not yet writable from the shared pose buffer.');
812
+ }
813
+ return null;
814
+ }
815
+ return unsupportedDiagnostic('visual-effect', path, `Component "${component.type}" has no registered state-machine channel strategy.`);
816
+ }
817
+ function vectorHasUnsupportedInlineTrack(vector) {
818
+ const fill = vector.fill;
819
+ if (fill) {
820
+ if (fill.kind === 'solid') {
821
+ if (fill.colorTrack || fill.opacityTrack)
822
+ return true;
823
+ }
824
+ else if (fill.startTrack || fill.endTrack || fill.stopsTrack || fill.opacityTrack)
825
+ return true;
826
+ }
827
+ const stroke = vector.stroke;
828
+ if (stroke && (stroke.colorTrack || stroke.opacityTrack || stroke.widthTrack || stroke.dashOffsetTrack
829
+ || (stroke.gradient && (stroke.gradient.startTrack || stroke.gradient.endTrack
830
+ || stroke.gradient.stopsTrack || stroke.gradient.opacityTrack))))
831
+ return true;
832
+ return vector.modifiers?.some(modifier => modifier.kind === 'round-corners'
833
+ ? modifier.radiusTrack !== undefined
834
+ : modifier.startTrack !== undefined || modifier.endTrack !== undefined
835
+ || modifier.offsetTrack !== undefined) ?? false;
836
+ }
837
+ function recordHasNestedTrack(record) {
838
+ if (Object.keys(record).some(key => key.endsWith('Track')))
839
+ return true;
840
+ return Object.values(record).some(value => value !== null && typeof value === 'object'
841
+ && !Array.isArray(value)
842
+ && recordHasNestedTrack(value));
843
+ }
844
+ function unsupportedDiagnostic(channelId, path, message) {
845
+ return Object.freeze({
846
+ code: hyaStateMachineChannelCapability(channelId).diagnosticCode,
847
+ severity: 'error',
848
+ channelId,
849
+ path,
850
+ message,
851
+ });
852
+ }
853
+ function assertStrictFloatTimes(times, trackId) {
854
+ for (let index = 1; index < times.length; index++) {
855
+ if (times[index] <= times[index - 1]) {
856
+ throw new RangeError(`HYA state-machine clip "${trackId}" has time keys that collapse after Float32 conversion.`);
857
+ }
858
+ }
859
+ }
860
+ function clamp01(value) {
861
+ return Math.min(1, Math.max(0, value));
862
+ }
863
+
864
+ /**
865
+ * Plays multiple named animation ranges and a state machine from one HYA
866
+ * asset. Transitions blend one pose onto one generated scene hierarchy.
867
+ */
868
+ class Animation2DStateMachineComponent extends Component {
869
+ static UniqueCheckType = UniqueCheckType.SYMBOL | UniqueCheckType.REPLACE;
870
+ static UniqueSymbol = Symbol.for('Animation2DStateMachineComponent');
871
+ static Lifecycle = ComponentLifecycleFlags.EntityRemoveComponent
872
+ | ComponentLifecycleFlags.EntityRemoveFromWorld;
873
+ animation;
874
+ stateMachineExtension;
875
+ runtimeExtensions;
876
+ playing;
877
+ speed;
878
+ _runtime = null;
879
+ _needsUpdate = true;
880
+ _parametersByName;
881
+ _parameterValues = new Map();
882
+ constructor(source, options = {}) {
883
+ super('Animation2DStateMachineComponent');
884
+ this.animation = isParsed(source) ? source : parseAnimation(source);
885
+ this.stateMachineExtension = getHyaStateMachineExtension(this.animation);
886
+ this.runtimeExtensions = options.runtimeExtensions;
887
+ this.playing = options.autoplay ?? true;
888
+ this.speed = finiteSpeed(options.speed ?? 1);
889
+ this._parametersByName = new Map(this.stateMachineExtension.stateMachine.parameters.map(parameter => [parameter.name, parameter]));
890
+ }
891
+ get runtimeStats() {
892
+ return this._runtime?.stats ?? EMPTY_RUNTIME_STATS;
893
+ }
894
+ get layerSnapshots() {
895
+ if (this._runtime)
896
+ return this._runtime.layerSnapshots;
897
+ return Object.freeze(this.stateMachineExtension.stateMachine.layers.map(layer => Object.freeze({
898
+ layerId: layer.id,
899
+ currentStateId: layer.initialStateId,
900
+ currentTime: 0,
901
+ transitionId: null,
902
+ sourceStateId: null,
903
+ destinationStateId: null,
904
+ transitionProgress: 0,
905
+ })));
906
+ }
907
+ get channelDiagnostics() {
908
+ return this._runtime?.diagnostics ?? EMPTY_CHANNEL_DIAGNOSTICS;
909
+ }
910
+ get liveActionCount() { return this._runtime?.liveActionCount ?? 0; }
911
+ get liveBindingCount() { return this._runtime?.liveBindingCount ?? 0; }
912
+ get sideEffectOwnerCount() { return this._runtime?.sideEffectOwnerCount ?? 0; }
913
+ play() {
914
+ this.playing = true;
915
+ this._runtime?.setPlaying(true);
916
+ this._needsUpdate = true;
917
+ return this;
918
+ }
919
+ pause() {
920
+ this.playing = false;
921
+ this._runtime?.setPlaying(false);
922
+ return this;
923
+ }
924
+ setSpeed(speed) {
925
+ this.speed = finiteSpeed(speed);
926
+ return this;
927
+ }
928
+ setFloat(name, value) {
929
+ if (!Number.isFinite(value))
930
+ throw new RangeError('Float parameters must be finite.');
931
+ this._setParameter(name, 'float', value);
932
+ this._runtime?.controller.setFloat(name, value);
933
+ return this;
934
+ }
935
+ setInteger(name, value) {
936
+ if (!Number.isSafeInteger(value))
937
+ throw new RangeError('Integer parameters must be safe integers.');
938
+ this._setParameter(name, 'integer', value);
939
+ this._runtime?.controller.setInteger(name, value);
940
+ return this;
941
+ }
942
+ setBoolean(name, value) {
943
+ if (typeof value !== 'boolean')
944
+ throw new TypeError('Boolean parameters require a boolean.');
945
+ this._setParameter(name, 'boolean', value);
946
+ this._runtime?.controller.setBoolean(name, value);
947
+ return this;
948
+ }
949
+ setTrigger(name) {
950
+ this._setParameter(name, 'trigger', true);
951
+ this._runtime?.controller.setTrigger(name);
952
+ return this;
953
+ }
954
+ resetTrigger(name) {
955
+ this._setParameter(name, 'trigger', false);
956
+ this._runtime?.controller.resetTrigger(name);
957
+ return this;
958
+ }
959
+ getParameter(name) {
960
+ const parameter = this._requireParameter(name);
961
+ if (this._runtime)
962
+ return this._runtime.controller.getParameter(name);
963
+ if (this._parameterValues.has(name))
964
+ return this._parameterValues.get(name);
965
+ return parameter.type === 'trigger' ? false : parameter.defaultValue;
966
+ }
967
+ reset() {
968
+ this._parameterValues.clear();
969
+ this._runtime?.reset();
970
+ this._needsUpdate = this._runtime === null;
971
+ return this;
972
+ }
973
+ onEntityRemoveComponent(_entity, component) {
974
+ if (component === this)
975
+ this._disposeRuntime();
976
+ }
977
+ onEntityRemoveFromWorld(_entity, _world) {
978
+ this._disposeRuntime();
979
+ }
980
+ clone() {
981
+ const clone = new Animation2DStateMachineComponent(this.animation, {
982
+ autoplay: this.playing,
983
+ speed: this.speed,
984
+ ...(this.runtimeExtensions ? { runtimeExtensions: this.runtimeExtensions } : {}),
985
+ });
986
+ for (const parameter of this.stateMachineExtension.stateMachine.parameters) {
987
+ clone._parameterValues.set(parameter.name, this.getParameter(parameter.name));
988
+ }
989
+ clone.disabled = this.disabled;
990
+ return clone;
991
+ }
992
+ _getParameterValues() {
993
+ return this._parameterValues;
994
+ }
995
+ _disposeRuntime() {
996
+ if (!this._runtime)
997
+ return;
998
+ for (const parameter of this.stateMachineExtension.stateMachine.parameters) {
999
+ this._parameterValues.set(parameter.name, this._runtime.controller.getParameter(parameter.name));
1000
+ }
1001
+ this._runtime.destroy();
1002
+ this._runtime = null;
1003
+ this._needsUpdate = true;
1004
+ }
1005
+ _setParameter(name, type, value) {
1006
+ const parameter = this._requireParameter(name);
1007
+ if (parameter.type !== type) {
1008
+ throw new TypeError(`State-machine parameter "${name}" is ${parameter.type}, not ${type}.`);
1009
+ }
1010
+ this._parameterValues.set(name, value);
1011
+ this._needsUpdate = true;
1012
+ }
1013
+ _requireParameter(name) {
1014
+ const parameter = this._parametersByName.get(name);
1015
+ if (!parameter)
1016
+ throw new ReferenceError(`Unknown state-machine parameter "${name}".`);
1017
+ return parameter;
1018
+ }
1019
+ }
1020
+ const EMPTY_RUNTIME_STATS = Object.freeze({
1021
+ nodeCount: 0,
1022
+ visualCount: 0,
1023
+ unsupportedComponentCount: 0,
1024
+ pendingResourceCount: 0,
1025
+ failedResourceCount: 0,
1026
+ textCount: 0,
1027
+ particleCount: 0,
1028
+ audioCount: 0,
1029
+ });
1030
+ const EMPTY_CHANNEL_DIAGNOSTICS = Object.freeze([]);
1031
+ function isParsed(source) {
1032
+ return typeof source === 'object' && source !== null && 'source' in source;
1033
+ }
1034
+ function finiteSpeed(value) {
1035
+ if (!Number.isFinite(value) || value < 0) {
1036
+ throw new RangeError('Animation2D state-machine speed must be finite and non-negative.');
1037
+ }
1038
+ return value;
1039
+ }
1040
+
1041
+ /** State-machine-only pose writer kept out of the ordinary Animation2D bundle. */
1042
+ class Animation2DStateMachineVisualRuntime extends Animation2DRuntime {
1043
+ _particleOwners = new Map();
1044
+ _audioOwners = new Map();
1045
+ _effectPayloads = new Map();
1046
+ _diagnostics = [];
1047
+ _reportedAudioRejections = new Set();
1048
+ constructor(owner, animation, runtimeExtensions, assetManager) {
1049
+ super(owner, animation, runtimeExtensions, assetManager);
1050
+ for (const node of this._nodes) {
1051
+ for (const particle of node.particles) {
1052
+ particle.playing = false;
1053
+ particle.emitting = false;
1054
+ particle.clear();
1055
+ }
1056
+ for (const audio of node.audio)
1057
+ audio.exitStateMachine();
1058
+ }
1059
+ }
1060
+ get diagnostics() {
1061
+ return this._diagnostics;
1062
+ }
1063
+ get sideEffectOwnerCount() {
1064
+ return ownerCount(this._particleOwners) + ownerCount(this._audioOwners);
1065
+ }
1066
+ resetSideEffects() {
1067
+ this._particleOwners.clear();
1068
+ this._audioOwners.clear();
1069
+ this._effectPayloads.clear();
1070
+ for (const node of this._nodes) {
1071
+ for (const particle of node.particles) {
1072
+ particle.playing = false;
1073
+ particle.emitting = false;
1074
+ particle.clear();
1075
+ }
1076
+ for (const audio of node.audio)
1077
+ audio.exitStateMachine();
1078
+ }
1079
+ }
1080
+ applyPose(pose, playing = this._playing, speed = this._speed) {
1081
+ this._playing = playing;
1082
+ this._speed = speed;
1083
+ this._opacityMemo.clear();
1084
+ for (const node of this._nodes) {
1085
+ node.entity.disabled = false;
1086
+ node.transform.setPosition(node.initialX, node.initialY);
1087
+ node.transform.rotation = node.initialRotation;
1088
+ node.transform.setScale(node.initialScaleX, node.initialScaleY);
1089
+ node.opacity = node.initialOpacity;
1090
+ }
1091
+ for (const channel of pose.channels) {
1092
+ const node = this._nodesById.get(channel.binding.targetId);
1093
+ if (!node) {
1094
+ throw new ReferenceError(`Animation2D pose references unknown node "${channel.binding.targetId}".`);
1095
+ }
1096
+ switch (channel.binding.path) {
1097
+ case 'transform.position': {
1098
+ const value = numericPoseValue(channel.value, channel.binding.id, 2);
1099
+ node.transform.setPosition(value[0], -value[1]);
1100
+ break;
1101
+ }
1102
+ case 'transform.rotation': {
1103
+ const value = numericPoseValue(channel.value, channel.binding.id, 1);
1104
+ node.transform.rotation = -value[0];
1105
+ break;
1106
+ }
1107
+ case 'transform.scale': {
1108
+ const value = numericPoseValue(channel.value, channel.binding.id, 2);
1109
+ node.transform.setScale(value[0], value[1]);
1110
+ break;
1111
+ }
1112
+ case 'opacity': {
1113
+ const value = numericPoseValue(channel.value, channel.binding.id, 1);
1114
+ node.opacity = clamp(value[0], 0, 1);
1115
+ break;
1116
+ }
1117
+ case 'visibility':
1118
+ if (typeof channel.value !== 'boolean') {
1119
+ throw new TypeError(`Animation2D visibility binding "${channel.binding.id}" requires a boolean.`);
1120
+ }
1121
+ node.entity.disabled = !channel.value;
1122
+ break;
1123
+ default:
1124
+ if (!this._applyStateMachineVisualChannel(node, channel.binding.path, channel.value)) {
1125
+ throw new RangeError(`Animation2D state-machine runtime does not support pose binding path "${channel.binding.path}".`);
1126
+ }
1127
+ }
1128
+ }
1129
+ for (const node of this._nodes)
1130
+ this._applyVisualOpacity(node);
1131
+ for (const [key, owners] of this._audioOwners) {
1132
+ if (owners.length === 0)
1133
+ continue;
1134
+ const target = parseEffectTargetKey(key);
1135
+ const node = this._nodesById.get(target.targetId);
1136
+ this._audioTarget(key)?.updateStateMachineProperties(speed, node ? this._resolveOpacity(node) : 1);
1137
+ }
1138
+ for (const effect of pose.effects)
1139
+ this._applyEffect(effect, playing, speed);
1140
+ this._lastAppliedTime = Number.NaN;
1141
+ }
1142
+ setPlaying(playing) {
1143
+ this._playing = playing;
1144
+ for (const [key, owners] of this._particleOwners) {
1145
+ if (owners.length === 0)
1146
+ continue;
1147
+ const target = this._particleTarget(key);
1148
+ if (target)
1149
+ target.playing = playing;
1150
+ }
1151
+ for (const [key, owners] of this._audioOwners) {
1152
+ if (owners.length === 0)
1153
+ continue;
1154
+ const target = this._audioTarget(key);
1155
+ target?.setStateMachinePlaying(playing, this._speed, reason => this._reportAudioRejection(key, reason));
1156
+ }
1157
+ }
1158
+ destroy() {
1159
+ this.resetSideEffects();
1160
+ super.destroy();
1161
+ }
1162
+ _applyVisualOpacity(node) {
1163
+ const opacity = this._resolveOpacity(node);
1164
+ if (node.deferredVisuals.length > 0 && this._isNodeActive(node)) {
1165
+ for (const deferred of node.deferredVisuals.splice(0))
1166
+ this._materializeCoreVisual(node, deferred);
1167
+ }
1168
+ for (const visual of node.visuals) {
1169
+ const alpha = visual.baseAlpha * opacity;
1170
+ if (Math.abs(alpha - visual.lastAlpha) < 1e-6)
1171
+ continue;
1172
+ visual.color[3] = alpha;
1173
+ visual.component.revision++;
1174
+ visual.lastAlpha = alpha;
1175
+ }
1176
+ for (const extension of node.extensions)
1177
+ extension.setOpacity?.(opacity);
1178
+ }
1179
+ _applyEffect(effect, playing, speed) {
1180
+ const payload = effect.cue.payload;
1181
+ if (!payload || typeof payload.targetId !== 'string' || !Number.isSafeInteger(payload.slot)) {
1182
+ throw new TypeError(`Animation2D effect cue "${effect.cue.id}" has an invalid HYA target payload.`);
1183
+ }
1184
+ const key = effectTargetKey(payload);
1185
+ this._effectPayloads.set(key, payload);
1186
+ const owners = effect.cue.kind === 'particle'
1187
+ ? this._particleOwners
1188
+ : this._audioOwners;
1189
+ const stack = owners.get(key) ?? [];
1190
+ if (!owners.has(key))
1191
+ owners.set(key, stack);
1192
+ if (effect.lifecycle === 'enter') {
1193
+ const existing = stack.indexOf(effect.actionId);
1194
+ if (existing >= 0)
1195
+ stack.splice(existing, 1);
1196
+ stack.push(effect.actionId);
1197
+ if (effect.cue.kind === 'particle') {
1198
+ const particle = this._particleTarget(key);
1199
+ if (particle) {
1200
+ particle.emitting = true;
1201
+ particle.restart(true);
1202
+ particle.playing = playing;
1203
+ }
1204
+ }
1205
+ else {
1206
+ const audio = this._audioTarget(key);
1207
+ const node = this._nodesById.get(payload.targetId);
1208
+ const opacity = node ? this._resolveOpacity(node) : 1;
1209
+ audio?.enterStateMachine(playing, speed, opacity, reason => this._reportAudioRejection(key, reason));
1210
+ }
1211
+ return;
1212
+ }
1213
+ if (effect.lifecycle === 'exit') {
1214
+ const index = stack.indexOf(effect.actionId);
1215
+ const wasDominant = index === stack.length - 1;
1216
+ if (index >= 0)
1217
+ stack.splice(index, 1);
1218
+ if (stack.length === 0) {
1219
+ owners.delete(key);
1220
+ if (effect.cue.kind === 'particle') {
1221
+ const particle = this._particleTarget(key);
1222
+ if (particle) {
1223
+ particle.playing = false;
1224
+ particle.emitting = false;
1225
+ particle.clear();
1226
+ }
1227
+ }
1228
+ else
1229
+ this._audioTarget(key)?.exitStateMachine();
1230
+ }
1231
+ else if (wasDominant && effect.cue.kind === 'particle') {
1232
+ const particle = this._particleTarget(key);
1233
+ if (particle) {
1234
+ particle.emitting = true;
1235
+ particle.restart(true);
1236
+ particle.playing = playing;
1237
+ }
1238
+ }
1239
+ return;
1240
+ }
1241
+ if (stack[stack.length - 1] !== effect.actionId)
1242
+ return;
1243
+ if (effect.lifecycle === 'loop')
1244
+ return;
1245
+ if (effect.cue.kind === 'particle') {
1246
+ const particle = this._particleTarget(key);
1247
+ if (particle) {
1248
+ particle.emitting = true;
1249
+ particle.restart(true);
1250
+ particle.playing = playing;
1251
+ }
1252
+ }
1253
+ else {
1254
+ const audio = this._audioTarget(key);
1255
+ const node = this._nodesById.get(payload.targetId);
1256
+ const opacity = node ? this._resolveOpacity(node) : 1;
1257
+ audio?.restartStateMachine(playing, speed, opacity, reason => this._reportAudioRejection(key, reason));
1258
+ }
1259
+ }
1260
+ _particleTarget(key) {
1261
+ const target = parseEffectTargetKey(key);
1262
+ return this._nodesById.get(target.targetId)?.particles[target.slot];
1263
+ }
1264
+ _audioTarget(key) {
1265
+ const target = parseEffectTargetKey(key);
1266
+ return this._nodesById.get(target.targetId)?.audio[target.slot];
1267
+ }
1268
+ _reportAudioRejection(key, reason) {
1269
+ if (this._reportedAudioRejections.has(key))
1270
+ return;
1271
+ this._reportedAudioRejections.add(key);
1272
+ const target = parseEffectTargetKey(key);
1273
+ const sourcePath = this._effectPayloads.get(key)?.sourcePath
1274
+ ?? `$.nodes[id=${JSON.stringify(target.targetId)}].components[audio=${target.slot}]`;
1275
+ this._diagnostics.push(audioAutoplayRejectedDiagnostic(sourcePath, reason));
1276
+ }
1277
+ }
1278
+ function effectTargetKey(payload) {
1279
+ return `${payload.targetId}\u0000${payload.slot}`;
1280
+ }
1281
+ function parseEffectTargetKey(key) {
1282
+ const separator = key.lastIndexOf('\u0000');
1283
+ return {
1284
+ targetId: key.slice(0, separator),
1285
+ slot: Number(key.slice(separator + 1)),
1286
+ };
1287
+ }
1288
+ function ownerCount(owners) {
1289
+ let count = 0;
1290
+ for (const stack of owners.values())
1291
+ count += stack.length;
1292
+ return count;
1293
+ }
1294
+ function numericPoseValue(value, bindingId, size) {
1295
+ if (!(value instanceof Float32Array) || value.length < size) {
1296
+ throw new TypeError(`Animation2D numeric binding "${bindingId}" requires ${size} Float32 values.`);
1297
+ }
1298
+ return value;
1299
+ }
1300
+ function clamp(value, min, max) {
1301
+ return Math.min(max, Math.max(min, value));
1302
+ }
1303
+
1304
+ /**
1305
+ * Stateful allocation-free sampler. Rotation values are radians and LINEAR
1306
+ * sampling follows the shortest signed arc in [-PI, PI).
1307
+ */
1308
+ class Animation2DSampler {
1309
+ track;
1310
+ output;
1311
+ discreteValue;
1312
+ _cursor = 0;
1313
+ constructor(track) {
1314
+ validateTrack(track);
1315
+ this.track = track;
1316
+ this.output = new Float32Array(isNumericBinding(track.binding)
1317
+ ? track.binding.valueSize
1318
+ : 0);
1319
+ }
1320
+ sample(timeSeconds, out = this.output) {
1321
+ if (!Number.isFinite(timeSeconds)) {
1322
+ throw new RangeError(`Animation2D sample time must be finite; received ${timeSeconds}.`);
1323
+ }
1324
+ const keyIndex = this._findKey(timeSeconds);
1325
+ if (!isNumericBinding(this.track.binding)) {
1326
+ this.discreteValue = this.track.values[keyIndex];
1327
+ return this.discreteValue;
1328
+ }
1329
+ const track = this.track;
1330
+ const size = track.binding.valueSize;
1331
+ if (out.length < size) {
1332
+ throw new RangeError(`Animation2D sampler output requires ${size} values; received ${out.length}.`);
1333
+ }
1334
+ const times = track.times;
1335
+ if (keyIndex >= times.length - 1 || track.interpolation === 'step') {
1336
+ copyNumericKey(track, keyIndex, out);
1337
+ return out;
1338
+ }
1339
+ const startTime = times[keyIndex];
1340
+ const linearAlpha = (timeSeconds - startTime) / (times[keyIndex + 1] - startTime);
1341
+ const alpha = track.interpolation === 'cubic-bezier'
1342
+ ? sampleCubicEasing(track.easings, keyIndex, linearAlpha)
1343
+ : linearAlpha;
1344
+ const startOffset = keyIndex * size;
1345
+ const endOffset = startOffset + size;
1346
+ if (track.binding.strategy === 'rotation') {
1347
+ const start = track.values[startOffset];
1348
+ const delta = wrapAngle(track.values[endOffset] - start);
1349
+ out[0] = wrapAngle(start + delta * alpha);
1350
+ return out;
1351
+ }
1352
+ if (track.spatialTangents) {
1353
+ const tangentOffset = keyIndex * 4;
1354
+ for (let component = 0; component < 2; component++) {
1355
+ const start = track.values[startOffset + component];
1356
+ const end = track.values[endOffset + component];
1357
+ out[component] = cubicSpatialCoordinate(alpha, start, start + track.spatialTangents[tangentOffset + component], end + track.spatialTangents[tangentOffset + 2 + component], end);
1358
+ }
1359
+ return out;
1360
+ }
1361
+ for (let component = 0; component < size; component++) {
1362
+ const start = track.values[startOffset + component];
1363
+ out[component] = start
1364
+ + (track.values[endOffset + component] - start) * alpha;
1365
+ }
1366
+ return out;
1367
+ }
1368
+ resetCursor() {
1369
+ this._cursor = 0;
1370
+ }
1371
+ _findKey(timeSeconds) {
1372
+ const times = this.track.times;
1373
+ const last = times.length - 1;
1374
+ if (last === 0 || timeSeconds <= times[0]) {
1375
+ this._cursor = 0;
1376
+ return 0;
1377
+ }
1378
+ if (timeSeconds >= times[last]) {
1379
+ this._cursor = Math.max(0, last - 1);
1380
+ return last;
1381
+ }
1382
+ let cursor = Math.min(this._cursor, last - 1);
1383
+ if (timeSeconds >= times[cursor] && timeSeconds < times[cursor + 1])
1384
+ return cursor;
1385
+ if (timeSeconds >= times[cursor + 1]) {
1386
+ while (cursor + 1 < last && timeSeconds >= times[cursor + 1])
1387
+ cursor++;
1388
+ this._cursor = cursor;
1389
+ return cursor;
1390
+ }
1391
+ let low = 0;
1392
+ let high = cursor;
1393
+ while (low <= high) {
1394
+ const middle = (low + high) >>> 1;
1395
+ if (timeSeconds < times[middle]) {
1396
+ high = middle - 1;
1397
+ }
1398
+ else if (timeSeconds >= times[middle + 1]) {
1399
+ low = middle + 1;
1400
+ }
1401
+ else {
1402
+ this._cursor = middle;
1403
+ return middle;
1404
+ }
1405
+ }
1406
+ this._cursor = Math.max(0, low);
1407
+ return this._cursor;
1408
+ }
1409
+ }
1410
+ function wrapAngle(value) {
1411
+ const twoPi = Math.PI * 2;
1412
+ let wrapped = (value + Math.PI) % twoPi;
1413
+ if (wrapped < 0)
1414
+ wrapped += twoPi;
1415
+ return wrapped - Math.PI;
1416
+ }
1417
+ function isNumericBinding(binding) {
1418
+ return binding.strategy === 'continuous' || binding.strategy === 'rotation';
1419
+ }
1420
+ function copyNumericKey(track, keyIndex, out) {
1421
+ const size = track.binding.valueSize;
1422
+ const offset = keyIndex * size;
1423
+ for (let component = 0; component < size; component++) {
1424
+ out[component] = track.values[offset + component];
1425
+ }
1426
+ if (track.binding.strategy === 'rotation')
1427
+ out[0] = wrapAngle(out[0]);
1428
+ }
1429
+ function validateTrack(track) {
1430
+ if (!track.id)
1431
+ throw new RangeError('Animation2D track id must not be empty.');
1432
+ if (!track.binding.id)
1433
+ throw new RangeError('Animation2D binding id must not be empty.');
1434
+ if (track.times.length === 0) {
1435
+ throw new RangeError(`Animation2D track "${track.id}" must contain at least one key.`);
1436
+ }
1437
+ for (let index = 0; index < track.times.length; index++) {
1438
+ const time = track.times[index];
1439
+ if (!Number.isFinite(time) || (index > 0 && time <= track.times[index - 1])) {
1440
+ throw new RangeError(`Animation2D track "${track.id}" times must be finite and strictly increasing.`);
1441
+ }
1442
+ }
1443
+ if (isNumericBinding(track.binding)) {
1444
+ const numericTrack = track;
1445
+ const size = track.binding.valueSize ?? 0;
1446
+ if (!Number.isInteger(size) || size <= 0) {
1447
+ throw new RangeError(`Animation2D numeric binding "${track.binding.id}" requires a positive valueSize.`);
1448
+ }
1449
+ if (track.binding.strategy === 'rotation' && size !== 1) {
1450
+ throw new RangeError(`Animation2D rotation binding "${track.binding.id}" must have valueSize 1.`);
1451
+ }
1452
+ const expected = track.times.length * size;
1453
+ if (!(track.values instanceof Float32Array) || track.values.length !== expected) {
1454
+ throw new RangeError(`Animation2D track "${track.id}" requires ${expected} numeric values.`);
1455
+ }
1456
+ if (numericTrack.interpolation === 'cubic-bezier') {
1457
+ const expectedEasings = Math.max(0, track.times.length - 1) * 4;
1458
+ if (!(numericTrack.easings instanceof Float32Array) || numericTrack.easings.length !== expectedEasings) {
1459
+ throw new RangeError(`Animation2D track "${track.id}" requires ${expectedEasings} cubic easing values.`);
1460
+ }
1461
+ for (let index = 0; index < numericTrack.easings.length; index += 4) {
1462
+ const x1 = numericTrack.easings[index];
1463
+ const x2 = numericTrack.easings[index + 2];
1464
+ if (x1 < 0 || x1 > 1 || x2 < 0 || x2 > 1) {
1465
+ throw new RangeError(`Animation2D track "${track.id}" cubic easing x controls must be in [0, 1].`);
1466
+ }
1467
+ }
1468
+ }
1469
+ else if (numericTrack.easings !== undefined) {
1470
+ throw new RangeError(`Animation2D track "${track.id}" easings require cubic-bezier interpolation.`);
1471
+ }
1472
+ if (numericTrack.spatialTangents !== undefined) {
1473
+ const expectedTangents = Math.max(0, track.times.length - 1) * 4;
1474
+ if (track.binding.path !== 'transform.position' || size !== 2) {
1475
+ throw new RangeError(`Animation2D track "${track.id}" spatialTangents require transform.position/2.`);
1476
+ }
1477
+ if (numericTrack.interpolation === 'step') {
1478
+ throw new RangeError(`Animation2D track "${track.id}" spatialTangents cannot use step.`);
1479
+ }
1480
+ if (!(numericTrack.spatialTangents instanceof Float32Array)
1481
+ || numericTrack.spatialTangents.length !== expectedTangents) {
1482
+ throw new RangeError(`Animation2D track "${track.id}" requires ${expectedTangents} spatialTangents.`);
1483
+ }
1484
+ }
1485
+ return;
1486
+ }
1487
+ if (track.interpolation !== 'step') {
1488
+ throw new RangeError(`Animation2D ${track.binding.strategy} track "${track.id}" must use step interpolation.`);
1489
+ }
1490
+ if (track.values.length !== track.times.length) {
1491
+ throw new RangeError(`Animation2D track "${track.id}" requires one discrete value per key.`);
1492
+ }
1493
+ }
1494
+ function sampleCubicEasing(easings, segment, x) {
1495
+ const offset = segment * 4;
1496
+ const x1 = easings[offset];
1497
+ const y1 = easings[offset + 1];
1498
+ const x2 = easings[offset + 2];
1499
+ const y2 = easings[offset + 3];
1500
+ let parameter = x;
1501
+ for (let iteration = 0; iteration < 6; iteration++) {
1502
+ const estimate = cubicCoordinate(parameter, x1, x2) - x;
1503
+ const derivative = cubicDerivative(parameter, x1, x2);
1504
+ if (Math.abs(derivative) < 1e-6)
1505
+ break;
1506
+ parameter = Math.min(1, Math.max(0, parameter - estimate / derivative));
1507
+ }
1508
+ return cubicCoordinate(parameter, y1, y2);
1509
+ }
1510
+ function cubicCoordinate(value, first, second) {
1511
+ const inverse = 1 - value;
1512
+ return 3 * inverse * inverse * value * first
1513
+ + 3 * inverse * value * value * second
1514
+ + value * value * value;
1515
+ }
1516
+ function cubicDerivative(value, first, second) {
1517
+ const inverse = 1 - value;
1518
+ return 3 * inverse * inverse * first
1519
+ + 6 * inverse * value * (second - first)
1520
+ + 3 * value * value * (1 - second);
1521
+ }
1522
+ function cubicSpatialCoordinate(value, start, first, second, end) {
1523
+ const inverse = 1 - value;
1524
+ return inverse * inverse * inverse * start
1525
+ + 3 * inverse * inverse * value * first
1526
+ + 3 * inverse * value * value * second
1527
+ + value * value * value * end;
1528
+ }
1529
+
1530
+ /**
1531
+ * Mutable mixer-owned playback state. Effect lifecycle records and sampler
1532
+ * outputs are allocated at construction and reused on every frame.
1533
+ */
1534
+ class Animation2DActionRuntime {
1535
+ id;
1536
+ clip;
1537
+ order;
1538
+ runtimeTracks;
1539
+ runtimeEffects;
1540
+ _owner;
1541
+ _effectActive;
1542
+ _pendingEffects = [];
1543
+ _pendingEffectOutput = {
1544
+ emit: effect => { this._pendingEffects.push(effect); },
1545
+ };
1546
+ _valid = true;
1547
+ _status = 'idle';
1548
+ _paused = false;
1549
+ _time = 0;
1550
+ _rawTime = 0;
1551
+ _timeScale;
1552
+ _weight;
1553
+ _loop;
1554
+ _repetitions;
1555
+ _clampWhenFinished;
1556
+ _blendMode;
1557
+ _mask;
1558
+ _layer;
1559
+ _effectsStarted = false;
1560
+ _fadeActive = false;
1561
+ _fadeStart = 0;
1562
+ _fadeEnd = 0;
1563
+ _fadeFrom = 1;
1564
+ _fadeTo = 1;
1565
+ _fadeValue = 1;
1566
+ constructor(owner, clip, id, order, options = {}) {
1567
+ validateClip(clip);
1568
+ this._owner = owner;
1569
+ this.clip = clip;
1570
+ this.id = id;
1571
+ this.order = order;
1572
+ this._loop = options.loop ?? 'once';
1573
+ this._repetitions = validateRepetitions(options.repetitions ?? (this._loop === 'once' ? 1 : Infinity));
1574
+ this._clampWhenFinished = options.clampWhenFinished ?? false;
1575
+ this._timeScale = finiteNumber(options.timeScale ?? 1, 'Animation2DAction.timeScale');
1576
+ this._weight = nonNegativeNumber(options.weight ?? 1, 'Animation2DAction.weight');
1577
+ this._fadeValue = this._weight;
1578
+ this._layer = integerNumber(options.layer ?? 0, 'Animation2DAction.layer');
1579
+ this._blendMode = options.blendMode ?? 'override';
1580
+ this._mask = options.mask ?? null;
1581
+ const tracks = [];
1582
+ for (let index = 0; index < clip.tracks.length; index++) {
1583
+ const track = clip.tracks[index];
1584
+ const sampler = new Animation2DSampler(track);
1585
+ const reference = isNumericBinding(track.binding)
1586
+ ? new Float32Array(track.binding.valueSize)
1587
+ : new Float32Array(0);
1588
+ if (reference.length > 0)
1589
+ sampler.sample(0, reference);
1590
+ tracks.push({ track, sampler, reference });
1591
+ }
1592
+ this.runtimeTracks = tracks;
1593
+ const effects = [];
1594
+ const sourceEffects = clip.effects ?? [];
1595
+ for (let index = 0; index < sourceEffects.length; index++) {
1596
+ const cue = sourceEffects[index];
1597
+ const start = cue.start;
1598
+ const end = cue.end ?? clip.duration;
1599
+ validateEffect(cue, start, end, clip.duration);
1600
+ effects.push({
1601
+ cue,
1602
+ start,
1603
+ end,
1604
+ fullSpan: start === 0 && end === clip.duration,
1605
+ loopBehavior: cue.loopBehavior ?? (cue.kind === 'particle' ? 'restart' : 'continue'),
1606
+ enterEvent: effectEvent(id, clip.id, cue, 'enter'),
1607
+ exitEvent: effectEvent(id, clip.id, cue, 'exit'),
1608
+ restartEvent: effectEvent(id, clip.id, cue, 'restart'),
1609
+ loopEvent: effectEvent(id, clip.id, cue, 'loop'),
1610
+ seekEvent: effectEvent(id, clip.id, cue, 'seek'),
1611
+ });
1612
+ }
1613
+ this.runtimeEffects = effects;
1614
+ this._effectActive = new Uint8Array(effects.length);
1615
+ }
1616
+ get duration() { return this.clip.duration; }
1617
+ get status() { return this._status; }
1618
+ get paused() { return this._paused; }
1619
+ set paused(value) {
1620
+ this._assertValid();
1621
+ this._paused = Boolean(value);
1622
+ if (this._paused && this._status === 'running')
1623
+ this._status = 'paused';
1624
+ else if (!this._paused && this._status === 'paused')
1625
+ this._status = 'running';
1626
+ }
1627
+ get time() { return this._time; }
1628
+ set time(value) { this.seek(value); }
1629
+ get timeScale() { return this._timeScale; }
1630
+ set timeScale(value) {
1631
+ this._assertValid();
1632
+ this._timeScale = finiteNumber(value, 'Animation2DAction.timeScale');
1633
+ }
1634
+ get weight() { return this._weight; }
1635
+ set weight(value) {
1636
+ this._assertValid();
1637
+ this._weight = nonNegativeNumber(value, 'Animation2DAction.weight');
1638
+ this._fadeActive = false;
1639
+ this._fadeValue = this._weight;
1640
+ }
1641
+ get layer() { return this._layer; }
1642
+ set layer(value) {
1643
+ this._assertValid();
1644
+ this._layer = integerNumber(value, 'Animation2DAction.layer');
1645
+ }
1646
+ get loop() { return this._loop; }
1647
+ set loop(value) {
1648
+ this._assertValid();
1649
+ if (value !== 'once' && value !== 'repeat' && value !== 'ping-pong') {
1650
+ throw new RangeError(`Unsupported Animation2D loop mode "${String(value)}".`);
1651
+ }
1652
+ this._loop = value;
1653
+ this._setSampleTime(0);
1654
+ }
1655
+ get repetitions() { return this._repetitions; }
1656
+ set repetitions(value) {
1657
+ this._assertValid();
1658
+ this._repetitions = validateRepetitions(value);
1659
+ }
1660
+ get clampWhenFinished() { return this._clampWhenFinished; }
1661
+ set clampWhenFinished(value) {
1662
+ this._assertValid();
1663
+ this._clampWhenFinished = Boolean(value);
1664
+ }
1665
+ get blendMode() { return this._blendMode; }
1666
+ set blendMode(value) {
1667
+ this._assertValid();
1668
+ if (value !== 'override' && value !== 'additive') {
1669
+ throw new RangeError(`Unsupported Animation2D blend mode "${String(value)}".`);
1670
+ }
1671
+ this._blendMode = value;
1672
+ }
1673
+ get mask() { return this._mask; }
1674
+ set mask(value) {
1675
+ this._assertValid();
1676
+ this._mask = value;
1677
+ }
1678
+ get effectiveWeight() {
1679
+ if (!this._valid || this._paused || (this._status !== 'running'
1680
+ && !(this._status === 'finished' && this._clampWhenFinished)))
1681
+ return 0;
1682
+ return this._fadeFactor(this._owner.time);
1683
+ }
1684
+ play() {
1685
+ this._assertValid();
1686
+ if (this._status === 'finished' && !this._clampWhenFinished) {
1687
+ this._rawTime = this._timeScale < 0 ? this.clip.duration : 0;
1688
+ this._setSampleTime(0);
1689
+ this._effectsStarted = false;
1690
+ }
1691
+ this._paused = false;
1692
+ this._status = 'running';
1693
+ return this;
1694
+ }
1695
+ pause() {
1696
+ this.paused = true;
1697
+ return this;
1698
+ }
1699
+ stop() {
1700
+ this._assertValid();
1701
+ this._queueActiveExits();
1702
+ this._paused = false;
1703
+ this._status = 'stopped';
1704
+ this._effectsStarted = false;
1705
+ return this;
1706
+ }
1707
+ reset() {
1708
+ this._assertValid();
1709
+ this._queueActiveExits();
1710
+ this._paused = false;
1711
+ this._status = 'idle';
1712
+ this._rawTime = 0;
1713
+ this._time = 0;
1714
+ this._effectsStarted = false;
1715
+ this._fadeActive = false;
1716
+ this._fadeValue = this._weight;
1717
+ this._resetSamplerCursors();
1718
+ return this;
1719
+ }
1720
+ seek(timeSeconds) {
1721
+ this._assertValid();
1722
+ const next = Math.min(this.clip.duration, Math.max(0, finiteNumber(timeSeconds, 'Animation2DAction.time')));
1723
+ for (let index = 0; index < this.runtimeEffects.length; index++) {
1724
+ const wasActive = this._effectActive[index] !== 0;
1725
+ const active = effectActiveAt(this.runtimeEffects[index], next, this.clip.duration);
1726
+ const effect = this.runtimeEffects[index];
1727
+ if (wasActive && !active)
1728
+ this._pendingEffects.push(effect.exitEvent);
1729
+ this._pendingEffects.push(effect.seekEvent);
1730
+ if (!wasActive && active)
1731
+ this._pendingEffects.push(effect.enterEvent);
1732
+ this._effectActive[index] = active ? 1 : 0;
1733
+ }
1734
+ this._rawTime = next;
1735
+ this._time = next;
1736
+ this._effectsStarted = true;
1737
+ this._resetSamplerCursors();
1738
+ return this;
1739
+ }
1740
+ /**
1741
+ * Advances a controller-owned absolute playhead without classifying the
1742
+ * move as a seek. Crossed side-effect boundaries are queued exactly once.
1743
+ */
1744
+ synchronizeTime(timeSeconds) {
1745
+ this._assertValid();
1746
+ const after = this._clampSynchronizedTime(timeSeconds);
1747
+ const terminalDirection = this._terminalDirection(after);
1748
+ if (!this._effectsStarted) {
1749
+ this._emitInitialEffects(this._pendingEffectOutput);
1750
+ this._effectsStarted = true;
1751
+ }
1752
+ this._emitEffectCrossings(this._rawTime, after, this._pendingEffectOutput, terminalDirection);
1753
+ this._rawTime = after;
1754
+ this._setSampleTime(terminalDirection);
1755
+ this._syncEffectActivity(terminalDirection);
1756
+ this._resetSamplerCursors();
1757
+ return this;
1758
+ }
1759
+ reverse() {
1760
+ this._assertValid();
1761
+ this._timeScale = -Math.max(Number.EPSILON, Math.abs(this._timeScale));
1762
+ return this;
1763
+ }
1764
+ fadeTo(targetWeight, durationSeconds) {
1765
+ this._assertValid();
1766
+ const target = nonNegativeNumber(targetWeight, 'Animation2DAction.fade target');
1767
+ const duration = nonNegativeNumber(durationSeconds, 'Animation2DAction.fade duration');
1768
+ this._scheduleFade(this._fadeFactor(this._owner.time), target, duration);
1769
+ return this;
1770
+ }
1771
+ fadeIn(durationSeconds) {
1772
+ this._assertValid();
1773
+ const duration = nonNegativeNumber(durationSeconds, 'Animation2DAction.fade duration');
1774
+ this._scheduleFade(0, this._weight > 0 ? this._weight : 1, duration);
1775
+ return this;
1776
+ }
1777
+ fadeOut(durationSeconds) {
1778
+ return this.fadeTo(0, durationSeconds);
1779
+ }
1780
+ crossFadeFrom(source, durationSeconds) {
1781
+ this._assertSibling(source);
1782
+ source.play().fadeOut(durationSeconds);
1783
+ this.play().fadeIn(durationSeconds);
1784
+ return this;
1785
+ }
1786
+ crossFadeTo(destination, durationSeconds) {
1787
+ this._assertSibling(destination);
1788
+ destination.crossFadeFrom(this, durationSeconds);
1789
+ return this;
1790
+ }
1791
+ acceptsBinding(binding) {
1792
+ const include = this._mask?.include;
1793
+ if (include && include.length > 0) {
1794
+ let accepted = false;
1795
+ for (let index = 0; index < include.length; index++) {
1796
+ if (include[index] === binding.id) {
1797
+ accepted = true;
1798
+ break;
1799
+ }
1800
+ }
1801
+ if (!accepted)
1802
+ return false;
1803
+ }
1804
+ const exclude = this._mask?.exclude;
1805
+ if (exclude) {
1806
+ for (let index = 0; index < exclude.length; index++) {
1807
+ if (exclude[index] === binding.id)
1808
+ return false;
1809
+ }
1810
+ }
1811
+ return true;
1812
+ }
1813
+ flushPendingEffects(out) {
1814
+ for (let index = 0; index < this._pendingEffects.length; index++) {
1815
+ out.emit(this._pendingEffects[index]);
1816
+ }
1817
+ this._pendingEffects.length = 0;
1818
+ }
1819
+ advance(mixerDelta, out) {
1820
+ if (!this._valid)
1821
+ return;
1822
+ this._finishFade(this._owner.time);
1823
+ this.flushPendingEffects(out);
1824
+ if (this._paused || this._status !== 'running')
1825
+ return;
1826
+ if (!this._effectsStarted) {
1827
+ this._emitInitialEffects(out);
1828
+ this._effectsStarted = true;
1829
+ }
1830
+ const actionDelta = mixerDelta * this._timeScale;
1831
+ if (actionDelta === 0 || this.clip.duration === 0)
1832
+ return;
1833
+ const before = this._rawTime;
1834
+ let after = before + actionDelta;
1835
+ let terminalDirection = 0;
1836
+ const totalDuration = this._loop === 'once'
1837
+ ? this.clip.duration
1838
+ : this._repetitions === Infinity
1839
+ ? Infinity
1840
+ : this.clip.duration * this._repetitions;
1841
+ if (totalDuration !== Infinity) {
1842
+ if (actionDelta > 0 && after >= totalDuration) {
1843
+ after = totalDuration;
1844
+ terminalDirection = 1;
1845
+ }
1846
+ else if (actionDelta < 0 && after <= 0) {
1847
+ after = 0;
1848
+ terminalDirection = -1;
1849
+ }
1850
+ }
1851
+ this._emitEffectCrossings(before, after, out, terminalDirection);
1852
+ this._rawTime = after;
1853
+ this._setSampleTime(terminalDirection);
1854
+ this._syncEffectActivity(terminalDirection);
1855
+ if (terminalDirection !== 0) {
1856
+ this._status = 'finished';
1857
+ if (!this._clampWhenFinished)
1858
+ this._queueActiveExits();
1859
+ }
1860
+ }
1861
+ /** @internal Allocates one checkpoint that can be reused for every update. */
1862
+ createSynchronizedState() {
1863
+ return {
1864
+ status: this._status,
1865
+ paused: this._paused,
1866
+ time: this._time,
1867
+ rawTime: this._rawTime,
1868
+ timeScale: this._timeScale,
1869
+ weight: this._weight,
1870
+ loop: this._loop,
1871
+ repetitions: this._repetitions,
1872
+ clampWhenFinished: this._clampWhenFinished,
1873
+ blendMode: this._blendMode,
1874
+ mask: this._mask,
1875
+ layer: this._layer,
1876
+ effectsStarted: this._effectsStarted,
1877
+ fadeActive: this._fadeActive,
1878
+ fadeStart: this._fadeStart,
1879
+ fadeEnd: this._fadeEnd,
1880
+ fadeFrom: this._fadeFrom,
1881
+ fadeTo: this._fadeTo,
1882
+ fadeValue: this._fadeValue,
1883
+ effectActive: new Uint8Array(this._effectActive.length),
1884
+ pendingEffects: [],
1885
+ };
1886
+ }
1887
+ /** @internal Captures playhead, fade, and effect-cursor state without sampling. */
1888
+ captureSynchronizedState(state) {
1889
+ this._assertValid();
1890
+ if (state.effectActive.length !== this._effectActive.length) {
1891
+ throw new RangeError('Animation2D action checkpoint has an incompatible effect count.');
1892
+ }
1893
+ state.status = this._status;
1894
+ state.paused = this._paused;
1895
+ state.time = this._time;
1896
+ state.rawTime = this._rawTime;
1897
+ state.timeScale = this._timeScale;
1898
+ state.weight = this._weight;
1899
+ state.loop = this._loop;
1900
+ state.repetitions = this._repetitions;
1901
+ state.clampWhenFinished = this._clampWhenFinished;
1902
+ state.blendMode = this._blendMode;
1903
+ state.mask = this._mask;
1904
+ state.layer = this._layer;
1905
+ state.effectsStarted = this._effectsStarted;
1906
+ state.fadeActive = this._fadeActive;
1907
+ state.fadeStart = this._fadeStart;
1908
+ state.fadeEnd = this._fadeEnd;
1909
+ state.fadeFrom = this._fadeFrom;
1910
+ state.fadeTo = this._fadeTo;
1911
+ state.fadeValue = this._fadeValue;
1912
+ state.effectActive.set(this._effectActive);
1913
+ state.pendingEffects.length = 0;
1914
+ for (const effect of this._pendingEffects)
1915
+ state.pendingEffects.push(effect);
1916
+ }
1917
+ /** @internal Restores an exact controller transaction checkpoint. */
1918
+ restoreSynchronizedState(state) {
1919
+ this._assertValid();
1920
+ if (state.effectActive.length !== this._effectActive.length) {
1921
+ throw new RangeError('Animation2D action checkpoint has an incompatible effect count.');
1922
+ }
1923
+ this._status = state.status;
1924
+ this._paused = state.paused;
1925
+ this._time = state.time;
1926
+ this._rawTime = state.rawTime;
1927
+ this._timeScale = state.timeScale;
1928
+ this._weight = state.weight;
1929
+ this._loop = state.loop;
1930
+ this._repetitions = state.repetitions;
1931
+ this._clampWhenFinished = state.clampWhenFinished;
1932
+ this._blendMode = state.blendMode;
1933
+ this._mask = state.mask;
1934
+ this._layer = state.layer;
1935
+ this._effectsStarted = state.effectsStarted;
1936
+ this._fadeActive = state.fadeActive;
1937
+ this._fadeStart = state.fadeStart;
1938
+ this._fadeEnd = state.fadeEnd;
1939
+ this._fadeFrom = state.fadeFrom;
1940
+ this._fadeTo = state.fadeTo;
1941
+ this._fadeValue = state.fadeValue;
1942
+ this._effectActive.set(state.effectActive);
1943
+ this._pendingEffects.length = 0;
1944
+ for (const effect of state.pendingEffects)
1945
+ this._pendingEffects.push(effect);
1946
+ this._resetSamplerCursors();
1947
+ }
1948
+ invalidate() {
1949
+ if (!this._valid)
1950
+ return;
1951
+ this._valid = false;
1952
+ this._status = 'stopped';
1953
+ this._paused = false;
1954
+ this._fadeActive = false;
1955
+ this._pendingEffects.length = 0;
1956
+ this._effectActive.fill(0);
1957
+ }
1958
+ _setSampleTime(terminalDirection) {
1959
+ const duration = this.clip.duration;
1960
+ if (duration === 0) {
1961
+ this._time = 0;
1962
+ return;
1963
+ }
1964
+ if (this._loop === 'once') {
1965
+ this._time = Math.min(duration, Math.max(0, this._rawTime));
1966
+ return;
1967
+ }
1968
+ if (terminalDirection > 0 && this._loop === 'repeat') {
1969
+ this._time = duration;
1970
+ return;
1971
+ }
1972
+ const traversal = Math.floor(this._rawTime / duration);
1973
+ let phase = this._rawTime - traversal * duration;
1974
+ if (phase < 0)
1975
+ phase += duration;
1976
+ if (this._loop === 'repeat') {
1977
+ this._time = phase;
1978
+ }
1979
+ else {
1980
+ this._time = isEven(traversal) ? phase : duration - phase;
1981
+ }
1982
+ }
1983
+ _emitInitialEffects(out) {
1984
+ for (let index = 0; index < this.runtimeEffects.length; index++) {
1985
+ const active = effectActiveAt(this.runtimeEffects[index], this._time, this.clip.duration);
1986
+ this._effectActive[index] = active ? 1 : 0;
1987
+ if (active)
1988
+ out.emit(this.runtimeEffects[index].enterEvent);
1989
+ }
1990
+ }
1991
+ _emitEffectCrossings(before, after, out, terminalDirection = 0) {
1992
+ if (before === after || this.runtimeEffects.length === 0)
1993
+ return;
1994
+ if (this._loop === 'repeat') {
1995
+ this._emitRepeatEffectCrossings(before, after, out, terminalDirection);
1996
+ return;
1997
+ }
1998
+ if (this._loop === 'once') {
1999
+ this._emitOnceEffectCrossings(before, after, out);
2000
+ return;
2001
+ }
2002
+ this._emitPingPongEffectCrossings(before, after, out, terminalDirection);
2003
+ }
2004
+ _emitOnceEffectCrossings(before, after, out) {
2005
+ for (let index = 0; index < this.runtimeEffects.length; index++) {
2006
+ const effect = this.runtimeEffects[index];
2007
+ if (after > before) {
2008
+ if (effect.start > before && effect.start <= after)
2009
+ out.emit(effect.enterEvent);
2010
+ if (effect.end > before && effect.end <= after)
2011
+ out.emit(effect.exitEvent);
2012
+ }
2013
+ else {
2014
+ if (effect.end < before && effect.end >= after)
2015
+ out.emit(effect.enterEvent);
2016
+ if (effect.start < before && effect.start >= after)
2017
+ out.emit(effect.exitEvent);
2018
+ }
2019
+ }
2020
+ }
2021
+ _emitRepeatEffectCrossings(before, after, out, terminalDirection) {
2022
+ const duration = this.clip.duration;
2023
+ const minimum = Math.min(before, after);
2024
+ const maximum = Math.max(before, after);
2025
+ const firstCycle = Math.floor(minimum / duration) - 1;
2026
+ const lastCycle = Math.floor(maximum / duration) + 1;
2027
+ for (let effectIndex = 0; effectIndex < this.runtimeEffects.length; effectIndex++) {
2028
+ const effect = this.runtimeEffects[effectIndex];
2029
+ if (effect.fullSpan) {
2030
+ if (after > before) {
2031
+ for (let cycle = firstCycle; cycle <= lastCycle; cycle++) {
2032
+ const boundary = cycle * duration;
2033
+ if (boundary > before && boundary <= after) {
2034
+ if (terminalDirection > 0 && boundary === after)
2035
+ out.emit(effect.exitEvent);
2036
+ else
2037
+ this._emitLoopEffect(effect, out);
2038
+ }
2039
+ }
2040
+ }
2041
+ else {
2042
+ for (let cycle = lastCycle; cycle >= firstCycle; cycle--) {
2043
+ const boundary = cycle * duration;
2044
+ if (boundary < before && boundary >= after) {
2045
+ if (terminalDirection < 0 && boundary === after)
2046
+ out.emit(effect.exitEvent);
2047
+ else
2048
+ this._emitLoopEffect(effect, out);
2049
+ }
2050
+ }
2051
+ }
2052
+ continue;
2053
+ }
2054
+ if (after > before) {
2055
+ for (let cycle = firstCycle; cycle <= lastCycle; cycle++) {
2056
+ const cycleStart = cycle * duration;
2057
+ const enter = cycleStart + effect.start;
2058
+ const exit = cycleStart + effect.end;
2059
+ if (enter > before && enter <= after
2060
+ && !(terminalDirection > 0 && enter === after))
2061
+ out.emit(effect.enterEvent);
2062
+ if (exit > before && exit <= after)
2063
+ out.emit(effect.exitEvent);
2064
+ }
2065
+ }
2066
+ else {
2067
+ for (let cycle = lastCycle; cycle >= firstCycle; cycle--) {
2068
+ const cycleStart = cycle * duration;
2069
+ const enter = cycleStart + effect.end;
2070
+ const exit = cycleStart + effect.start;
2071
+ if (enter < before && enter >= after
2072
+ && !(terminalDirection < 0 && enter === after))
2073
+ out.emit(effect.enterEvent);
2074
+ if (exit < before && exit >= after)
2075
+ out.emit(effect.exitEvent);
2076
+ }
2077
+ }
2078
+ }
2079
+ }
2080
+ _emitPingPongEffectCrossings(before, after, out, terminalDirection) {
2081
+ const duration = this.clip.duration;
2082
+ const minimum = Math.min(before, after);
2083
+ const maximum = Math.max(before, after);
2084
+ const firstTraversal = Math.floor(minimum / duration) - 1;
2085
+ const lastTraversal = Math.floor(maximum / duration) + 1;
2086
+ for (let effectIndex = 0; effectIndex < this.runtimeEffects.length; effectIndex++) {
2087
+ const effect = this.runtimeEffects[effectIndex];
2088
+ if (effect.fullSpan) {
2089
+ if (after > before) {
2090
+ for (let traversal = firstTraversal; traversal <= lastTraversal; traversal++) {
2091
+ const boundary = traversal * duration;
2092
+ if (boundary <= before || boundary > after)
2093
+ continue;
2094
+ if (terminalDirection > 0 && boundary === after)
2095
+ out.emit(effect.exitEvent);
2096
+ else
2097
+ this._emitLoopEffect(effect, out);
2098
+ }
2099
+ }
2100
+ else {
2101
+ for (let traversal = lastTraversal; traversal >= firstTraversal; traversal--) {
2102
+ const boundary = traversal * duration;
2103
+ if (boundary >= before || boundary < after)
2104
+ continue;
2105
+ if (terminalDirection < 0 && boundary === after)
2106
+ out.emit(effect.exitEvent);
2107
+ else
2108
+ this._emitLoopEffect(effect, out);
2109
+ }
2110
+ }
2111
+ continue;
2112
+ }
2113
+ if (after > before) {
2114
+ for (let traversal = firstTraversal; traversal <= lastTraversal; traversal++) {
2115
+ const traversalStart = traversal * duration;
2116
+ const forward = isEven(traversal);
2117
+ const enter = traversalStart + (forward ? effect.start : duration - effect.end);
2118
+ const exit = traversalStart + (forward ? effect.end : duration - effect.start);
2119
+ if (enter > before && enter <= after
2120
+ && !(terminalDirection > 0 && enter === after))
2121
+ out.emit(effect.enterEvent);
2122
+ if (exit > before && exit <= after)
2123
+ out.emit(effect.exitEvent);
2124
+ }
2125
+ }
2126
+ else {
2127
+ for (let traversal = lastTraversal; traversal >= firstTraversal; traversal--) {
2128
+ const traversalStart = traversal * duration;
2129
+ const forward = isEven(traversal);
2130
+ const forwardEnter = traversalStart
2131
+ + (forward ? effect.start : duration - effect.end);
2132
+ const forwardExit = traversalStart
2133
+ + (forward ? effect.end : duration - effect.start);
2134
+ if (forwardExit < before && forwardExit >= after
2135
+ && !(terminalDirection < 0 && forwardExit === after))
2136
+ out.emit(effect.enterEvent);
2137
+ if (forwardEnter < before && forwardEnter >= after)
2138
+ out.emit(effect.exitEvent);
2139
+ }
2140
+ }
2141
+ }
2142
+ }
2143
+ _emitLoopEffect(effect, out) {
2144
+ out.emit(effect.loopBehavior === 'restart' ? effect.restartEvent : effect.loopEvent);
2145
+ }
2146
+ _syncEffectActivity(terminalDirection = 0) {
2147
+ for (let index = 0; index < this.runtimeEffects.length; index++) {
2148
+ const effect = this.runtimeEffects[index];
2149
+ const active = effect.fullSpan && this._loop === 'ping-pong'
2150
+ ? terminalDirection === 0
2151
+ : effectActiveAt(effect, this._time, this.clip.duration);
2152
+ this._effectActive[index] = active ? 1 : 0;
2153
+ }
2154
+ }
2155
+ _queueActiveExits() {
2156
+ for (let index = 0; index < this._effectActive.length; index++) {
2157
+ if (this._effectActive[index] === 0)
2158
+ continue;
2159
+ this._pendingEffects.push(this.runtimeEffects[index].exitEvent);
2160
+ this._effectActive[index] = 0;
2161
+ }
2162
+ }
2163
+ _scheduleFade(from, to, duration) {
2164
+ this._fadeFrom = from;
2165
+ this._fadeTo = to;
2166
+ this._fadeStart = this._owner.time;
2167
+ this._fadeEnd = this._fadeStart + duration;
2168
+ if (duration === 0) {
2169
+ this._weight = to;
2170
+ this._fadeValue = to;
2171
+ this._fadeActive = false;
2172
+ }
2173
+ else {
2174
+ this._fadeValue = from;
2175
+ this._fadeActive = true;
2176
+ }
2177
+ }
2178
+ _fadeFactor(time) {
2179
+ if (!this._fadeActive)
2180
+ return this._fadeValue;
2181
+ if (time <= this._fadeStart)
2182
+ return this._fadeFrom;
2183
+ if (time >= this._fadeEnd)
2184
+ return this._fadeTo;
2185
+ const alpha = (time - this._fadeStart) / (this._fadeEnd - this._fadeStart);
2186
+ return this._fadeFrom + (this._fadeTo - this._fadeFrom) * alpha;
2187
+ }
2188
+ _finishFade(time) {
2189
+ if (!this._fadeActive || time < this._fadeEnd)
2190
+ return;
2191
+ this._weight = this._fadeTo;
2192
+ this._fadeValue = this._fadeTo;
2193
+ this._fadeActive = false;
2194
+ }
2195
+ _assertSibling(action) {
2196
+ this._assertValid();
2197
+ if (action._owner !== this._owner || !action._valid) {
2198
+ throw new RangeError('Animation2D cross-fade actions must belong to the same active mixer.');
2199
+ }
2200
+ }
2201
+ _clampSynchronizedTime(timeSeconds) {
2202
+ const next = finiteNumber(timeSeconds, 'Animation2DAction synchronized time');
2203
+ if (this._loop === 'once')
2204
+ return Math.min(this.clip.duration, Math.max(0, next));
2205
+ if (this._repetitions === Infinity)
2206
+ return next;
2207
+ return Math.min(this.clip.duration * this._repetitions, Math.max(0, next));
2208
+ }
2209
+ _terminalDirection(time) {
2210
+ if (this._loop === 'once') {
2211
+ if (time === this.clip.duration && this._rawTime < time)
2212
+ return 1;
2213
+ if (time === 0 && this._rawTime > time)
2214
+ return -1;
2215
+ return 0;
2216
+ }
2217
+ if (this._repetitions === Infinity)
2218
+ return 0;
2219
+ const totalDuration = this.clip.duration * this._repetitions;
2220
+ if (time === totalDuration && this._rawTime < time)
2221
+ return 1;
2222
+ if (time === 0 && this._rawTime > time)
2223
+ return -1;
2224
+ return 0;
2225
+ }
2226
+ _resetSamplerCursors() {
2227
+ for (let index = 0; index < this.runtimeTracks.length; index++) {
2228
+ this.runtimeTracks[index].sampler.resetCursor();
2229
+ }
2230
+ }
2231
+ _assertValid() {
2232
+ this._owner.assertActive();
2233
+ if (!this._valid)
2234
+ throw new Error(`Animation2D action "${this.id}" is no longer valid.`);
2235
+ }
2236
+ }
2237
+ function effectEvent(actionId, clipId, cue, lifecycle) {
2238
+ return { actionId, clipId, cue, lifecycle };
2239
+ }
2240
+ function effectActiveAt(effect, time, duration) {
2241
+ if (effect.fullSpan)
2242
+ return time >= 0 && time < duration;
2243
+ return time >= effect.start && time < effect.end;
2244
+ }
2245
+ function validateClip(clip) {
2246
+ if (clip.format !== 'haiyue-animation2d-clip@1') {
2247
+ throw new RangeError(`Unsupported Animation2D clip format "${String(clip.format)}".`);
2248
+ }
2249
+ if (!clip.id)
2250
+ throw new RangeError('Animation2D clip id must not be empty.');
2251
+ if (!Number.isFinite(clip.duration) || clip.duration < 0) {
2252
+ throw new RangeError(`Animation2D clip duration must be finite and non-negative; received ${clip.duration}.`);
2253
+ }
2254
+ for (let index = 0; index < clip.tracks.length; index++) {
2255
+ const track = clip.tracks[index];
2256
+ const first = track.times[0];
2257
+ const last = track.times[track.times.length - 1];
2258
+ if (first !== undefined && (first < 0 || last > clip.duration)) {
2259
+ throw new RangeError(`Animation2D track "${track.id}" keys must lie within its clip.`);
2260
+ }
2261
+ }
2262
+ }
2263
+ function validateEffect(cue, start, end, duration) {
2264
+ if (!cue.id)
2265
+ throw new RangeError('Animation2D effect cue id must not be empty.');
2266
+ if (!Number.isFinite(start) || !Number.isFinite(end)
2267
+ || start < 0 || end < start || end > duration) {
2268
+ throw new RangeError(`Animation2D effect "${cue.id}" interval must lie within its clip.`);
2269
+ }
2270
+ }
2271
+ function validateRepetitions(value) {
2272
+ if (value === Infinity)
2273
+ return value;
2274
+ if (!Number.isInteger(value) || value <= 0) {
2275
+ throw new RangeError(`Animation2D repetitions must be a positive integer or Infinity; received ${value}.`);
2276
+ }
2277
+ return value;
2278
+ }
2279
+ function finiteNumber(value, property) {
2280
+ if (!Number.isFinite(value))
2281
+ throw new RangeError(`${property} must be finite; received ${value}.`);
2282
+ return value;
2283
+ }
2284
+ function nonNegativeNumber(value, property) {
2285
+ const result = finiteNumber(value, property);
2286
+ if (result < 0)
2287
+ throw new RangeError(`${property} must be non-negative; received ${value}.`);
2288
+ return result;
2289
+ }
2290
+ function integerNumber(value, property) {
2291
+ if (!Number.isInteger(value))
2292
+ throw new RangeError(`${property} must be an integer; received ${value}.`);
2293
+ return value;
2294
+ }
2295
+ function isEven(value) {
2296
+ return Math.abs(value % 2) === 0;
2297
+ }
2298
+
2299
+ /**
2300
+ * Format-independent 2D pose mixer and shared state-machine mixer port.
2301
+ *
2302
+ * Action/track/layer accumulators are retained across frames. Once actions
2303
+ * and their masks/layers have been warmed, update/evaluate allocates no
2304
+ * action- or property-proportional temporary objects.
2305
+ */
2306
+ class Animation2DMixerRuntime {
2307
+ _clips = new Map();
2308
+ _actions = [];
2309
+ _actionsById = new Map();
2310
+ _accumulators = new Map();
2311
+ _accumulatorList = [];
2312
+ _stateMachineLayers = new Map();
2313
+ _destroyed = false;
2314
+ _time = 0;
2315
+ _timeScale = 1;
2316
+ _nextActionId = 1;
2317
+ _nextActionOrder = 0;
2318
+ _nextStateMachineLayer = 0;
2319
+ constructor(clips = []) {
2320
+ for (let index = 0; index < clips.length; index++)
2321
+ this.registerClip(clips[index]);
2322
+ }
2323
+ get time() { return this._time; }
2324
+ get timeScale() { return this._timeScale; }
2325
+ set timeScale(value) {
2326
+ this.assertActive();
2327
+ if (!Number.isFinite(value)) {
2328
+ throw new RangeError(`Animation2D mixer timeScale must be finite; received ${value}.`);
2329
+ }
2330
+ this._timeScale = value;
2331
+ }
2332
+ get actions() { return this._actions; }
2333
+ get liveBindingCount() { return this._accumulators.size; }
2334
+ registerClip(clip) {
2335
+ this.assertActive();
2336
+ const existing = this._clips.get(clip.id);
2337
+ if (existing && existing !== clip) {
2338
+ throw new RangeError(`Animation2D clip id "${clip.id}" is already registered.`);
2339
+ }
2340
+ this._clips.set(clip.id, clip);
2341
+ return this;
2342
+ }
2343
+ unregisterClip(clipId) {
2344
+ this.assertActive();
2345
+ return this._clips.delete(clipId);
2346
+ }
2347
+ createAction(source, options = {}) {
2348
+ this.assertActive();
2349
+ const clip = typeof source === 'string' ? this._clips.get(source) : source;
2350
+ if (!clip)
2351
+ throw new RangeError(`Animation2D clip "${String(source)}" is not registered.`);
2352
+ const stateOptions = isStateMachineOptions(options) ? options : null;
2353
+ const runtimeOptions = stateOptions ? null : options;
2354
+ const layer = stateOptions
2355
+ ? this._stateMachineLayer(stateOptions.layerId)
2356
+ : runtimeOptions.layer ?? 0;
2357
+ const id = runtimeOptions?.id
2358
+ ? runtimeOptions.id
2359
+ : `${clip.id}:${stateOptions?.stateId ?? 'action'}:${this._nextActionId++}`;
2360
+ if (!id)
2361
+ throw new RangeError('Animation2D action id must not be empty.');
2362
+ if (this._actionsById.has(id)) {
2363
+ throw new RangeError(`Animation2D action id "${id}" already exists.`);
2364
+ }
2365
+ const actionOptions = stateOptions
2366
+ ? {
2367
+ id,
2368
+ layer,
2369
+ loop: stateOptions.loop,
2370
+ blendMode: stateOptions.blendMode,
2371
+ ...(stateOptions.mask ? { mask: stateOptions.mask } : {}),
2372
+ }
2373
+ : { ...runtimeOptions, id, layer };
2374
+ const action = new Animation2DActionRuntime(this, clip, id, this._nextActionOrder++, actionOptions);
2375
+ for (let index = 0; index < action.runtimeTracks.length; index++) {
2376
+ this._retainBinding(action.runtimeTracks[index].track.binding, layer);
2377
+ }
2378
+ this._actions.push(action);
2379
+ this._actionsById.set(id, action);
2380
+ return action;
2381
+ }
2382
+ getAction(actionId) {
2383
+ this.assertActive();
2384
+ return this._actionsById.get(actionId) ?? null;
2385
+ }
2386
+ removeAction(action) {
2387
+ this.assertActive();
2388
+ const runtime = typeof action === 'string' ? this._actionsById.get(action) : action;
2389
+ if (!runtime || this._actionsById.get(runtime.id) !== runtime)
2390
+ return false;
2391
+ const index = this._actions.indexOf(runtime);
2392
+ if (index >= 0)
2393
+ this._actions.splice(index, 1);
2394
+ this._actionsById.delete(runtime.id);
2395
+ for (let trackIndex = 0; trackIndex < runtime.runtimeTracks.length; trackIndex++) {
2396
+ this._releaseBinding(runtime.runtimeTracks[trackIndex].track.binding);
2397
+ }
2398
+ runtime.invalidate();
2399
+ return true;
2400
+ }
2401
+ update(deltaSeconds, out) {
2402
+ this.assertActive();
2403
+ if (!Number.isFinite(deltaSeconds)) {
2404
+ throw new RangeError(`Animation2D mixer delta must be finite; received ${deltaSeconds}.`);
2405
+ }
2406
+ const mixerDelta = deltaSeconds * this._timeScale;
2407
+ this._time += mixerDelta;
2408
+ out.reset(this._time);
2409
+ this._advanceActions(mixerDelta, out);
2410
+ this._evaluateInto(out);
2411
+ return out.seal();
2412
+ }
2413
+ evaluate(out) {
2414
+ this.assertActive();
2415
+ out.reset(this._time);
2416
+ for (let index = 0; index < this._actions.length; index++) {
2417
+ this._actions[index].flushPendingEffects(out);
2418
+ }
2419
+ this._evaluateInto(out);
2420
+ return out.seal();
2421
+ }
2422
+ setMixerTime(timeSeconds, out) {
2423
+ this.assertActive();
2424
+ if (!Number.isFinite(timeSeconds)) {
2425
+ throw new RangeError(`Animation2D mixer time must be finite; received ${timeSeconds}.`);
2426
+ }
2427
+ const delta = timeSeconds - this._time;
2428
+ this._time = timeSeconds;
2429
+ out.reset(this._time);
2430
+ this._advanceActions(delta, out);
2431
+ this._evaluateInto(out);
2432
+ return out.seal();
2433
+ }
2434
+ play(action) {
2435
+ this._assertOwned(action);
2436
+ action.play();
2437
+ }
2438
+ stop(action) {
2439
+ this._assertOwned(action);
2440
+ action.stop();
2441
+ }
2442
+ fade(action, targetWeight, durationSeconds) {
2443
+ this._assertOwned(action);
2444
+ action.fadeTo(targetWeight, durationSeconds);
2445
+ }
2446
+ setWeight(action, weight) {
2447
+ this._assertOwned(action);
2448
+ action.weight = weight;
2449
+ }
2450
+ setTime(action, timeSeconds) {
2451
+ this._assertOwned(action);
2452
+ action.synchronizeTime(timeSeconds);
2453
+ }
2454
+ setTimeScale(action, timeScale) {
2455
+ this._assertOwned(action);
2456
+ action.timeScale = timeScale;
2457
+ }
2458
+ destroyAction(action) {
2459
+ this.removeAction(action);
2460
+ }
2461
+ destroy() {
2462
+ if (this._destroyed)
2463
+ return;
2464
+ for (let index = 0; index < this._actions.length; index++)
2465
+ this._actions[index].invalidate();
2466
+ this._actions.length = 0;
2467
+ this._actionsById.clear();
2468
+ this._accumulators.clear();
2469
+ this._accumulatorList.length = 0;
2470
+ this._clips.clear();
2471
+ this._stateMachineLayers.clear();
2472
+ this._destroyed = true;
2473
+ }
2474
+ assertActive() {
2475
+ if (this._destroyed)
2476
+ throw new Error('Animation2D mixer has been destroyed.');
2477
+ }
2478
+ _advanceActions(mixerDelta, out) {
2479
+ for (let index = 0; index < this._actions.length; index++) {
2480
+ this._actions[index].advance(mixerDelta, out);
2481
+ }
2482
+ }
2483
+ _evaluateInto(out) {
2484
+ for (let index = 0; index < this._accumulatorList.length; index++) {
2485
+ resetAccumulator(this._accumulatorList[index]);
2486
+ }
2487
+ for (let actionIndex = 0; actionIndex < this._actions.length; actionIndex++) {
2488
+ const action = this._actions[actionIndex];
2489
+ const weight = action.effectiveWeight;
2490
+ if (weight <= 0)
2491
+ continue;
2492
+ for (let trackIndex = 0; trackIndex < action.runtimeTracks.length; trackIndex++) {
2493
+ const runtimeTrack = action.runtimeTracks[trackIndex];
2494
+ const binding = runtimeTrack.track.binding;
2495
+ if (!action.acceptsBinding(binding))
2496
+ continue;
2497
+ const accumulator = this._accumulators.get(binding.id);
2498
+ if (!accumulator)
2499
+ continue;
2500
+ let layer = accumulator.layersById.get(action.layer);
2501
+ if (!layer) {
2502
+ layer = createLayerAccumulator(binding, action.layer);
2503
+ insertLayer(accumulator, layer);
2504
+ }
2505
+ accumulator.active = true;
2506
+ const sampled = runtimeTrack.sampler.sample(action.time);
2507
+ if (isNumericBinding(binding)) {
2508
+ accumulateNumeric(layer, binding, runtimeTrack, sampled, weight, action.blendMode);
2509
+ }
2510
+ else {
2511
+ accumulateDominant(layer, sampled, weight, action.order);
2512
+ }
2513
+ }
2514
+ }
2515
+ for (let index = 0; index < this._accumulatorList.length; index++) {
2516
+ const accumulator = this._accumulatorList[index];
2517
+ if (!accumulator.active)
2518
+ continue;
2519
+ if (isNumericBinding(accumulator.binding)) {
2520
+ finalizeNumeric(accumulator);
2521
+ out.writeNumeric(accumulator.binding, accumulator.result);
2522
+ }
2523
+ else {
2524
+ let hasValue = false;
2525
+ let value;
2526
+ for (let layerIndex = 0; layerIndex < accumulator.layers.length; layerIndex++) {
2527
+ const layer = accumulator.layers[layerIndex];
2528
+ if (!layer.dominantSet)
2529
+ continue;
2530
+ hasValue = true;
2531
+ value = layer.dominantValue;
2532
+ }
2533
+ if (hasValue)
2534
+ out.writeDiscrete(accumulator.binding, value);
2535
+ }
2536
+ }
2537
+ }
2538
+ _retainBinding(binding, layer) {
2539
+ const existing = this._accumulators.get(binding.id);
2540
+ if (existing) {
2541
+ if (existing.binding.strategy !== binding.strategy
2542
+ || existing.binding.valueSize !== binding.valueSize) {
2543
+ throw new RangeError(`Animation2D binding id "${binding.id}" has incompatible contracts.`);
2544
+ }
2545
+ existing.references++;
2546
+ if (!existing.layersById.has(layer))
2547
+ insertLayer(existing, createLayerAccumulator(binding, layer));
2548
+ return;
2549
+ }
2550
+ const size = isNumericBinding(binding) ? binding.valueSize : 0;
2551
+ const accumulator = {
2552
+ binding,
2553
+ result: new Float32Array(size),
2554
+ base: new Float32Array(size),
2555
+ layers: [],
2556
+ layersById: new Map(),
2557
+ references: 1,
2558
+ active: false,
2559
+ };
2560
+ writeDefaultBase(binding, accumulator.base);
2561
+ insertLayer(accumulator, createLayerAccumulator(binding, layer));
2562
+ this._accumulators.set(binding.id, accumulator);
2563
+ this._accumulatorList.push(accumulator);
2564
+ }
2565
+ _releaseBinding(binding) {
2566
+ const accumulator = this._accumulators.get(binding.id);
2567
+ if (!accumulator)
2568
+ return;
2569
+ accumulator.references--;
2570
+ if (accumulator.references > 0)
2571
+ return;
2572
+ this._accumulators.delete(binding.id);
2573
+ const index = this._accumulatorList.indexOf(accumulator);
2574
+ if (index >= 0)
2575
+ this._accumulatorList.splice(index, 1);
2576
+ }
2577
+ _stateMachineLayer(layerId) {
2578
+ const existing = this._stateMachineLayers.get(layerId);
2579
+ if (existing !== undefined)
2580
+ return existing;
2581
+ const layer = this._nextStateMachineLayer++;
2582
+ this._stateMachineLayers.set(layerId, layer);
2583
+ return layer;
2584
+ }
2585
+ _assertOwned(action) {
2586
+ this.assertActive();
2587
+ if (this._actionsById.get(action.id) !== action) {
2588
+ throw new RangeError('Animation2D action does not belong to this mixer.');
2589
+ }
2590
+ }
2591
+ }
2592
+ function resetAccumulator(accumulator) {
2593
+ accumulator.active = false;
2594
+ for (let index = 0; index < accumulator.layers.length; index++) {
2595
+ const layer = accumulator.layers[index];
2596
+ layer.overrideSum.fill(0);
2597
+ layer.additiveSum.fill(0);
2598
+ layer.overrideWeight = 0;
2599
+ layer.rotationAnchor = 0;
2600
+ layer.rotationAnchorSet = false;
2601
+ layer.dominantSet = false;
2602
+ layer.dominantValue = undefined;
2603
+ layer.dominantWeight = -1;
2604
+ layer.dominantOrder = Number.MAX_SAFE_INTEGER;
2605
+ }
2606
+ }
2607
+ function createLayerAccumulator(binding, layer) {
2608
+ const size = isNumericBinding(binding) ? binding.valueSize : 0;
2609
+ return {
2610
+ layer,
2611
+ overrideSum: new Float32Array(size),
2612
+ additiveSum: new Float32Array(size),
2613
+ overrideWeight: 0,
2614
+ rotationAnchor: 0,
2615
+ rotationAnchorSet: false,
2616
+ dominantSet: false,
2617
+ dominantValue: undefined,
2618
+ dominantWeight: -1,
2619
+ dominantOrder: Number.MAX_SAFE_INTEGER,
2620
+ };
2621
+ }
2622
+ function insertLayer(accumulator, layer) {
2623
+ let index = accumulator.layers.length;
2624
+ while (index > 0 && accumulator.layers[index - 1].layer > layer.layer)
2625
+ index--;
2626
+ accumulator.layers.splice(index, 0, layer);
2627
+ accumulator.layersById.set(layer.layer, layer);
2628
+ }
2629
+ function accumulateNumeric(layer, binding, runtimeTrack, sampled, weight, blendMode) {
2630
+ if (blendMode === 'additive') {
2631
+ if (binding.strategy === 'rotation') {
2632
+ layer.additiveSum[0] = layer.additiveSum[0]
2633
+ + wrapAngle(sampled[0] - runtimeTrack.reference[0]) * weight;
2634
+ return;
2635
+ }
2636
+ for (let index = 0; index < sampled.length; index++) {
2637
+ layer.additiveSum[index] = layer.additiveSum[index]
2638
+ + (sampled[index] - runtimeTrack.reference[index]) * weight;
2639
+ }
2640
+ return;
2641
+ }
2642
+ if (binding.strategy === 'rotation') {
2643
+ if (!layer.rotationAnchorSet) {
2644
+ layer.rotationAnchor = sampled[0];
2645
+ layer.rotationAnchorSet = true;
2646
+ }
2647
+ const unwrapped = layer.rotationAnchor + wrapAngle(sampled[0] - layer.rotationAnchor);
2648
+ layer.overrideSum[0] = layer.overrideSum[0] + unwrapped * weight;
2649
+ }
2650
+ else {
2651
+ for (let index = 0; index < sampled.length; index++) {
2652
+ layer.overrideSum[index] = layer.overrideSum[index] + sampled[index] * weight;
2653
+ }
2654
+ }
2655
+ layer.overrideWeight += weight;
2656
+ }
2657
+ function accumulateDominant(layer, value, weight, order) {
2658
+ if (weight < layer.dominantWeight
2659
+ || (weight === layer.dominantWeight && order >= layer.dominantOrder))
2660
+ return;
2661
+ layer.dominantSet = true;
2662
+ layer.dominantValue = value;
2663
+ layer.dominantWeight = weight;
2664
+ layer.dominantOrder = order;
2665
+ }
2666
+ function finalizeNumeric(accumulator) {
2667
+ const binding = accumulator.binding;
2668
+ const result = accumulator.result;
2669
+ result.set(accumulator.base);
2670
+ for (let layerIndex = 0; layerIndex < accumulator.layers.length; layerIndex++) {
2671
+ const layer = accumulator.layers[layerIndex];
2672
+ const weight = layer.overrideWeight;
2673
+ if (weight > 0) {
2674
+ if (binding.strategy === 'rotation') {
2675
+ const target = layer.overrideSum[0] / weight;
2676
+ const current = target + wrapAngle(result[0] - target);
2677
+ const alpha = Math.min(1, weight);
2678
+ result[0] = current + (target - current) * alpha;
2679
+ }
2680
+ else if (weight < 1) {
2681
+ const baseWeight = 1 - weight;
2682
+ for (let index = 0; index < result.length; index++) {
2683
+ result[index] = layer.overrideSum[index] + result[index] * baseWeight;
2684
+ }
2685
+ }
2686
+ else {
2687
+ const inverseWeight = 1 / weight;
2688
+ for (let index = 0; index < result.length; index++) {
2689
+ result[index] = layer.overrideSum[index] * inverseWeight;
2690
+ }
2691
+ }
2692
+ }
2693
+ for (let index = 0; index < result.length; index++) {
2694
+ result[index] = result[index] + layer.additiveSum[index];
2695
+ }
2696
+ if (binding.strategy === 'rotation')
2697
+ result[0] = wrapAngle(result[0]);
2698
+ }
2699
+ }
2700
+ function writeDefaultBase(binding, out) {
2701
+ const authored = binding.defaultValue;
2702
+ if (authored && typeof authored === 'object' && 'length' in authored) {
2703
+ const values = authored;
2704
+ for (let index = 0; index < out.length; index++)
2705
+ out[index] = values[index] ?? 0;
2706
+ return;
2707
+ }
2708
+ out.fill(0);
2709
+ if (binding.path === 'transform.scale'
2710
+ || binding.path === 'opacity'
2711
+ || binding.path === 'color')
2712
+ out.fill(1);
2713
+ }
2714
+ function isStateMachineOptions(options) {
2715
+ return 'layerId' in options && 'stateId' in options;
2716
+ }
2717
+
2718
+ class ReusablePoseChannel {
2719
+ binding;
2720
+ value;
2721
+ _numeric = new Float32Array(0);
2722
+ constructor(binding) {
2723
+ this.binding = binding;
2724
+ }
2725
+ writeNumeric(binding, source) {
2726
+ const size = numericSize(binding);
2727
+ this.binding = binding;
2728
+ if (this._numeric.length !== size)
2729
+ this._numeric = new Float32Array(size);
2730
+ for (let index = 0; index < size; index++)
2731
+ this._numeric[index] = source[index] ?? 0;
2732
+ this.value = this._numeric;
2733
+ }
2734
+ writeDiscrete(binding, value) {
2735
+ this.binding = binding;
2736
+ this.value = value;
2737
+ }
2738
+ }
2739
+ /**
2740
+ * Caller-owned frame-transient pose. Channel records, numeric storage, and
2741
+ * effect arrays are reused after warmup.
2742
+ */
2743
+ class Animation2DPoseBuffer {
2744
+ _channelPool = [];
2745
+ _channels = [];
2746
+ _effects = [];
2747
+ _sequence = 0;
2748
+ _mixerTime = 0;
2749
+ _sealed = true;
2750
+ get sequence() { return this._sequence; }
2751
+ get mixerTime() { return this._mixerTime; }
2752
+ get channels() { return this._channels; }
2753
+ get effects() { return this._effects; }
2754
+ reset(mixerTime) {
2755
+ if (!Number.isFinite(mixerTime)) {
2756
+ throw new RangeError(`Animation2D pose mixerTime must be finite; received ${mixerTime}.`);
2757
+ }
2758
+ this._mixerTime = mixerTime;
2759
+ this._channels.length = 0;
2760
+ this._effects.length = 0;
2761
+ this._sealed = false;
2762
+ }
2763
+ writeNumeric(binding, value) {
2764
+ const size = numericSize(binding);
2765
+ if (value.length < size) {
2766
+ throw new RangeError(`Animation2D pose channel "${binding.id}" requires ${size} values; received ${value.length}.`);
2767
+ }
2768
+ const channel = this._channelAt(this._channels.length, binding);
2769
+ channel.writeNumeric(binding, value);
2770
+ this._channels.push(channel);
2771
+ }
2772
+ writeDiscrete(binding, value) {
2773
+ const channel = this._channelAt(this._channels.length, binding);
2774
+ channel.writeDiscrete(binding, value);
2775
+ this._channels.push(channel);
2776
+ }
2777
+ emit(effect) {
2778
+ this._effects.push(effect);
2779
+ }
2780
+ seal() {
2781
+ if (!this._sealed) {
2782
+ this._sequence++;
2783
+ this._sealed = true;
2784
+ }
2785
+ return this;
2786
+ }
2787
+ _channelAt(index, binding) {
2788
+ let channel = this._channelPool[index];
2789
+ if (!channel) {
2790
+ channel = new ReusablePoseChannel(binding);
2791
+ this._channelPool.push(channel);
2792
+ }
2793
+ return channel;
2794
+ }
2795
+ }
2796
+ function numericSize(binding) {
2797
+ const size = binding.valueSize ?? 0;
2798
+ if (!Number.isInteger(size) || size <= 0) {
2799
+ throw new RangeError(`Animation2D numeric binding "${binding.id}" requires a positive valueSize.`);
2800
+ }
2801
+ return size;
2802
+ }
2803
+
2804
+ class Animation2DStateMachineRuntime {
2805
+ extension;
2806
+ _mixer;
2807
+ _adapter;
2808
+ _pose = new Animation2DPoseBuffer();
2809
+ _visual;
2810
+ _destroyed = false;
2811
+ controller;
2812
+ constructor(owner, animation, extension, parameterValues, runtimeExtensions, assetManager) {
2813
+ this.extension = extension;
2814
+ const clips = createHyaAnimation2DClips(animation, extension);
2815
+ this._mixer = new Animation2DMixerRuntime(clips);
2816
+ this._adapter = new Animation2DStateMachineMixerAdapter(this._mixer);
2817
+ let visual;
2818
+ try {
2819
+ this.controller = createAnimation2DStateMachineController(extension.stateMachine, this._adapter);
2820
+ applyParameterValues(this.controller, extension, parameterValues);
2821
+ this.controller.update(0);
2822
+ visual = new Animation2DStateMachineVisualRuntime(owner, animation, runtimeExtensions, assetManager);
2823
+ this._visual = visual;
2824
+ }
2825
+ catch (error) {
2826
+ visual?.destroy();
2827
+ this.controller?.destroy();
2828
+ this._adapter.destroy();
2829
+ this._mixer.destroy();
2830
+ throw error;
2831
+ }
2832
+ }
2833
+ get stats() { return this._visual.stats; }
2834
+ get diagnostics() { return this._visual.diagnostics; }
2835
+ get liveActionCount() { return this._adapter.liveActionCount; }
2836
+ get liveBindingCount() { return this._adapter.liveBindingCount; }
2837
+ get sideEffectOwnerCount() { return this._visual.sideEffectOwnerCount; }
2838
+ get layerSnapshots() {
2839
+ return this.controller.layerSnapshots;
2840
+ }
2841
+ update(deltaSeconds, playing, speed) {
2842
+ this._requireActive();
2843
+ const pose = runAnimationStateMachineControllerUpdateTransaction(this.controller, playing ? deltaSeconds * speed : 0, () => undefined, () => this._mixer.evaluate(this._pose));
2844
+ this._visual.applyPose(pose, playing, speed);
2845
+ }
2846
+ setPlaying(playing) {
2847
+ this._requireActive();
2848
+ this._visual.setPlaying(playing);
2849
+ }
2850
+ reset() {
2851
+ this._requireActive();
2852
+ this._visual.resetSideEffects();
2853
+ this.controller.reset();
2854
+ const pose = runAnimationStateMachineControllerUpdateTransaction(this.controller, 0, () => undefined, () => this._mixer.evaluate(this._pose));
2855
+ this._visual.applyPose(pose, false, 1);
2856
+ }
2857
+ destroy() {
2858
+ if (this._destroyed)
2859
+ return;
2860
+ this._destroyed = true;
2861
+ this.controller.destroy();
2862
+ this._adapter.destroy();
2863
+ this._mixer.destroy();
2864
+ this._visual.destroy();
2865
+ }
2866
+ _requireActive() {
2867
+ if (this._destroyed)
2868
+ throw new Error('Animation2D state-machine runtime has been destroyed.');
2869
+ }
2870
+ }
2871
+ function applyParameterValues(controller, extension, values) {
2872
+ for (const parameter of extension.stateMachine.parameters) {
2873
+ if (!values.has(parameter.name))
2874
+ continue;
2875
+ const value = values.get(parameter.name);
2876
+ if (parameter.type === 'float')
2877
+ controller.setFloat(parameter.name, value);
2878
+ else if (parameter.type === 'integer')
2879
+ controller.setInteger(parameter.name, value);
2880
+ else if (parameter.type === 'boolean')
2881
+ controller.setBoolean(parameter.name, value);
2882
+ else if (value)
2883
+ controller.setTrigger(parameter.name);
2884
+ }
2885
+ }
2886
+
2887
+ class Animation2DStateMachineSystem extends System {
2888
+ _assetManager;
2889
+ constructor(options = {}) {
2890
+ super({ all: [Animation2DStateMachineComponent] });
2891
+ this.name = 'Animation2DStateMachineSystem';
2892
+ if (options.priority !== undefined)
2893
+ this.priority = options.priority;
2894
+ this._assetManager = options.assetManager;
2895
+ }
2896
+ update(world, _time, delta) {
2897
+ if (this.disabled)
2898
+ return this;
2899
+ const entities = this.entitySet.get(world);
2900
+ if (!entities)
2901
+ return this;
2902
+ for (const entity of entities)
2903
+ this._updateEntity(entity, delta);
2904
+ return this;
2905
+ }
2906
+ destroy() {
2907
+ for (const world of this.usedBy) {
2908
+ const entities = this.entitySet.get(world);
2909
+ if (!entities)
2910
+ continue;
2911
+ for (const entity of entities) {
2912
+ entity.getComponent(Animation2DStateMachineComponent)?._disposeRuntime();
2913
+ }
2914
+ }
2915
+ return super.destroy();
2916
+ }
2917
+ _updateEntity(entity, delta) {
2918
+ const component = entity.getComponent(Animation2DStateMachineComponent);
2919
+ if (!component)
2920
+ return;
2921
+ if (component.disabled) {
2922
+ component._disposeRuntime();
2923
+ return;
2924
+ }
2925
+ component._runtime ??= new Animation2DStateMachineRuntime(entity, component.animation, component.stateMachineExtension, component._getParameterValues(), component.runtimeExtensions, this._assetManager);
2926
+ if ((!component.playing || component.speed === 0) && !component._needsUpdate)
2927
+ return;
2928
+ component._runtime.update(Math.max(0, delta) * 0.001, component.playing, component.speed);
2929
+ component._needsUpdate = false;
2930
+ }
2931
+ }
2932
+
2933
+ export { Animation2DStateMachineComponent, Animation2DStateMachineSystem, AnimationStateMachineChannelError, assertAudioStateMachineCompatible, audioStateMachineCompatibilityDiagnostic, createHyaAnimation2DClips, getHyaStateMachineExtension };
2934
+ //# sourceMappingURL=hya-state-machine.js.map