@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,2240 @@
1
+ import { Component } from '@haiyue/engine';
2
+ import { createAbortError } from '@haiyue/engine/experimental/async';
3
+ import { c as composeTrsMatrix, b as applyMorphWeights, u as updateSkinnedPrimitive } from './GltfAnimationRuntime-CRHsLycN.js';
4
+
5
+ /**
6
+ * Stable domain error for the Animation3D facade.
7
+ *
8
+ * Callers should branch on `code`; messages remain diagnostic and may gain
9
+ * additional context without changing the error contract.
10
+ */
11
+ class Animation3DError extends Error {
12
+ code;
13
+ details;
14
+ constructor(code, message, details = {}) {
15
+ super(message);
16
+ this.name = 'Animation3DError';
17
+ this.code = code;
18
+ this.details = Object.freeze({ ...details });
19
+ }
20
+ }
21
+
22
+ class ReusablePoseChannel {
23
+ binding;
24
+ value;
25
+ constructor(binding) {
26
+ this.binding = binding;
27
+ this.value = new Float32Array(binding.valueSize);
28
+ }
29
+ write(binding, source) {
30
+ this.binding = binding;
31
+ if (this.value.length !== binding.valueSize) {
32
+ this.value = new Float32Array(binding.valueSize);
33
+ }
34
+ for (let index = 0; index < binding.valueSize; index++) {
35
+ this.value[index] = source[index] ?? 0;
36
+ }
37
+ }
38
+ }
39
+ /**
40
+ * Reusable caller-owned mixer output.
41
+ *
42
+ * Channel records and value arrays are pooled after first use. seal() returns
43
+ * this object as the read-only pose view rather than allocating a snapshot.
44
+ */
45
+ class Animation3DPoseBuffer {
46
+ _channelPool = [];
47
+ _channels = [];
48
+ _events = [];
49
+ _sequence = 0;
50
+ _mixerTime = 0;
51
+ _sealed = true;
52
+ get sequence() { return this._sequence; }
53
+ get mixerTime() { return this._mixerTime; }
54
+ get channels() { return this._channels; }
55
+ get events() { return this._events; }
56
+ reset(mixerTime) {
57
+ if (!Number.isFinite(mixerTime)) {
58
+ throw new RangeError(`Animation3D pose mixerTime must be finite; received ${mixerTime}.`);
59
+ }
60
+ this._mixerTime = mixerTime;
61
+ this._channels.length = 0;
62
+ this._events.length = 0;
63
+ this._sealed = false;
64
+ }
65
+ write(binding, value) {
66
+ if (value.length < binding.valueSize) {
67
+ throw new RangeError(`Animation3D pose channel "${binding.id}" requires ${binding.valueSize} values; received ${value.length}.`);
68
+ }
69
+ const index = this._channels.length;
70
+ let channel = this._channelPool[index];
71
+ if (!channel) {
72
+ channel = new ReusablePoseChannel(binding);
73
+ this._channelPool.push(channel);
74
+ }
75
+ channel.write(binding, value);
76
+ this._channels.push(channel);
77
+ }
78
+ emit(event) {
79
+ this._events.push(event);
80
+ }
81
+ seal() {
82
+ if (!this._sealed) {
83
+ this._sequence++;
84
+ this._sealed = true;
85
+ }
86
+ return this;
87
+ }
88
+ }
89
+
90
+ const SLERP_LINEAR_THRESHOLD = 0.9995;
91
+ /**
92
+ * Stateful, allocation-free sampler for one immutable track.
93
+ *
94
+ * The sampler owns one reusable output buffer and a key cursor optimized for
95
+ * coherent playback. Callers may provide another destination when needed.
96
+ */
97
+ class Animation3DTrackSampler {
98
+ track;
99
+ valueSize;
100
+ output;
101
+ _cursor = 0;
102
+ constructor(track) {
103
+ validateAnimation3DTrack(track);
104
+ this.track = track;
105
+ this.valueSize = track.binding.valueSize;
106
+ this.output = new Float32Array(this.valueSize);
107
+ }
108
+ sample(timeSeconds, out = this.output) {
109
+ if (!Number.isFinite(timeSeconds)) {
110
+ throw new RangeError(`Animation3D track sample time must be finite; received ${timeSeconds}.`);
111
+ }
112
+ if (out.length < this.valueSize) {
113
+ throw new RangeError(`Animation3D track output requires ${this.valueSize} values; received ${out.length}.`);
114
+ }
115
+ const times = this.track.times;
116
+ const keyCount = times.length;
117
+ if (keyCount === 1 || timeSeconds <= times[0]) {
118
+ this._copyKeyValue(0, out);
119
+ this._cursor = 0;
120
+ return out;
121
+ }
122
+ const lastIndex = keyCount - 1;
123
+ if (timeSeconds >= times[lastIndex]) {
124
+ this._copyKeyValue(lastIndex, out);
125
+ this._cursor = Math.max(0, lastIndex - 1);
126
+ return out;
127
+ }
128
+ const keyIndex = this._findInterval(timeSeconds);
129
+ if (this.track.interpolation === 'step') {
130
+ this._copyKeyValue(keyIndex, out);
131
+ return out;
132
+ }
133
+ const startTime = times[keyIndex];
134
+ const endTime = times[keyIndex + 1];
135
+ const alpha = (timeSeconds - startTime) / (endTime - startTime);
136
+ if (this.track.interpolation === 'cubic-spline') {
137
+ this._sampleCubic(keyIndex, endTime - startTime, alpha, out);
138
+ }
139
+ else if (this.track.binding.valueType === 'quaternion') {
140
+ this._sampleQuaternionLinear(keyIndex, alpha, out);
141
+ }
142
+ else {
143
+ this._sampleLinear(keyIndex, alpha, out);
144
+ }
145
+ return out;
146
+ }
147
+ resetCursor() {
148
+ this._cursor = 0;
149
+ }
150
+ _findInterval(timeSeconds) {
151
+ const times = this.track.times;
152
+ let cursor = Math.min(this._cursor, times.length - 2);
153
+ if (timeSeconds >= times[cursor] && timeSeconds < times[cursor + 1]) {
154
+ return cursor;
155
+ }
156
+ if (timeSeconds >= times[cursor + 1]) {
157
+ while (cursor + 2 < times.length && timeSeconds >= times[cursor + 1])
158
+ cursor++;
159
+ this._cursor = cursor;
160
+ return cursor;
161
+ }
162
+ let low = 0;
163
+ let high = cursor;
164
+ while (low <= high) {
165
+ const middle = (low + high) >>> 1;
166
+ if (timeSeconds < times[middle]) {
167
+ high = middle - 1;
168
+ }
169
+ else if (timeSeconds >= times[middle + 1]) {
170
+ low = middle + 1;
171
+ }
172
+ else {
173
+ this._cursor = middle;
174
+ return middle;
175
+ }
176
+ }
177
+ this._cursor = Math.max(0, low);
178
+ return this._cursor;
179
+ }
180
+ _copyKeyValue(keyIndex, out) {
181
+ const size = this.valueSize;
182
+ const values = this.track.values;
183
+ const offset = this.track.interpolation === 'cubic-spline'
184
+ ? keyIndex * size * 3 + size
185
+ : keyIndex * size;
186
+ for (let component = 0; component < size; component++) {
187
+ out[component] = values[offset + component];
188
+ }
189
+ if (this.track.binding.valueType === 'quaternion')
190
+ normalizeQuaternion$1(out);
191
+ }
192
+ _sampleLinear(keyIndex, alpha, out) {
193
+ const size = this.valueSize;
194
+ const values = this.track.values;
195
+ const startOffset = keyIndex * size;
196
+ const endOffset = startOffset + size;
197
+ for (let component = 0; component < size; component++) {
198
+ const start = values[startOffset + component];
199
+ out[component] = start + (values[endOffset + component] - start) * alpha;
200
+ }
201
+ }
202
+ _sampleQuaternionLinear(keyIndex, alpha, out) {
203
+ const values = this.track.values;
204
+ const startOffset = keyIndex * 4;
205
+ const endOffset = startOffset + 4;
206
+ const ax = values[startOffset];
207
+ const ay = values[startOffset + 1];
208
+ const az = values[startOffset + 2];
209
+ const aw = values[startOffset + 3];
210
+ let bx = values[endOffset];
211
+ let by = values[endOffset + 1];
212
+ let bz = values[endOffset + 2];
213
+ let bw = values[endOffset + 3];
214
+ let dot = ax * bx + ay * by + az * bz + aw * bw;
215
+ if (dot < 0) {
216
+ dot = -dot;
217
+ bx = -bx;
218
+ by = -by;
219
+ bz = -bz;
220
+ bw = -bw;
221
+ }
222
+ dot = Math.min(1, Math.max(-1, dot));
223
+ if (dot > SLERP_LINEAR_THRESHOLD) {
224
+ out[0] = ax + (bx - ax) * alpha;
225
+ out[1] = ay + (by - ay) * alpha;
226
+ out[2] = az + (bz - az) * alpha;
227
+ out[3] = aw + (bw - aw) * alpha;
228
+ normalizeQuaternion$1(out);
229
+ return;
230
+ }
231
+ const theta = Math.acos(dot);
232
+ const sinTheta = Math.sin(theta);
233
+ const startWeight = Math.sin((1 - alpha) * theta) / sinTheta;
234
+ const endWeight = Math.sin(alpha * theta) / sinTheta;
235
+ out[0] = ax * startWeight + bx * endWeight;
236
+ out[1] = ay * startWeight + by * endWeight;
237
+ out[2] = az * startWeight + bz * endWeight;
238
+ out[3] = aw * startWeight + bw * endWeight;
239
+ normalizeQuaternion$1(out);
240
+ }
241
+ _sampleCubic(keyIndex, keyDuration, alpha, out) {
242
+ const size = this.valueSize;
243
+ const values = this.track.values;
244
+ const startGroup = keyIndex * size * 3;
245
+ const endGroup = startGroup + size * 3;
246
+ const startValue = startGroup + size;
247
+ const startOutTangent = startGroup + size * 2;
248
+ const endInTangent = endGroup;
249
+ const endValue = endGroup + size;
250
+ const alpha2 = alpha * alpha;
251
+ const alpha3 = alpha2 * alpha;
252
+ const h00 = 2 * alpha3 - 3 * alpha2 + 1;
253
+ const h10 = alpha3 - 2 * alpha2 + alpha;
254
+ const h01 = -2 * alpha3 + 3 * alpha2;
255
+ const h11 = alpha3 - alpha2;
256
+ for (let component = 0; component < size; component++) {
257
+ out[component] = h00 * values[startValue + component]
258
+ + h10 * keyDuration * values[startOutTangent + component]
259
+ + h01 * values[endValue + component]
260
+ + h11 * keyDuration * values[endInTangent + component];
261
+ }
262
+ if (this.track.binding.valueType === 'quaternion')
263
+ normalizeQuaternion$1(out);
264
+ }
265
+ }
266
+ /** @internal Shared by clip admission and direct sampler construction. */
267
+ function validateAnimation3DTrack(track, clipDuration = Infinity) {
268
+ const trackId = typeof track?.id === 'string' ? track.id : '';
269
+ const fail = (message) => {
270
+ throw new Animation3DError('invalid-track', message, { trackId });
271
+ };
272
+ if (!track || typeof track !== 'object') {
273
+ fail('Animation3D track must be an object.');
274
+ }
275
+ if (trackId.trim().length === 0) {
276
+ fail('Animation3D track id must not be empty.');
277
+ }
278
+ if (!track.binding || typeof track.binding !== 'object') {
279
+ fail(`Animation3D track "${trackId}" requires a binding.`);
280
+ }
281
+ if (typeof track.binding.id !== 'string' || track.binding.id.trim().length === 0) {
282
+ fail(`Animation3D track "${trackId}" binding id must not be empty.`);
283
+ }
284
+ validateBindingTarget(track, fail);
285
+ const size = track.binding.valueSize;
286
+ if (!Number.isInteger(size) || size <= 0) {
287
+ fail(`Animation3D binding valueSize must be a positive integer; received ${size}.`);
288
+ }
289
+ const fixedSize = fixedBindingValueSize(track.binding.valueType);
290
+ if (fixedSize !== null && Number.isNaN(fixedSize)) {
291
+ fail(`Animation3D track "${trackId}" has an unsupported binding value type.`);
292
+ }
293
+ if (fixedSize !== null && size !== fixedSize) {
294
+ fail(`Animation3D ${track.binding.valueType} binding requires ${fixedSize} values; received ${size}.`);
295
+ }
296
+ if (track.binding.valueType === 'weights' && !Number.isSafeInteger(size)) {
297
+ fail(`Animation3D weights binding valueSize must be a positive safe integer; received ${size}.`);
298
+ }
299
+ validateBindingPath(track, size, fail);
300
+ if (track.interpolation !== 'step'
301
+ && track.interpolation !== 'linear'
302
+ && track.interpolation !== 'cubic-spline') {
303
+ fail(`Animation3D track "${trackId}" has an unsupported interpolation.`);
304
+ }
305
+ if (!(track.times instanceof Float32Array)) {
306
+ fail(`Animation3D track "${trackId}" times must be a Float32Array.`);
307
+ }
308
+ if (!(track.values instanceof Float32Array)) {
309
+ fail(`Animation3D track "${trackId}" values must be a Float32Array.`);
310
+ }
311
+ if (track.times.length === 0) {
312
+ fail(`Animation3D track "${trackId}" must contain at least one key.`);
313
+ }
314
+ for (let index = 0; index < track.times.length; index++) {
315
+ const time = track.times[index];
316
+ if (!Number.isFinite(time) || time < 0 || time > clipDuration
317
+ || (index > 0 && time <= track.times[index - 1])) {
318
+ fail(`Animation3D track "${trackId}" key times must be finite, non-negative, `
319
+ + 'strictly increasing, and within the clip duration.');
320
+ }
321
+ }
322
+ const stride = track.interpolation === 'cubic-spline' ? size * 3 : size;
323
+ const expectedValues = track.times.length * stride;
324
+ if (track.values.length !== expectedValues) {
325
+ fail(`Animation3D track "${trackId}" requires ${expectedValues} values; received ${track.values.length}.`);
326
+ }
327
+ for (let index = 0; index < track.values.length; index++) {
328
+ if (!Number.isFinite(track.values[index])) {
329
+ fail(`Animation3D track "${trackId}" values must be finite.`);
330
+ }
331
+ }
332
+ if (track.binding.valueType === 'quaternion') {
333
+ const groupStride = track.interpolation === 'cubic-spline' ? size * 3 : size;
334
+ const valueOffset = track.interpolation === 'cubic-spline' ? size : 0;
335
+ for (let keyIndex = 0; keyIndex < track.times.length; keyIndex++) {
336
+ const offset = keyIndex * groupStride + valueOffset;
337
+ const length = Math.hypot(track.values[offset], track.values[offset + 1], track.values[offset + 2], track.values[offset + 3]);
338
+ if (length <= Number.EPSILON) {
339
+ fail(`Animation3D track "${trackId}" contains a zero-length quaternion key.`);
340
+ }
341
+ }
342
+ }
343
+ }
344
+ function validateBindingTarget(track, fail) {
345
+ const { target } = track.binding;
346
+ if (!target || typeof target !== 'object') {
347
+ fail(`Animation3D track "${track.id}" binding requires a target.`);
348
+ }
349
+ if (target.kind === 'node-id') {
350
+ if (typeof target.nodeId !== 'string' || target.nodeId.trim().length === 0) {
351
+ fail(`Animation3D track "${track.id}" node-id target must not be empty.`);
352
+ }
353
+ return;
354
+ }
355
+ if (target.kind === 'node-path') {
356
+ if (!Array.isArray(target.segments)
357
+ || target.segments.some(segment => typeof segment !== 'string')) {
358
+ fail(`Animation3D track "${track.id}" node-path target must contain string segments.`);
359
+ }
360
+ return;
361
+ }
362
+ if (target.kind === 'slot') {
363
+ if (typeof target.slot !== 'string' || target.slot.trim().length === 0) {
364
+ fail(`Animation3D track "${track.id}" slot target must not be empty.`);
365
+ }
366
+ return;
367
+ }
368
+ fail(`Animation3D track "${track.id}" has an unsupported binding target.`);
369
+ }
370
+ function validateBindingPath(track, size, fail) {
371
+ const { binding } = track;
372
+ if (binding.path === 'transform.translation') {
373
+ if (binding.valueType !== 'vec3' || size !== 3) {
374
+ fail('Animation3D translation tracks require a vec3 binding.');
375
+ }
376
+ return;
377
+ }
378
+ if (binding.path === 'transform.rotation') {
379
+ if (binding.valueType !== 'quaternion' || size !== 4) {
380
+ fail('Animation3D rotation tracks require a quaternion binding.');
381
+ }
382
+ return;
383
+ }
384
+ if (binding.path === 'transform.scale') {
385
+ if (binding.valueType !== 'vec3' || size !== 3) {
386
+ fail('Animation3D scale tracks require a vec3 binding.');
387
+ }
388
+ return;
389
+ }
390
+ if (binding.path === 'morph.weights') {
391
+ if (binding.valueType !== 'weights') {
392
+ fail('Animation3D morph tracks require a weights binding.');
393
+ }
394
+ return;
395
+ }
396
+ if (binding.path === 'property') {
397
+ if (String(binding.valueType) === 'weights'
398
+ || typeof binding.component !== 'string'
399
+ || binding.component.trim().length === 0
400
+ || typeof binding.property !== 'string'
401
+ || binding.property.trim().length === 0) {
402
+ fail('Animation3D property tracks require component, property, and a fixed value type.');
403
+ }
404
+ return;
405
+ }
406
+ fail(`Animation3D track "${track.id}" has an unsupported binding path.`);
407
+ }
408
+ function fixedBindingValueSize(valueType) {
409
+ if (valueType === 'scalar')
410
+ return 1;
411
+ if (valueType === 'vec2')
412
+ return 2;
413
+ if (valueType === 'vec3')
414
+ return 3;
415
+ if (valueType === 'vec4' || valueType === 'quaternion')
416
+ return 4;
417
+ if (valueType === 'weights')
418
+ return null;
419
+ return Number.NaN;
420
+ }
421
+ function normalizeQuaternion$1(value) {
422
+ const x = value[0];
423
+ const y = value[1];
424
+ const z = value[2];
425
+ const w = value[3];
426
+ const length = Math.hypot(x, y, z, w);
427
+ if (length <= Number.EPSILON) {
428
+ value[0] = 0;
429
+ value[1] = 0;
430
+ value[2] = 0;
431
+ value[3] = 1;
432
+ return;
433
+ }
434
+ const inverseLength = 1 / length;
435
+ value[0] = x * inverseLength;
436
+ value[1] = y * inverseLength;
437
+ value[2] = z * inverseLength;
438
+ value[3] = w * inverseLength;
439
+ }
440
+
441
+ /**
442
+ * Mutable mixer-owned playback state for one immutable clip.
443
+ */
444
+ class Animation3DActionRuntime {
445
+ id;
446
+ clip;
447
+ runtimeTracks;
448
+ _owner;
449
+ _runtimeEvents;
450
+ _valid = true;
451
+ _status = 'idle';
452
+ _enabled = true;
453
+ _paused = false;
454
+ _time = 0;
455
+ _rawTime = 0;
456
+ _timeScale;
457
+ _weight;
458
+ _loop;
459
+ _repetitions;
460
+ _clampWhenFinished;
461
+ _blendMode;
462
+ _mask;
463
+ _startTime = null;
464
+ _eventsStarted = false;
465
+ _fadeActive = false;
466
+ _fadeStart = 0;
467
+ _fadeEnd = 0;
468
+ _fadeFrom = 1;
469
+ _fadeTo = 1;
470
+ _fadeValue = 1;
471
+ _warpActive = false;
472
+ _warpStart = 0;
473
+ _warpEnd = 0;
474
+ _warpFrom = 1;
475
+ _warpTo = 1;
476
+ _warpValue = 1;
477
+ constructor(owner, clip, id, options = {}) {
478
+ validateClip(clip);
479
+ this._owner = owner;
480
+ this.clip = clip;
481
+ this.id = id;
482
+ this._loop = options.loop ?? 'once';
483
+ this._repetitions = validateRepetitions(options.repetitions ?? (this._loop === 'once' ? 1 : Infinity));
484
+ this._clampWhenFinished = options.clampWhenFinished ?? false;
485
+ this._timeScale = finiteNumber(options.timeScale ?? 1, 'Animation3DAction.timeScale');
486
+ this._weight = nonNegativeNumber(options.weight ?? 1, 'Animation3DAction.weight');
487
+ this._blendMode = options.blendMode ?? 'override';
488
+ this._mask = options.mask ?? null;
489
+ const tracks = [];
490
+ for (let index = 0; index < clip.tracks.length; index++) {
491
+ const track = clip.tracks[index];
492
+ const sampler = new Animation3DTrackSampler(track);
493
+ const reference = new Float32Array(track.binding.valueSize);
494
+ sampler.sample(0, reference);
495
+ tracks.push({ track, sampler, reference });
496
+ }
497
+ this.runtimeTracks = tracks;
498
+ const sortedEvents = [...clip.events].sort((a, b) => a.time - b.time);
499
+ const runtimeEvents = [];
500
+ for (let index = 0; index < sortedEvents.length; index++) {
501
+ const event = sortedEvents[index];
502
+ runtimeEvents.push({
503
+ event,
504
+ poseEvent: { actionId: id, clipId: clip.id, event },
505
+ });
506
+ }
507
+ this._runtimeEvents = runtimeEvents;
508
+ }
509
+ get status() { return this._status; }
510
+ get enabled() { return this._enabled; }
511
+ set enabled(value) {
512
+ this._assertValid();
513
+ this._enabled = Boolean(value);
514
+ }
515
+ get paused() { return this._paused; }
516
+ set paused(value) {
517
+ this._assertValid();
518
+ const next = Boolean(value);
519
+ if (this._paused === next)
520
+ return;
521
+ this._paused = next;
522
+ if (next && (this._status === 'running' || this._status === 'scheduled')) {
523
+ this._status = 'paused';
524
+ }
525
+ else if (!next && this._status === 'paused') {
526
+ this._status = this._startTime !== null && this._owner.time < this._startTime
527
+ ? 'scheduled'
528
+ : 'running';
529
+ }
530
+ }
531
+ get time() { return this._time; }
532
+ set time(value) {
533
+ this._assertValid();
534
+ const duration = this.clip.duration;
535
+ const next = Math.min(duration, Math.max(0, finiteNumber(value, 'Animation3DAction.time')));
536
+ this._rawTime = next;
537
+ this._time = next;
538
+ this._eventsStarted = false;
539
+ this._resetSamplerCursors();
540
+ }
541
+ get timeScale() { return this._timeScale; }
542
+ set timeScale(value) {
543
+ this._assertValid();
544
+ this._timeScale = finiteNumber(value, 'Animation3DAction.timeScale');
545
+ }
546
+ get weight() { return this._weight; }
547
+ set weight(value) {
548
+ this._assertValid();
549
+ this._weight = nonNegativeNumber(value, 'Animation3DAction.weight');
550
+ }
551
+ get loop() { return this._loop; }
552
+ set loop(value) {
553
+ this._assertValid();
554
+ if (value !== 'once' && value !== 'repeat' && value !== 'ping-pong') {
555
+ throw new RangeError(`Unsupported Animation3D loop mode "${String(value)}".`);
556
+ }
557
+ this._loop = value;
558
+ this._setSampleTime(0);
559
+ }
560
+ get repetitions() { return this._repetitions; }
561
+ set repetitions(value) {
562
+ this._assertValid();
563
+ this._repetitions = validateRepetitions(value);
564
+ }
565
+ get clampWhenFinished() { return this._clampWhenFinished; }
566
+ set clampWhenFinished(value) {
567
+ this._assertValid();
568
+ this._clampWhenFinished = Boolean(value);
569
+ }
570
+ get blendMode() { return this._blendMode; }
571
+ set blendMode(value) {
572
+ this._assertValid();
573
+ if (value !== 'override' && value !== 'additive') {
574
+ throw new RangeError(`Unsupported Animation3D blend mode "${String(value)}".`);
575
+ }
576
+ this._blendMode = value;
577
+ }
578
+ get mask() { return this._mask; }
579
+ set mask(value) {
580
+ this._assertValid();
581
+ this._mask = value;
582
+ }
583
+ get effectiveTimeScale() {
584
+ if (!this._valid || this._paused || this._status !== 'running')
585
+ return 0;
586
+ return this._timeScale * this._warpFactor(this._owner.time);
587
+ }
588
+ get effectiveWeight() {
589
+ if (!this._valid || !this._enabled || this._status === 'idle'
590
+ || this._status === 'scheduled' || this._status === 'stopped'
591
+ || (this._status === 'finished' && !this._clampWhenFinished))
592
+ return 0;
593
+ return this._weight * this._fadeFactor(this._owner.time);
594
+ }
595
+ play() {
596
+ this._assertValid();
597
+ this._enabled = true;
598
+ this._paused = false;
599
+ this._status = this._startTime !== null && this._owner.time < this._startTime
600
+ ? 'scheduled'
601
+ : 'running';
602
+ return this;
603
+ }
604
+ stop() {
605
+ this._assertValid();
606
+ this._enabled = false;
607
+ this._paused = false;
608
+ this._status = 'stopped';
609
+ this._startTime = null;
610
+ this._fadeActive = false;
611
+ this._warpActive = false;
612
+ return this;
613
+ }
614
+ reset() {
615
+ this._assertValid();
616
+ this._enabled = true;
617
+ this._paused = false;
618
+ this._status = 'idle';
619
+ this._time = 0;
620
+ this._rawTime = 0;
621
+ this._startTime = null;
622
+ this._eventsStarted = false;
623
+ this._fadeActive = false;
624
+ this._fadeValue = 1;
625
+ this._warpActive = false;
626
+ this._warpValue = 1;
627
+ this._resetSamplerCursors();
628
+ return this;
629
+ }
630
+ startAt(mixerTimeSeconds) {
631
+ this._assertValid();
632
+ this._startTime = finiteNumber(mixerTimeSeconds, 'Animation3DAction.startAt');
633
+ if (this._status === 'running' || this._status === 'scheduled') {
634
+ this._status = this._owner.time < this._startTime ? 'scheduled' : 'running';
635
+ }
636
+ return this;
637
+ }
638
+ fadeIn(durationSeconds) {
639
+ this._assertValid();
640
+ const duration = nonNegativeNumber(durationSeconds, 'Animation3DAction.fadeIn');
641
+ this._fadeValue = 0;
642
+ this._scheduleFade(0, 1, duration);
643
+ return this;
644
+ }
645
+ fadeOut(durationSeconds) {
646
+ this._assertValid();
647
+ const duration = nonNegativeNumber(durationSeconds, 'Animation3DAction.fadeOut');
648
+ this._scheduleFade(this._fadeFactor(this._owner.time), 0, duration);
649
+ return this;
650
+ }
651
+ crossFadeFrom(source, durationSeconds, warp = false) {
652
+ this._assertValid();
653
+ const sourceRuntime = this._requireSibling(source);
654
+ const duration = nonNegativeNumber(durationSeconds, 'Animation3DAction.crossFadeFrom');
655
+ sourceRuntime.play().fadeOut(duration);
656
+ this.play().fadeIn(duration);
657
+ if (warp && sourceRuntime.clip.duration > 0 && this.clip.duration > 0) {
658
+ sourceRuntime._scheduleWarp(1, sourceRuntime.clip.duration / this.clip.duration, duration);
659
+ this._scheduleWarp(this.clip.duration / sourceRuntime.clip.duration, 1, duration);
660
+ }
661
+ return this;
662
+ }
663
+ crossFadeTo(destination, durationSeconds, warp = false) {
664
+ this._assertValid();
665
+ const destinationRuntime = this._requireSibling(destination);
666
+ destinationRuntime.crossFadeFrom(this, durationSeconds, warp);
667
+ return this;
668
+ }
669
+ stopFading() {
670
+ this._assertValid();
671
+ if (this._fadeActive)
672
+ this._fadeValue = this._fadeFactor(this._owner.time);
673
+ this._fadeActive = false;
674
+ return this;
675
+ }
676
+ acceptsBinding(binding) {
677
+ const include = this._mask?.include;
678
+ if (include && include.length > 0) {
679
+ let found = false;
680
+ for (let index = 0; index < include.length; index++) {
681
+ if (include[index] === binding.id) {
682
+ found = true;
683
+ break;
684
+ }
685
+ }
686
+ if (!found)
687
+ return false;
688
+ }
689
+ const exclude = this._mask?.exclude;
690
+ if (exclude) {
691
+ for (let index = 0; index < exclude.length; index++) {
692
+ if (exclude[index] === binding.id)
693
+ return false;
694
+ }
695
+ }
696
+ return true;
697
+ }
698
+ canContribute() {
699
+ return this.effectiveWeight > 0;
700
+ }
701
+ /** @internal Allocated once by an integration handle and reused every frame. */
702
+ createSynchronizedState() {
703
+ const state = {};
704
+ this.captureSynchronizedState(state);
705
+ return state;
706
+ }
707
+ /** @internal Captures every field changed by controller synchronization. */
708
+ captureSynchronizedState(out) {
709
+ this._assertValid();
710
+ out.status = this._status;
711
+ out.enabled = this._enabled;
712
+ out.paused = this._paused;
713
+ out.time = this._time;
714
+ out.rawTime = this._rawTime;
715
+ out.timeScale = this._timeScale;
716
+ out.weight = this._weight;
717
+ out.loop = this._loop;
718
+ out.repetitions = this._repetitions;
719
+ out.clampWhenFinished = this._clampWhenFinished;
720
+ out.blendMode = this._blendMode;
721
+ out.mask = this._mask;
722
+ out.startTime = this._startTime;
723
+ out.eventsStarted = this._eventsStarted;
724
+ out.fadeActive = this._fadeActive;
725
+ out.fadeStart = this._fadeStart;
726
+ out.fadeEnd = this._fadeEnd;
727
+ out.fadeFrom = this._fadeFrom;
728
+ out.fadeTo = this._fadeTo;
729
+ out.fadeValue = this._fadeValue;
730
+ out.warpActive = this._warpActive;
731
+ out.warpStart = this._warpStart;
732
+ out.warpEnd = this._warpEnd;
733
+ out.warpFrom = this._warpFrom;
734
+ out.warpTo = this._warpTo;
735
+ out.warpValue = this._warpValue;
736
+ }
737
+ /** @internal Restores event cursors as well as the sampled playhead. */
738
+ restoreSynchronizedState(state) {
739
+ this._assertValid();
740
+ this._status = state.status;
741
+ this._enabled = state.enabled;
742
+ this._paused = state.paused;
743
+ this._time = state.time;
744
+ this._rawTime = state.rawTime;
745
+ this._timeScale = state.timeScale;
746
+ this._weight = state.weight;
747
+ this._loop = state.loop;
748
+ this._repetitions = state.repetitions;
749
+ this._clampWhenFinished = state.clampWhenFinished;
750
+ this._blendMode = state.blendMode;
751
+ this._mask = state.mask;
752
+ this._startTime = state.startTime;
753
+ this._eventsStarted = state.eventsStarted;
754
+ this._fadeActive = state.fadeActive;
755
+ this._fadeStart = state.fadeStart;
756
+ this._fadeEnd = state.fadeEnd;
757
+ this._fadeFrom = state.fadeFrom;
758
+ this._fadeTo = state.fadeTo;
759
+ this._fadeValue = state.fadeValue;
760
+ this._warpActive = state.warpActive;
761
+ this._warpStart = state.warpStart;
762
+ this._warpEnd = state.warpEnd;
763
+ this._warpFrom = state.warpFrom;
764
+ this._warpTo = state.warpTo;
765
+ this._warpValue = state.warpValue;
766
+ this._resetSamplerCursors();
767
+ }
768
+ /**
769
+ * Sets an unwrapped controller-owned playhead without emitting events.
770
+ * Integration adapters use this for initial destination offsets before play.
771
+ */
772
+ seekSynchronizedTime(timeSeconds) {
773
+ this._assertValid();
774
+ this._rawTime = this._clampSynchronizedTime(timeSeconds);
775
+ this._setSampleTime(0);
776
+ this._eventsStarted = false;
777
+ this._resetSamplerCursors();
778
+ }
779
+ /**
780
+ * Moves to an absolute controller-owned playhead and emits every crossed
781
+ * clip event exactly once. This does not advance the mixer clock.
782
+ */
783
+ synchronizeTime(timeSeconds, out) {
784
+ this._assertValid();
785
+ const after = this._clampSynchronizedTime(timeSeconds);
786
+ const before = this._rawTime;
787
+ const direction = after === before ? this._timeScale : after - before;
788
+ if (!this._eventsStarted) {
789
+ this._emitStartingEvents(direction, out);
790
+ this._eventsStarted = true;
791
+ }
792
+ this._emitCrossedEvents(before, after, out);
793
+ this._rawTime = after;
794
+ this._setSampleTime(0);
795
+ }
796
+ advance(mixerDelta, previousMixerTime, currentMixerTime, out) {
797
+ if (!this._valid)
798
+ return;
799
+ this._finishTransitions(currentMixerTime);
800
+ if (!this._enabled || this._paused)
801
+ return;
802
+ let activeMixerDelta = mixerDelta;
803
+ if (this._status === 'scheduled') {
804
+ const startTime = this._startTime;
805
+ if (startTime === null) {
806
+ this._status = 'running';
807
+ }
808
+ else if (mixerDelta >= 0) {
809
+ if (currentMixerTime < startTime)
810
+ return;
811
+ activeMixerDelta = currentMixerTime - Math.max(previousMixerTime, startTime);
812
+ this._status = 'running';
813
+ this._startTime = null;
814
+ }
815
+ else {
816
+ if (currentMixerTime > startTime)
817
+ return;
818
+ activeMixerDelta = currentMixerTime - Math.min(previousMixerTime, startTime);
819
+ this._status = 'running';
820
+ this._startTime = null;
821
+ }
822
+ }
823
+ if (this._status !== 'running')
824
+ return;
825
+ const actionDelta = activeMixerDelta * this._timeScale * this._warpFactor(currentMixerTime);
826
+ if (actionDelta === 0 || this.clip.duration === 0)
827
+ return;
828
+ const before = this._rawTime;
829
+ let after = before + actionDelta;
830
+ let terminalDirection = 0;
831
+ const totalDuration = this._loop === 'once'
832
+ ? this.clip.duration
833
+ : this._repetitions === Infinity
834
+ ? Infinity
835
+ : this.clip.duration * this._repetitions;
836
+ if (totalDuration !== Infinity) {
837
+ if (after >= totalDuration && actionDelta > 0) {
838
+ after = totalDuration;
839
+ terminalDirection = 1;
840
+ }
841
+ else if (after <= 0 && actionDelta < 0) {
842
+ after = 0;
843
+ terminalDirection = -1;
844
+ }
845
+ }
846
+ if (!this._eventsStarted) {
847
+ this._emitStartingEvents(actionDelta, out);
848
+ this._eventsStarted = true;
849
+ }
850
+ this._emitCrossedEvents(before, after, out);
851
+ this._rawTime = after;
852
+ this._setSampleTime(terminalDirection);
853
+ if (terminalDirection !== 0) {
854
+ this._status = 'finished';
855
+ if (!this._clampWhenFinished)
856
+ this._enabled = false;
857
+ }
858
+ }
859
+ invalidate() {
860
+ if (!this._valid)
861
+ return;
862
+ this._valid = false;
863
+ this._enabled = false;
864
+ this._paused = false;
865
+ this._status = 'stopped';
866
+ this._startTime = null;
867
+ this._fadeActive = false;
868
+ this._warpActive = false;
869
+ }
870
+ _clampSynchronizedTime(timeSeconds) {
871
+ const next = finiteNumber(timeSeconds, 'Animation3DAction synchronized time');
872
+ if (this._loop === 'once') {
873
+ return Math.min(this.clip.duration, Math.max(0, next));
874
+ }
875
+ if (this._repetitions === Infinity)
876
+ return next;
877
+ return Math.min(this.clip.duration * this._repetitions, Math.max(0, next));
878
+ }
879
+ _setSampleTime(terminalDirection) {
880
+ const duration = this.clip.duration;
881
+ if (duration === 0) {
882
+ this._time = 0;
883
+ return;
884
+ }
885
+ if (this._loop === 'once') {
886
+ this._time = Math.min(duration, Math.max(0, this._rawTime));
887
+ return;
888
+ }
889
+ if (terminalDirection > 0 && this._loop === 'repeat') {
890
+ this._time = duration;
891
+ return;
892
+ }
893
+ const traversal = Math.floor(this._rawTime / duration);
894
+ let phase = this._rawTime - traversal * duration;
895
+ if (phase < 0)
896
+ phase += duration;
897
+ if (this._loop === 'repeat') {
898
+ this._time = phase;
899
+ return;
900
+ }
901
+ this._time = isEven(traversal) ? phase : duration - phase;
902
+ }
903
+ _emitCrossedEvents(before, after, out) {
904
+ if (before === after || this._runtimeEvents.length === 0)
905
+ return;
906
+ if (this._loop === 'once') {
907
+ this._emitOnceEvents(before, after, out);
908
+ }
909
+ else if (this._loop === 'repeat') {
910
+ this._emitRepeatEvents(before, after, out);
911
+ }
912
+ else {
913
+ this._emitPingPongEvents(before, after, out);
914
+ }
915
+ }
916
+ _emitStartingEvents(direction, out) {
917
+ if (direction >= 0) {
918
+ for (let index = 0; index < this._runtimeEvents.length; index++) {
919
+ const runtimeEvent = this._runtimeEvents[index];
920
+ if (runtimeEvent.event.time === this._time)
921
+ out.emit(runtimeEvent.poseEvent);
922
+ }
923
+ }
924
+ else {
925
+ for (let index = this._runtimeEvents.length - 1; index >= 0; index--) {
926
+ const runtimeEvent = this._runtimeEvents[index];
927
+ if (runtimeEvent.event.time === this._time)
928
+ out.emit(runtimeEvent.poseEvent);
929
+ }
930
+ }
931
+ }
932
+ _emitOnceEvents(before, after, out) {
933
+ if (after > before) {
934
+ for (let index = 0; index < this._runtimeEvents.length; index++) {
935
+ const runtimeEvent = this._runtimeEvents[index];
936
+ if (runtimeEvent.event.time > before && runtimeEvent.event.time <= after) {
937
+ out.emit(runtimeEvent.poseEvent);
938
+ }
939
+ }
940
+ }
941
+ else {
942
+ for (let index = this._runtimeEvents.length - 1; index >= 0; index--) {
943
+ const runtimeEvent = this._runtimeEvents[index];
944
+ if (runtimeEvent.event.time < before && runtimeEvent.event.time >= after) {
945
+ out.emit(runtimeEvent.poseEvent);
946
+ }
947
+ }
948
+ }
949
+ }
950
+ _emitRepeatEvents(before, after, out) {
951
+ const duration = this.clip.duration;
952
+ const minimum = Math.min(before, after);
953
+ const maximum = Math.max(before, after);
954
+ const firstCycle = Math.floor(minimum / duration) - 1;
955
+ const lastCycle = Math.floor(maximum / duration) + 1;
956
+ if (after > before) {
957
+ for (let cycle = firstCycle; cycle <= lastCycle; cycle++) {
958
+ const cycleStart = cycle * duration;
959
+ for (let index = 0; index < this._runtimeEvents.length; index++) {
960
+ const runtimeEvent = this._runtimeEvents[index];
961
+ const occurrence = cycleStart + runtimeEvent.event.time;
962
+ if (occurrence > before && occurrence <= after)
963
+ out.emit(runtimeEvent.poseEvent);
964
+ }
965
+ }
966
+ }
967
+ else {
968
+ for (let cycle = lastCycle; cycle >= firstCycle; cycle--) {
969
+ const cycleStart = cycle * duration;
970
+ for (let index = this._runtimeEvents.length - 1; index >= 0; index--) {
971
+ const runtimeEvent = this._runtimeEvents[index];
972
+ const occurrence = cycleStart + runtimeEvent.event.time;
973
+ if (occurrence < before && occurrence >= after)
974
+ out.emit(runtimeEvent.poseEvent);
975
+ }
976
+ }
977
+ }
978
+ }
979
+ _emitPingPongEvents(before, after, out) {
980
+ const duration = this.clip.duration;
981
+ const minimum = Math.min(before, after);
982
+ const maximum = Math.max(before, after);
983
+ const firstTraversal = Math.floor(minimum / duration) - 1;
984
+ const lastTraversal = Math.floor(maximum / duration) + 1;
985
+ if (after > before) {
986
+ for (let traversal = firstTraversal; traversal <= lastTraversal; traversal++) {
987
+ if (isEven(traversal)) {
988
+ this._emitPingPongTraversal(traversal, before, after, true, true, out);
989
+ }
990
+ else {
991
+ this._emitPingPongTraversal(traversal, before, after, false, true, out);
992
+ }
993
+ }
994
+ }
995
+ else {
996
+ for (let traversal = lastTraversal; traversal >= firstTraversal; traversal--) {
997
+ if (isEven(traversal)) {
998
+ this._emitPingPongTraversal(traversal, before, after, false, false, out);
999
+ }
1000
+ else {
1001
+ this._emitPingPongTraversal(traversal, before, after, true, false, out);
1002
+ }
1003
+ }
1004
+ }
1005
+ }
1006
+ _emitPingPongTraversal(traversal, before, after, ascendingEvents, forwardRaw, out) {
1007
+ const duration = this.clip.duration;
1008
+ const even = isEven(traversal);
1009
+ let index = ascendingEvents ? 0 : this._runtimeEvents.length - 1;
1010
+ const end = ascendingEvents ? this._runtimeEvents.length : -1;
1011
+ const step = ascendingEvents ? 1 : -1;
1012
+ for (; index !== end; index += step) {
1013
+ const runtimeEvent = this._runtimeEvents[index];
1014
+ const eventTime = runtimeEvent.event.time;
1015
+ if ((even && eventTime === 0) || (!even && eventTime === duration))
1016
+ continue;
1017
+ const occurrence = traversal * duration + (even ? eventTime : duration - eventTime);
1018
+ const crossed = forwardRaw
1019
+ ? occurrence > before && occurrence <= after
1020
+ : occurrence < before && occurrence >= after;
1021
+ if (crossed)
1022
+ out.emit(runtimeEvent.poseEvent);
1023
+ }
1024
+ }
1025
+ _scheduleFade(from, to, duration) {
1026
+ this._fadeFrom = from;
1027
+ this._fadeTo = to;
1028
+ this._fadeStart = this._owner.time;
1029
+ this._fadeEnd = this._fadeStart + duration;
1030
+ if (duration === 0) {
1031
+ this._fadeValue = to;
1032
+ this._fadeActive = false;
1033
+ }
1034
+ else {
1035
+ this._fadeValue = from;
1036
+ this._fadeActive = true;
1037
+ }
1038
+ }
1039
+ _scheduleWarp(from, to, duration) {
1040
+ this._warpFrom = from;
1041
+ this._warpTo = to;
1042
+ this._warpStart = this._owner.time;
1043
+ this._warpEnd = this._warpStart + duration;
1044
+ if (duration === 0) {
1045
+ this._warpValue = to;
1046
+ this._warpActive = false;
1047
+ }
1048
+ else {
1049
+ this._warpValue = from;
1050
+ this._warpActive = true;
1051
+ }
1052
+ }
1053
+ _fadeFactor(time) {
1054
+ if (!this._fadeActive)
1055
+ return this._fadeValue;
1056
+ if (time <= this._fadeStart)
1057
+ return this._fadeFrom;
1058
+ if (time >= this._fadeEnd)
1059
+ return this._fadeTo;
1060
+ const alpha = (time - this._fadeStart) / (this._fadeEnd - this._fadeStart);
1061
+ return this._fadeFrom + (this._fadeTo - this._fadeFrom) * alpha;
1062
+ }
1063
+ _warpFactor(time) {
1064
+ if (!this._warpActive)
1065
+ return this._warpValue;
1066
+ if (time <= this._warpStart)
1067
+ return this._warpFrom;
1068
+ if (time >= this._warpEnd)
1069
+ return this._warpTo;
1070
+ const alpha = (time - this._warpStart) / (this._warpEnd - this._warpStart);
1071
+ return this._warpFrom + (this._warpTo - this._warpFrom) * alpha;
1072
+ }
1073
+ _finishTransitions(time) {
1074
+ if (this._fadeActive && time >= this._fadeEnd) {
1075
+ this._fadeValue = this._fadeTo;
1076
+ this._fadeActive = false;
1077
+ }
1078
+ if (this._warpActive && time >= this._warpEnd) {
1079
+ this._warpValue = this._warpTo;
1080
+ this._warpActive = false;
1081
+ }
1082
+ }
1083
+ _requireSibling(action) {
1084
+ if (!(action instanceof Animation3DActionRuntime) || action._owner !== this._owner) {
1085
+ throw new RangeError('Animation3D cross-fade actions must belong to the same mixer.');
1086
+ }
1087
+ action._assertValid();
1088
+ return action;
1089
+ }
1090
+ _resetSamplerCursors() {
1091
+ for (let index = 0; index < this.runtimeTracks.length; index++) {
1092
+ this.runtimeTracks[index].sampler.resetCursor();
1093
+ }
1094
+ }
1095
+ _assertValid() {
1096
+ this._owner.assertActive();
1097
+ if (!this._valid) {
1098
+ throw new Animation3DError('invalid-action', `Animation3D action "${this.id}" is no longer valid.`, { actionId: this.id });
1099
+ }
1100
+ }
1101
+ }
1102
+ function validateClip(clip) {
1103
+ const clipId = typeof clip?.id === 'string' ? clip.id : '';
1104
+ const fail = (message) => {
1105
+ throw new Animation3DError('invalid-clip', message, { clipId });
1106
+ };
1107
+ if (!clip || typeof clip !== 'object') {
1108
+ fail('Animation3D clip must be an object.');
1109
+ }
1110
+ if (clip.format !== 'haiyue-animation3d-clip@1') {
1111
+ fail(`Unsupported Animation3D clip format "${String(clip.format)}".`);
1112
+ }
1113
+ if (clipId.trim().length === 0) {
1114
+ fail('Animation3D clip id must not be empty.');
1115
+ }
1116
+ if (typeof clip.name !== 'string') {
1117
+ fail(`Animation3D clip "${clipId}" name must be a string.`);
1118
+ }
1119
+ if (!Number.isFinite(clip.duration) || clip.duration < 0) {
1120
+ fail(`Animation3D clip duration must be finite and non-negative; received ${clip.duration}.`);
1121
+ }
1122
+ if (!Array.isArray(clip.tracks)) {
1123
+ fail(`Animation3D clip "${clipId}" tracks must be an array.`);
1124
+ }
1125
+ if (!Array.isArray(clip.events)) {
1126
+ fail(`Animation3D clip "${clipId}" events must be an array.`);
1127
+ }
1128
+ const trackIds = new Set();
1129
+ for (let index = 0; index < clip.tracks.length; index++) {
1130
+ const track = clip.tracks[index];
1131
+ validateAnimation3DTrack(track, clip.duration);
1132
+ if (trackIds.has(track.id)) {
1133
+ fail(`Animation3D clip "${clipId}" contains duplicate track id "${track.id}".`);
1134
+ }
1135
+ trackIds.add(track.id);
1136
+ }
1137
+ const eventIds = new Set();
1138
+ for (let index = 0; index < clip.events.length; index++) {
1139
+ const event = clip.events[index];
1140
+ if (!event || typeof event.id !== 'string' || event.id.trim().length === 0) {
1141
+ fail(`Animation3D clip "${clipId}" event ids must not be empty.`);
1142
+ }
1143
+ if (eventIds.has(event.id)) {
1144
+ fail(`Animation3D clip "${clipId}" contains duplicate event id "${event.id}".`);
1145
+ }
1146
+ eventIds.add(event.id);
1147
+ if (typeof event.name !== 'string') {
1148
+ fail(`Animation3D event "${event.id}" name must be a string.`);
1149
+ }
1150
+ if (!Number.isFinite(event.time) || event.time < 0 || event.time > clip.duration) {
1151
+ fail(`Animation3D event "${event.id}" must lie within its clip duration.`);
1152
+ }
1153
+ }
1154
+ }
1155
+ function validateRepetitions(value) {
1156
+ if (value === Infinity)
1157
+ return value;
1158
+ if (!Number.isInteger(value) || value <= 0) {
1159
+ throw new RangeError(`Animation3DAction.repetitions must be a positive integer or Infinity; received ${value}.`);
1160
+ }
1161
+ return value;
1162
+ }
1163
+ function finiteNumber(value, property) {
1164
+ if (!Number.isFinite(value))
1165
+ throw new RangeError(`${property} must be finite; received ${value}.`);
1166
+ return value;
1167
+ }
1168
+ function nonNegativeNumber(value, property) {
1169
+ const result = finiteNumber(value, property);
1170
+ if (result < 0)
1171
+ throw new RangeError(`${property} must be non-negative; received ${value}.`);
1172
+ return result;
1173
+ }
1174
+ function isEven(value) {
1175
+ return Math.abs(value % 2) === 0;
1176
+ }
1177
+
1178
+ /**
1179
+ * Contract-compatible Animation3D mixer runtime.
1180
+ *
1181
+ * Clips remain caller-owned immutable values. The mixer caches only samplers,
1182
+ * blend accumulators, and resolver results; it never retains AssetHandles.
1183
+ */
1184
+ class Animation3DMixerRuntime {
1185
+ resolver;
1186
+ _actions = [];
1187
+ _actionsById = new Map();
1188
+ _accumulators = new Map();
1189
+ _accumulatorList = [];
1190
+ _resolvedBindings = new Map();
1191
+ _resolverRevision = Number.NaN;
1192
+ _state = 'active';
1193
+ _time = 0;
1194
+ _timeScale = 1;
1195
+ _nextActionId = 1;
1196
+ _synchronizedFrameOut = null;
1197
+ _synchronizedPreviousTime = 0;
1198
+ constructor(resolver) {
1199
+ this.resolver = resolver;
1200
+ }
1201
+ get state() { return this._state; }
1202
+ get actions() { return this._actions; }
1203
+ get liveBindingCount() { return this._accumulators.size; }
1204
+ get time() { return this._time; }
1205
+ get timeScale() { return this._timeScale; }
1206
+ set timeScale(value) {
1207
+ this.assertActive();
1208
+ if (!Number.isFinite(value)) {
1209
+ throw new RangeError(`Animation3DMixer.timeScale must be finite; received ${value}.`);
1210
+ }
1211
+ this._timeScale = value;
1212
+ }
1213
+ createAction(clip, options = {}) {
1214
+ this.assertActive();
1215
+ const clipId = typeof clip?.id === 'string' ? clip.id : '';
1216
+ const id = options.id ?? `${clipId}:${this._nextActionId++}`;
1217
+ if (!id) {
1218
+ throw new Animation3DError('invalid-action', 'Animation3D action id must not be empty.', { clipId });
1219
+ }
1220
+ if (this._actionsById.has(id)) {
1221
+ throw new Animation3DError('duplicate-action-id', `Animation3D action id "${id}" already exists in this mixer.`, { actionId: id });
1222
+ }
1223
+ const action = new Animation3DActionRuntime(this, clip, id, options);
1224
+ for (let index = 0; index < action.runtimeTracks.length; index++) {
1225
+ this._retainBinding(action.runtimeTracks[index].track.binding);
1226
+ }
1227
+ this._actions.push(action);
1228
+ this._actionsById.set(id, action);
1229
+ return action;
1230
+ }
1231
+ getAction(actionId) {
1232
+ this.assertActive();
1233
+ return this._actionsById.get(actionId) ?? null;
1234
+ }
1235
+ removeAction(action) {
1236
+ this.assertActive();
1237
+ const runtime = typeof action === 'string'
1238
+ ? this._actionsById.get(action)
1239
+ : action instanceof Animation3DActionRuntime
1240
+ ? action
1241
+ : undefined;
1242
+ if (!runtime || this._actionsById.get(runtime.id) !== runtime)
1243
+ return false;
1244
+ const index = this._actions.indexOf(runtime);
1245
+ if (index >= 0)
1246
+ this._actions.splice(index, 1);
1247
+ this._actionsById.delete(runtime.id);
1248
+ for (let trackIndex = 0; trackIndex < runtime.runtimeTracks.length; trackIndex++) {
1249
+ this._releaseBinding(runtime.runtimeTracks[trackIndex].track.binding);
1250
+ }
1251
+ runtime.invalidate();
1252
+ return true;
1253
+ }
1254
+ stopAllActions() {
1255
+ this.assertActive();
1256
+ for (let index = 0; index < this._actions.length; index++)
1257
+ this._actions[index].stop();
1258
+ return this;
1259
+ }
1260
+ update(deltaSeconds, out) {
1261
+ this.assertActive();
1262
+ this._requireIndependentClock();
1263
+ if (!Number.isFinite(deltaSeconds)) {
1264
+ throw new RangeError(`Animation3DMixer delta must be finite; received ${deltaSeconds}.`);
1265
+ }
1266
+ const previousTime = this._time;
1267
+ const mixerDelta = deltaSeconds * this._timeScale;
1268
+ this._time += mixerDelta;
1269
+ out.reset(this._time);
1270
+ this._advanceActions(mixerDelta, previousTime, this._time, out);
1271
+ this._evaluateInto(out);
1272
+ return out.seal();
1273
+ }
1274
+ evaluate(out) {
1275
+ this.assertActive();
1276
+ this._requireIndependentClock();
1277
+ out.reset(this._time);
1278
+ this._evaluateInto(out);
1279
+ return out.seal();
1280
+ }
1281
+ setTime(timeSeconds, out) {
1282
+ this.assertActive();
1283
+ this._requireIndependentClock();
1284
+ if (!Number.isFinite(timeSeconds)) {
1285
+ throw new RangeError(`Animation3DMixer time must be finite; received ${timeSeconds}.`);
1286
+ }
1287
+ const previousTime = this._time;
1288
+ const mixerDelta = timeSeconds - previousTime;
1289
+ this._time = timeSeconds;
1290
+ out.reset(this._time);
1291
+ this._advanceActions(mixerDelta, previousTime, this._time, out);
1292
+ this._evaluateInto(out);
1293
+ return out.seal();
1294
+ }
1295
+ /**
1296
+ * Opens an externally-clocked evaluation frame. Ordinary update/evaluate/
1297
+ * setTime calls are rejected until the synchronized frame is closed.
1298
+ */
1299
+ beginSynchronizedFrame(timeSeconds, out) {
1300
+ this.assertActive();
1301
+ if (this._synchronizedFrameOut) {
1302
+ throw new Error('Animation3D mixer already has an active synchronized frame.');
1303
+ }
1304
+ if (!Number.isFinite(timeSeconds)) {
1305
+ throw new RangeError(`Animation3D synchronized frame time must be finite; received ${timeSeconds}.`);
1306
+ }
1307
+ this._synchronizedPreviousTime = this._time;
1308
+ this._time = timeSeconds;
1309
+ this._synchronizedFrameOut = out;
1310
+ out.reset(timeSeconds);
1311
+ }
1312
+ endSynchronizedFrame(out) {
1313
+ this.assertActive();
1314
+ if (this._synchronizedFrameOut !== out) {
1315
+ throw new Error('Animation3D synchronized frame output does not match the active frame.');
1316
+ }
1317
+ try {
1318
+ this._evaluateInto(out);
1319
+ const pose = out.seal();
1320
+ this._synchronizedFrameOut = null;
1321
+ this._synchronizedPreviousTime = this._time;
1322
+ return pose;
1323
+ }
1324
+ catch (error) {
1325
+ this._time = this._synchronizedPreviousTime;
1326
+ this._synchronizedFrameOut = null;
1327
+ throw error;
1328
+ }
1329
+ }
1330
+ cancelSynchronizedFrame(out) {
1331
+ if (this._synchronizedFrameOut !== out)
1332
+ return;
1333
+ this._time = this._synchronizedPreviousTime;
1334
+ this._synchronizedFrameOut = null;
1335
+ }
1336
+ /** @internal Restores a completed frame when a sibling mixer in one controller transaction fails. */
1337
+ rollbackCompletedSynchronizedFrame(timeSeconds) {
1338
+ this.assertActive();
1339
+ if (this._synchronizedFrameOut) {
1340
+ throw new Error('Animation3D mixer cannot roll back a completed frame while another frame is active.');
1341
+ }
1342
+ if (!Number.isFinite(timeSeconds)) {
1343
+ throw new RangeError(`Animation3D rollback time must be finite; received ${timeSeconds}.`);
1344
+ }
1345
+ this._time = timeSeconds;
1346
+ this._synchronizedPreviousTime = timeSeconds;
1347
+ }
1348
+ destroy() {
1349
+ if (this._state === 'destroyed')
1350
+ return;
1351
+ for (let index = 0; index < this._actions.length; index++)
1352
+ this._actions[index].invalidate();
1353
+ this._actions.length = 0;
1354
+ this._actionsById.clear();
1355
+ this._accumulatorList.length = 0;
1356
+ this._accumulators.clear();
1357
+ this._resolvedBindings.clear();
1358
+ this._synchronizedFrameOut = null;
1359
+ this._synchronizedPreviousTime = 0;
1360
+ this._state = 'destroyed';
1361
+ }
1362
+ assertActive() {
1363
+ if (this._state !== 'active') {
1364
+ throw new Animation3DError('mixer-destroyed', 'Animation3D mixer has been destroyed.');
1365
+ }
1366
+ }
1367
+ _requireIndependentClock() {
1368
+ if (this._synchronizedFrameOut) {
1369
+ throw new Error('Animation3D mixer clock cannot advance during a controller-owned synchronized frame.');
1370
+ }
1371
+ }
1372
+ _advanceActions(mixerDelta, previousTime, currentTime, out) {
1373
+ for (let index = 0; index < this._actions.length; index++) {
1374
+ this._actions[index].advance(mixerDelta, previousTime, currentTime, out);
1375
+ }
1376
+ }
1377
+ _evaluateInto(out) {
1378
+ this._invalidateResolvedBindingsIfNeeded();
1379
+ for (let index = 0; index < this._accumulatorList.length; index++) {
1380
+ resetAccumulator(this._accumulatorList[index]);
1381
+ }
1382
+ for (let actionIndex = 0; actionIndex < this._actions.length; actionIndex++) {
1383
+ const action = this._actions[actionIndex];
1384
+ const weight = action.effectiveWeight;
1385
+ if (weight <= 0)
1386
+ continue;
1387
+ const tracks = action.runtimeTracks;
1388
+ for (let trackIndex = 0; trackIndex < tracks.length; trackIndex++) {
1389
+ const runtimeTrack = tracks[trackIndex];
1390
+ const binding = runtimeTrack.track.binding;
1391
+ if (!action.acceptsBinding(binding))
1392
+ continue;
1393
+ const accumulator = this._accumulators.get(binding.id);
1394
+ if (!accumulator)
1395
+ continue;
1396
+ const sampled = runtimeTrack.sampler.sample(action.time);
1397
+ accumulator.active = true;
1398
+ if (action.blendMode === 'additive') {
1399
+ accumulateAdditive(accumulator, runtimeTrack, sampled, weight);
1400
+ }
1401
+ else {
1402
+ accumulateOverride(accumulator, sampled, weight);
1403
+ }
1404
+ }
1405
+ }
1406
+ for (let index = 0; index < this._accumulatorList.length; index++) {
1407
+ const accumulator = this._accumulatorList[index];
1408
+ if (!accumulator.active)
1409
+ continue;
1410
+ this._readBase(accumulator);
1411
+ finalizeAccumulator(accumulator);
1412
+ out.write(accumulator.binding, accumulator.result);
1413
+ }
1414
+ }
1415
+ _retainBinding(binding) {
1416
+ const existing = this._accumulators.get(binding.id);
1417
+ if (existing) {
1418
+ if (existing.binding.valueSize !== binding.valueSize
1419
+ || existing.binding.valueType !== binding.valueType) {
1420
+ throw new RangeError(`Animation3D binding id "${binding.id}" has incompatible value contracts.`);
1421
+ }
1422
+ existing.references++;
1423
+ return;
1424
+ }
1425
+ const size = binding.valueSize;
1426
+ const accumulator = {
1427
+ binding,
1428
+ base: new Float32Array(size),
1429
+ result: new Float32Array(size),
1430
+ overrideSum: new Float32Array(size),
1431
+ additiveSum: new Float32Array(size),
1432
+ quaternionAnchor: new Float32Array(4),
1433
+ additiveQuaternion: new Float32Array([0, 0, 0, 1]),
1434
+ references: 1,
1435
+ active: false,
1436
+ overrideWeight: 0,
1437
+ quaternionAnchorSet: false,
1438
+ additiveQuaternionUsed: false,
1439
+ };
1440
+ this._accumulators.set(binding.id, accumulator);
1441
+ this._accumulatorList.push(accumulator);
1442
+ }
1443
+ _releaseBinding(binding) {
1444
+ const accumulator = this._accumulators.get(binding.id);
1445
+ if (!accumulator)
1446
+ return;
1447
+ accumulator.references--;
1448
+ if (accumulator.references > 0)
1449
+ return;
1450
+ this._accumulators.delete(binding.id);
1451
+ this._resolvedBindings.delete(binding.id);
1452
+ const index = this._accumulatorList.indexOf(accumulator);
1453
+ if (index >= 0)
1454
+ this._accumulatorList.splice(index, 1);
1455
+ }
1456
+ _invalidateResolvedBindingsIfNeeded() {
1457
+ if (this._resolverRevision === this.resolver.revision)
1458
+ return;
1459
+ this._resolverRevision = this.resolver.revision;
1460
+ this._resolvedBindings.clear();
1461
+ }
1462
+ _readBase(accumulator) {
1463
+ const id = accumulator.binding.id;
1464
+ let resolved = this._resolvedBindings.get(id);
1465
+ if (resolved === undefined && !this._resolvedBindings.has(id)) {
1466
+ resolved = this.resolver.resolve(accumulator.binding);
1467
+ this._resolvedBindings.set(id, resolved);
1468
+ }
1469
+ if (resolved) {
1470
+ resolved.read(accumulator.base);
1471
+ return;
1472
+ }
1473
+ throw new Animation3DError('resolver-miss', `Animation3D binding resolver could not resolve "${id}".`, { resolver: 'binding', bindingId: id });
1474
+ }
1475
+ }
1476
+ function resetAccumulator(accumulator) {
1477
+ accumulator.active = false;
1478
+ accumulator.overrideWeight = 0;
1479
+ accumulator.quaternionAnchorSet = false;
1480
+ accumulator.additiveQuaternionUsed = false;
1481
+ accumulator.overrideSum.fill(0);
1482
+ accumulator.additiveSum.fill(0);
1483
+ accumulator.additiveQuaternion[0] = 0;
1484
+ accumulator.additiveQuaternion[1] = 0;
1485
+ accumulator.additiveQuaternion[2] = 0;
1486
+ accumulator.additiveQuaternion[3] = 1;
1487
+ }
1488
+ function accumulateOverride(accumulator, sampled, weight) {
1489
+ if (accumulator.binding.valueType !== 'quaternion') {
1490
+ for (let index = 0; index < sampled.length; index++) {
1491
+ accumulator.overrideSum[index] = accumulator.overrideSum[index] + sampled[index] * weight;
1492
+ }
1493
+ accumulator.overrideWeight += weight;
1494
+ return;
1495
+ }
1496
+ let sign = 1;
1497
+ if (!accumulator.quaternionAnchorSet) {
1498
+ accumulator.quaternionAnchor.set(sampled);
1499
+ accumulator.quaternionAnchorSet = true;
1500
+ }
1501
+ else {
1502
+ const dot = accumulator.quaternionAnchor[0] * sampled[0]
1503
+ + accumulator.quaternionAnchor[1] * sampled[1]
1504
+ + accumulator.quaternionAnchor[2] * sampled[2]
1505
+ + accumulator.quaternionAnchor[3] * sampled[3];
1506
+ if (dot < 0)
1507
+ sign = -1;
1508
+ }
1509
+ for (let index = 0; index < 4; index++) {
1510
+ accumulator.overrideSum[index] = accumulator.overrideSum[index]
1511
+ + sampled[index] * weight * sign;
1512
+ }
1513
+ accumulator.overrideWeight += weight;
1514
+ }
1515
+ function accumulateAdditive(accumulator, runtimeTrack, sampled, weight) {
1516
+ if (accumulator.binding.valueType === 'quaternion') {
1517
+ accumulateAdditiveQuaternion(accumulator.additiveQuaternion, sampled, runtimeTrack.reference, weight);
1518
+ accumulator.additiveQuaternionUsed = true;
1519
+ return;
1520
+ }
1521
+ for (let index = 0; index < sampled.length; index++) {
1522
+ accumulator.additiveSum[index] = accumulator.additiveSum[index]
1523
+ + (sampled[index] - runtimeTrack.reference[index]) * weight;
1524
+ }
1525
+ }
1526
+ function finalizeAccumulator(accumulator) {
1527
+ const size = accumulator.binding.valueSize;
1528
+ const overrideWeight = accumulator.overrideWeight;
1529
+ if (accumulator.binding.valueType !== 'quaternion') {
1530
+ if (overrideWeight > 0) {
1531
+ if (overrideWeight < 1) {
1532
+ const baseWeight = 1 - overrideWeight;
1533
+ for (let index = 0; index < size; index++) {
1534
+ accumulator.result[index] = accumulator.overrideSum[index]
1535
+ + accumulator.base[index] * baseWeight;
1536
+ }
1537
+ }
1538
+ else {
1539
+ const inverseWeight = 1 / overrideWeight;
1540
+ for (let index = 0; index < size; index++) {
1541
+ accumulator.result[index] = accumulator.overrideSum[index] * inverseWeight;
1542
+ }
1543
+ }
1544
+ }
1545
+ else {
1546
+ accumulator.result.set(accumulator.base);
1547
+ }
1548
+ for (let index = 0; index < size; index++) {
1549
+ accumulator.result[index] = accumulator.result[index] + accumulator.additiveSum[index];
1550
+ }
1551
+ return;
1552
+ }
1553
+ if (overrideWeight > 0) {
1554
+ accumulator.result.set(accumulator.overrideSum);
1555
+ if (overrideWeight < 1) {
1556
+ let sign = 1;
1557
+ const anchorDot = accumulator.quaternionAnchor[0] * accumulator.base[0]
1558
+ + accumulator.quaternionAnchor[1] * accumulator.base[1]
1559
+ + accumulator.quaternionAnchor[2] * accumulator.base[2]
1560
+ + accumulator.quaternionAnchor[3] * accumulator.base[3];
1561
+ if (anchorDot < 0)
1562
+ sign = -1;
1563
+ const baseWeight = 1 - overrideWeight;
1564
+ for (let index = 0; index < 4; index++) {
1565
+ accumulator.result[index] = accumulator.result[index]
1566
+ + accumulator.base[index] * baseWeight * sign;
1567
+ }
1568
+ }
1569
+ normalizeQuaternion$1(accumulator.result);
1570
+ }
1571
+ else {
1572
+ accumulator.result.set(accumulator.base);
1573
+ normalizeQuaternion$1(accumulator.result);
1574
+ }
1575
+ if (accumulator.additiveQuaternionUsed) {
1576
+ multiplyQuaternionInPlace(accumulator.result, accumulator.additiveQuaternion);
1577
+ normalizeQuaternion$1(accumulator.result);
1578
+ }
1579
+ }
1580
+ function accumulateAdditiveQuaternion(accumulated, sampled, reference, weight) {
1581
+ const rx = reference[0];
1582
+ const ry = reference[1];
1583
+ const rz = reference[2];
1584
+ const rw = reference[3];
1585
+ const inverseLengthSquared = 1 / Math.max(Number.EPSILON, rx * rx + ry * ry + rz * rz + rw * rw);
1586
+ const ix = -rx * inverseLengthSquared;
1587
+ const iy = -ry * inverseLengthSquared;
1588
+ const iz = -rz * inverseLengthSquared;
1589
+ const iw = rw * inverseLengthSquared;
1590
+ const sx = sampled[0];
1591
+ const sy = sampled[1];
1592
+ const sz = sampled[2];
1593
+ const sw = sampled[3];
1594
+ let dx = iw * sx + ix * sw + iy * sz - iz * sy;
1595
+ let dy = iw * sy - ix * sz + iy * sw + iz * sx;
1596
+ let dz = iw * sz + ix * sy - iy * sx + iz * sw;
1597
+ let dw = iw * sw - ix * sx - iy * sy - iz * sz;
1598
+ if (dw < 0) {
1599
+ dx = -dx;
1600
+ dy = -dy;
1601
+ dz = -dz;
1602
+ dw = -dw;
1603
+ }
1604
+ dw = Math.min(1, Math.max(-1, dw));
1605
+ let wx;
1606
+ let wy;
1607
+ let wz;
1608
+ let ww;
1609
+ if (dw > 0.9995) {
1610
+ wx = dx * weight;
1611
+ wy = dy * weight;
1612
+ wz = dz * weight;
1613
+ ww = 1 + (dw - 1) * weight;
1614
+ const inverseLength = 1 / Math.max(Number.EPSILON, Math.hypot(wx, wy, wz, ww));
1615
+ wx *= inverseLength;
1616
+ wy *= inverseLength;
1617
+ wz *= inverseLength;
1618
+ ww *= inverseLength;
1619
+ }
1620
+ else {
1621
+ const theta = Math.acos(dw);
1622
+ const scale = Math.sin(theta * weight) / Math.sin(theta);
1623
+ wx = dx * scale;
1624
+ wy = dy * scale;
1625
+ wz = dz * scale;
1626
+ ww = Math.cos(theta * weight);
1627
+ }
1628
+ const ax = accumulated[0];
1629
+ const ay = accumulated[1];
1630
+ const az = accumulated[2];
1631
+ const aw = accumulated[3];
1632
+ accumulated[0] = aw * wx + ax * ww + ay * wz - az * wy;
1633
+ accumulated[1] = aw * wy - ax * wz + ay * ww + az * wx;
1634
+ accumulated[2] = aw * wz + ax * wy - ay * wx + az * ww;
1635
+ accumulated[3] = aw * ww - ax * wx - ay * wy - az * wz;
1636
+ normalizeQuaternion$1(accumulated);
1637
+ }
1638
+ function multiplyQuaternionInPlace(target, right) {
1639
+ const ax = target[0];
1640
+ const ay = target[1];
1641
+ const az = target[2];
1642
+ const aw = target[3];
1643
+ const bx = right[0];
1644
+ const by = right[1];
1645
+ const bz = right[2];
1646
+ const bw = right[3];
1647
+ target[0] = aw * bx + ax * bw + ay * bz - az * by;
1648
+ target[1] = aw * by - ax * bz + ay * bw + az * bx;
1649
+ target[2] = aw * bz + ax * by - ay * bx + az * bw;
1650
+ target[3] = aw * bw - ax * bx - ay * by - az * bz;
1651
+ }
1652
+
1653
+ const MIXER_RUNTIMES = new WeakMap();
1654
+ /** @internal Connects the public facade to controller-only runtime operations. */
1655
+ function registerAnimation3DMixerRuntime(mixer, runtime) {
1656
+ MIXER_RUNTIMES.set(mixer, runtime);
1657
+ }
1658
+ /** @internal Used only by the state-machine facade integration. */
1659
+ function animation3DMixerRuntime(mixer) {
1660
+ const runtime = MIXER_RUNTIMES.get(mixer);
1661
+ if (!runtime) {
1662
+ throw new TypeError('Expected an Animation3DMixer facade instance.');
1663
+ }
1664
+ return runtime;
1665
+ }
1666
+
1667
+ /**
1668
+ * Public mixer facade. Controller-only clock/transaction hooks remain on the
1669
+ * internal runtime and are intentionally absent from this surface.
1670
+ */
1671
+ class Animation3DMixer {
1672
+ #runtime;
1673
+ constructor(resolver) {
1674
+ this.#runtime = new Animation3DMixerRuntime(resolver);
1675
+ registerAnimation3DMixerRuntime(this, this.#runtime);
1676
+ }
1677
+ get state() { return this.#runtime.state; }
1678
+ get resolver() { return this.#runtime.resolver; }
1679
+ get actions() { return this.#runtime.actions; }
1680
+ get time() { return this.#runtime.time; }
1681
+ get timeScale() { return this.#runtime.timeScale; }
1682
+ set timeScale(value) { this.#runtime.timeScale = value; }
1683
+ createAction(clip, options) {
1684
+ return this.#runtime.createAction(clip, options);
1685
+ }
1686
+ getAction(actionId) {
1687
+ return this.#runtime.getAction(actionId);
1688
+ }
1689
+ removeAction(action) {
1690
+ return this.#runtime.removeAction(action);
1691
+ }
1692
+ stopAllActions() {
1693
+ this.#runtime.stopAllActions();
1694
+ return this;
1695
+ }
1696
+ update(deltaSeconds, out) {
1697
+ return this.#runtime.update(deltaSeconds, out);
1698
+ }
1699
+ evaluate(out) {
1700
+ return this.#runtime.evaluate(out);
1701
+ }
1702
+ setTime(timeSeconds, out) {
1703
+ return this.#runtime.setTime(timeSeconds, out);
1704
+ }
1705
+ destroy() {
1706
+ this.#runtime.destroy();
1707
+ }
1708
+ }
1709
+
1710
+ class GltfAnimation3DResolvedEndpoint {
1711
+ binding;
1712
+ state;
1713
+ path;
1714
+ _onDirectWrite;
1715
+ constructor(binding, state, path, onDirectWrite) {
1716
+ this.binding = binding;
1717
+ this.state = state;
1718
+ this.path = path;
1719
+ this._onDirectWrite = onDirectWrite;
1720
+ }
1721
+ read(out) {
1722
+ copyValues(this.baseValue(), out, this.binding.valueSize);
1723
+ }
1724
+ write(value) {
1725
+ this.writeCurrent(value);
1726
+ commitTargetState(this.state, this.path.startsWith('transform.'), this.path === 'morph.weights');
1727
+ this._onDirectWrite();
1728
+ }
1729
+ resetCurrentFromBase() {
1730
+ this.state.translation.set(this.state.baseTranslation);
1731
+ this.state.rotation.set(this.state.baseRotation);
1732
+ this.state.scale.set(this.state.baseScale);
1733
+ this.state.weights.set(this.state.baseWeights);
1734
+ }
1735
+ writeCurrent(value) {
1736
+ switch (this.path) {
1737
+ case 'transform.translation':
1738
+ copyValues(value, this.state.translation, 3);
1739
+ this.state.touchedTransform = true;
1740
+ break;
1741
+ case 'transform.rotation':
1742
+ copyValues(value, this.state.rotation, 4);
1743
+ normalizeQuaternion(this.state.rotation);
1744
+ this.state.touchedTransform = true;
1745
+ break;
1746
+ case 'transform.scale':
1747
+ copyValues(value, this.state.scale, 3);
1748
+ this.state.touchedTransform = true;
1749
+ break;
1750
+ case 'morph.weights':
1751
+ copyValues(value, this.state.weights, this.binding.valueSize);
1752
+ this.state.touchedWeights = true;
1753
+ break;
1754
+ }
1755
+ }
1756
+ baseValue() {
1757
+ switch (this.path) {
1758
+ case 'transform.translation': return this.state.baseTranslation;
1759
+ case 'transform.rotation': return this.state.baseRotation;
1760
+ case 'transform.scale': return this.state.baseScale;
1761
+ case 'morph.weights': return this.state.baseWeights;
1762
+ }
1763
+ }
1764
+ }
1765
+ /**
1766
+ * Resolves source-independent Animation3D bindings to one loaded glTF model.
1767
+ * Base reads remain the authored glTF pose so partial fades do not accumulate
1768
+ * the previously applied frame back into the next mix.
1769
+ */
1770
+ class GltfAnimation3DBindingResolver {
1771
+ _endpoints;
1772
+ _revision = 0;
1773
+ _destroyed = false;
1774
+ constructor(endpoints) {
1775
+ this._endpoints = endpoints;
1776
+ }
1777
+ get revision() { return this._revision; }
1778
+ get bindingCount() { return this._endpoints.size; }
1779
+ get destroyed() { return this._destroyed; }
1780
+ resolve(binding) {
1781
+ if (this._destroyed)
1782
+ return null;
1783
+ const endpoint = this._endpoints.get(binding.id);
1784
+ if (!endpoint
1785
+ || endpoint.binding.path !== binding.path
1786
+ || endpoint.binding.valueType !== binding.valueType
1787
+ || endpoint.binding.valueSize !== binding.valueSize) {
1788
+ return null;
1789
+ }
1790
+ return endpoint;
1791
+ }
1792
+ destroy() {
1793
+ if (this._destroyed)
1794
+ return;
1795
+ this._destroyed = true;
1796
+ this._endpoints.clear();
1797
+ this._revision++;
1798
+ }
1799
+ }
1800
+ /**
1801
+ * Applies a transient Animation3D pose to glTF Transform3D, skinning and morph
1802
+ * resources. Target scratch and active lists are retained and reused.
1803
+ */
1804
+ class GltfAnimation3DPoseApplier {
1805
+ _targetStates;
1806
+ _endpoints;
1807
+ _skinnedPrimitives;
1808
+ _activeTargets = [];
1809
+ _previousTargets = [];
1810
+ _generation = 0;
1811
+ _destroyed = false;
1812
+ constructor(context) {
1813
+ this._targetStates = context.targetStates;
1814
+ this._endpoints = context.endpoints;
1815
+ this._skinnedPrimitives = context.skinnedPrimitives;
1816
+ }
1817
+ get targetCount() { return this._targetStates.size; }
1818
+ get destroyed() { return this._destroyed; }
1819
+ apply(pose) {
1820
+ this.assertActive();
1821
+ const generation = ++this._generation;
1822
+ this._activeTargets.length = 0;
1823
+ for (const channel of pose.channels) {
1824
+ const endpoint = this._endpoints.get(channel.binding.id);
1825
+ if (!endpoint)
1826
+ continue;
1827
+ const state = endpoint.state;
1828
+ if (state.seenGeneration !== generation) {
1829
+ state.seenGeneration = generation;
1830
+ state.touchedTransform = false;
1831
+ state.touchedWeights = false;
1832
+ endpoint.resetCurrentFromBase();
1833
+ this._activeTargets.push(state);
1834
+ }
1835
+ endpoint.writeCurrent(channel.value);
1836
+ }
1837
+ let changed = false;
1838
+ for (const state of this._previousTargets) {
1839
+ if (state.seenGeneration === generation)
1840
+ continue;
1841
+ state.translation.set(state.baseTranslation);
1842
+ state.rotation.set(state.baseRotation);
1843
+ state.scale.set(state.baseScale);
1844
+ state.weights.set(state.baseWeights);
1845
+ commitTargetState(state, state.appliedTransform, state.appliedWeights);
1846
+ changed ||= state.appliedTransform || state.appliedWeights;
1847
+ state.appliedTransform = false;
1848
+ state.appliedWeights = false;
1849
+ }
1850
+ for (const state of this._activeTargets) {
1851
+ const restoreTransform = state.appliedTransform && !state.touchedTransform;
1852
+ const restoreWeights = state.appliedWeights && !state.touchedWeights;
1853
+ commitTargetState(state, state.touchedTransform || restoreTransform, state.touchedWeights || restoreWeights);
1854
+ changed ||= state.touchedTransform || state.touchedWeights || restoreTransform || restoreWeights;
1855
+ state.appliedTransform = state.touchedTransform;
1856
+ state.appliedWeights = state.touchedWeights;
1857
+ }
1858
+ this._previousTargets.length = 0;
1859
+ this._previousTargets.push(...this._activeTargets);
1860
+ if (changed)
1861
+ updateSkinnedPrimitives(this._skinnedPrimitives);
1862
+ }
1863
+ destroy(restoreBasePose = true) {
1864
+ if (this._destroyed)
1865
+ return;
1866
+ if (restoreBasePose) {
1867
+ let changed = false;
1868
+ for (const state of this._previousTargets) {
1869
+ state.translation.set(state.baseTranslation);
1870
+ state.rotation.set(state.baseRotation);
1871
+ state.scale.set(state.baseScale);
1872
+ state.weights.set(state.baseWeights);
1873
+ commitTargetState(state, state.appliedTransform, state.appliedWeights);
1874
+ changed ||= state.appliedTransform || state.appliedWeights;
1875
+ }
1876
+ if (changed)
1877
+ updateSkinnedPrimitives(this._skinnedPrimitives);
1878
+ }
1879
+ this._destroyed = true;
1880
+ this._activeTargets.length = 0;
1881
+ this._previousTargets.length = 0;
1882
+ this._targetStates.clear();
1883
+ this._endpoints.clear();
1884
+ }
1885
+ assertActive() {
1886
+ if (this._destroyed)
1887
+ throw new Error('glTF Animation3D pose applier has been destroyed.');
1888
+ }
1889
+ }
1890
+ class GltfAnimation3DRuntimeOwnerComponent extends Component {
1891
+ _runtime = null;
1892
+ constructor() {
1893
+ super('GltfAnimation3DRuntimeOwner');
1894
+ }
1895
+ attach(runtime) {
1896
+ this._runtime = runtime;
1897
+ }
1898
+ release(runtime) {
1899
+ if (this._runtime === runtime)
1900
+ this._runtime = null;
1901
+ }
1902
+ clone() {
1903
+ return new GltfAnimation3DRuntimeOwnerComponent();
1904
+ }
1905
+ destroy() {
1906
+ if (this.destroyed)
1907
+ return;
1908
+ const runtime = this._runtime;
1909
+ this._runtime = null;
1910
+ runtime?.destroy();
1911
+ super.destroy();
1912
+ }
1913
+ }
1914
+ /**
1915
+ * Model-scoped owner combining the existing Animation3D mixer with glTF
1916
+ * binding and pose adapters. It does not own or dispose the LoadedGltfModel.
1917
+ */
1918
+ class GltfAnimation3DRuntime {
1919
+ mixer;
1920
+ pose = new Animation3DPoseBuffer();
1921
+ _clips;
1922
+ _owner;
1923
+ _resolver;
1924
+ _poseApplier;
1925
+ _root;
1926
+ _state = 'active';
1927
+ _signal;
1928
+ _abortListener;
1929
+ constructor(model, options = {}) {
1930
+ if (options.signal?.aborted)
1931
+ throw createAbortError('glTF Animation3D runtime creation aborted.', options.signal.reason);
1932
+ const context = createAdapterContext(model);
1933
+ this._clips = createClips(model.animationClips, context, options.clipIdPrefix);
1934
+ this._resolver = new GltfAnimation3DBindingResolver(context.endpoints);
1935
+ this._poseApplier = new GltfAnimation3DPoseApplier(context);
1936
+ this.mixer = new Animation3DMixer(this._resolver);
1937
+ this._root = model.root;
1938
+ this._owner = new GltfAnimation3DRuntimeOwnerComponent();
1939
+ this._owner.attach(this);
1940
+ model.root.addComponent(this._owner);
1941
+ this._signal = options.signal ?? null;
1942
+ this._abortListener = this._signal
1943
+ ? () => this.destroy()
1944
+ : null;
1945
+ if (this._signal && this._abortListener) {
1946
+ this._signal.addEventListener('abort', this._abortListener, { once: true });
1947
+ }
1948
+ }
1949
+ get state() { return this._state; }
1950
+ get root() { return this._root; }
1951
+ get clips() { return this._clips; }
1952
+ /** Diagnostic count without exposing the model-specific resolver handle. */
1953
+ get bindingCount() { return this._resolver.bindingCount; }
1954
+ /** Diagnostic count without exposing the model-specific pose-applier handle. */
1955
+ get targetCount() { return this._poseApplier.targetCount; }
1956
+ update(deltaSeconds) {
1957
+ this.assertActive();
1958
+ const pose = this.mixer.update(deltaSeconds, this.pose);
1959
+ this._poseApplier.apply(pose);
1960
+ return pose;
1961
+ }
1962
+ evaluate() {
1963
+ this.assertActive();
1964
+ const pose = this.mixer.evaluate(this.pose);
1965
+ this._poseApplier.apply(pose);
1966
+ return pose;
1967
+ }
1968
+ /** @internal Applies a pose produced by a shared multi-mixer state-machine transaction. */
1969
+ applySynchronizedPose(pose) {
1970
+ this.assertActive();
1971
+ this._poseApplier.apply(pose);
1972
+ }
1973
+ setTime(timeSeconds) {
1974
+ this.assertActive();
1975
+ const pose = this.mixer.setTime(timeSeconds, this.pose);
1976
+ this._poseApplier.apply(pose);
1977
+ return pose;
1978
+ }
1979
+ destroy() {
1980
+ if (this._state === 'destroyed')
1981
+ return;
1982
+ this._state = 'destroyed';
1983
+ if (this._signal && this._abortListener) {
1984
+ this._signal.removeEventListener('abort', this._abortListener);
1985
+ }
1986
+ const root = this._root;
1987
+ this._root = null;
1988
+ this._owner.release(this);
1989
+ this.mixer.destroy();
1990
+ this._poseApplier.destroy(root ? !root.destroyed : false);
1991
+ this._resolver.destroy();
1992
+ this._clips.length = 0;
1993
+ }
1994
+ assertActive() {
1995
+ if (this._state !== 'active' || this._root?.destroyed) {
1996
+ this.destroy();
1997
+ throw new Error('glTF Animation3D runtime has been destroyed.');
1998
+ }
1999
+ }
2000
+ }
2001
+ function createGltfAnimation3DRuntime(model, options = {}) {
2002
+ return new GltfAnimation3DRuntime(model, options);
2003
+ }
2004
+ /** Converts loaded legacy glTF clips without exposing glTF sampler structures. */
2005
+ function createGltfAnimation3DClips(model, clipIdPrefix) {
2006
+ const context = createAdapterContext(model);
2007
+ return createClips(model.animationClips, context, clipIdPrefix);
2008
+ }
2009
+ function createAdapterContext(model) {
2010
+ const targetSizes = new Map();
2011
+ const skinnedPrimitives = new Set();
2012
+ for (const clip of model.animationClips) {
2013
+ for (const channel of clip.channels) {
2014
+ const previousSize = targetSizes.get(channel.target) ?? channel.target.weights.length;
2015
+ targetSizes.set(channel.target, Math.max(previousSize, channel.path === 'weights' ? channel.valueSize : 0));
2016
+ }
2017
+ for (const primitive of clip.skinnedPrimitives)
2018
+ skinnedPrimitives.add(primitive);
2019
+ }
2020
+ const targetStates = new Map();
2021
+ for (const [target, weightSize] of targetSizes) {
2022
+ const nodePath = createStableNodePath(model.root, target.entity);
2023
+ const baseWeights = new Float32Array(Math.max(weightSize, target.weights.length));
2024
+ copyValues(target.weights, baseWeights, baseWeights.length);
2025
+ const baseRotation = new Float32Array(target.rotation);
2026
+ normalizeQuaternion(baseRotation);
2027
+ targetStates.set(target, {
2028
+ target,
2029
+ nodePath,
2030
+ nodeKey: nodePath.join('/'),
2031
+ baseTranslation: new Float32Array(target.translation),
2032
+ baseRotation,
2033
+ baseScale: new Float32Array(target.scale),
2034
+ baseWeights,
2035
+ translation: new Float32Array(target.translation),
2036
+ rotation: new Float32Array(baseRotation),
2037
+ scale: new Float32Array(target.scale),
2038
+ weights: new Float32Array(baseWeights),
2039
+ matrix: new Float32Array(16),
2040
+ seenGeneration: 0,
2041
+ touchedTransform: false,
2042
+ touchedWeights: false,
2043
+ appliedTransform: false,
2044
+ appliedWeights: false,
2045
+ });
2046
+ }
2047
+ const endpoints = new Map();
2048
+ const context = {
2049
+ targetStates,
2050
+ endpoints,
2051
+ skinnedPrimitives: [...skinnedPrimitives],
2052
+ };
2053
+ const updateSkinning = () => updateSkinnedPrimitives(context.skinnedPrimitives);
2054
+ for (const clip of model.animationClips) {
2055
+ for (const channel of clip.channels) {
2056
+ const state = requiredTargetState(context, channel.target);
2057
+ const binding = createBinding(state, channel.path, channel.valueSize);
2058
+ if (!endpoints.has(binding.id)) {
2059
+ endpoints.set(binding.id, new GltfAnimation3DResolvedEndpoint(binding, state, bindingPath(channel.path), updateSkinning));
2060
+ }
2061
+ }
2062
+ }
2063
+ return context;
2064
+ }
2065
+ function createClips(clips, context, clipIdPrefix = 'gltf-animation') {
2066
+ return clips.map((clip, clipIndex) => {
2067
+ const id = `${clipIdPrefix}:${clipIndex}`;
2068
+ const tracks = clip.channels.map((channel, channelIndex) => {
2069
+ const state = requiredTargetState(context, channel.target);
2070
+ const binding = context.endpoints.get(bindingId(state, bindingPath(channel.path)))?.binding;
2071
+ if (!binding)
2072
+ throw new Error(`Missing glTF Animation3D binding for ${channel.path}.`);
2073
+ const times = copyFiniteValues(channel.input, `${id}.tracks[${channelIndex}].times`);
2074
+ validateTimes(times, clip.duration, `${id}.tracks[${channelIndex}].times`);
2075
+ const interpolation = mapInterpolation(channel.interpolation);
2076
+ const values = copyFiniteValues(channel.output, `${id}.tracks[${channelIndex}].values`);
2077
+ validateTrackValues(times, values, binding.valueSize, interpolation, `${id}.tracks[${channelIndex}]`);
2078
+ return {
2079
+ id: `${id}:track:${channelIndex}`,
2080
+ binding,
2081
+ interpolation,
2082
+ times,
2083
+ values,
2084
+ };
2085
+ });
2086
+ return {
2087
+ format: 'haiyue-animation3d-clip@1',
2088
+ id,
2089
+ name: clip.name,
2090
+ duration: clip.duration,
2091
+ tracks,
2092
+ events: [],
2093
+ };
2094
+ });
2095
+ }
2096
+ function createBinding(state, path, valueSize) {
2097
+ const target = Object.freeze({
2098
+ kind: 'node-path',
2099
+ segments: state.nodePath,
2100
+ });
2101
+ switch (path) {
2102
+ case 'translation':
2103
+ return {
2104
+ id: bindingId(state, 'transform.translation'),
2105
+ target,
2106
+ path: 'transform.translation',
2107
+ valueType: 'vec3',
2108
+ valueSize: 3,
2109
+ };
2110
+ case 'rotation':
2111
+ return {
2112
+ id: bindingId(state, 'transform.rotation'),
2113
+ target,
2114
+ path: 'transform.rotation',
2115
+ valueType: 'quaternion',
2116
+ valueSize: 4,
2117
+ };
2118
+ case 'scale':
2119
+ return {
2120
+ id: bindingId(state, 'transform.scale'),
2121
+ target,
2122
+ path: 'transform.scale',
2123
+ valueType: 'vec3',
2124
+ valueSize: 3,
2125
+ };
2126
+ case 'weights':
2127
+ if (!Number.isInteger(valueSize) || valueSize <= 0) {
2128
+ throw new RangeError(`glTF morph binding requires a positive valueSize; received ${valueSize}.`);
2129
+ }
2130
+ return {
2131
+ id: bindingId(state, 'morph.weights'),
2132
+ target,
2133
+ path: 'morph.weights',
2134
+ valueType: 'weights',
2135
+ valueSize,
2136
+ };
2137
+ }
2138
+ }
2139
+ function bindingPath(path) {
2140
+ switch (path) {
2141
+ case 'translation': return 'transform.translation';
2142
+ case 'rotation': return 'transform.rotation';
2143
+ case 'scale': return 'transform.scale';
2144
+ case 'weights': return 'morph.weights';
2145
+ }
2146
+ }
2147
+ function bindingId(state, path) {
2148
+ return `gltf:${state.nodeKey}:${path}`;
2149
+ }
2150
+ function mapInterpolation(interpolation) {
2151
+ switch (interpolation) {
2152
+ case 'STEP': return 'step';
2153
+ case 'CUBICSPLINE': return 'cubic-spline';
2154
+ case 'LINEAR': return 'linear';
2155
+ }
2156
+ }
2157
+ function requiredTargetState(context, target) {
2158
+ const state = context.targetStates.get(target);
2159
+ if (!state)
2160
+ throw new Error(`glTF animation target "${target.entity.name}" is not registered.`);
2161
+ return state;
2162
+ }
2163
+ function createStableNodePath(root, target) {
2164
+ const reversed = [];
2165
+ let current = target;
2166
+ while (current && current !== root) {
2167
+ const parent = current.parent;
2168
+ if (!parent) {
2169
+ throw new Error(`glTF animation target "${target.name}" is outside the loaded model root.`);
2170
+ }
2171
+ const siblingIndex = parent.children.indexOf(current);
2172
+ reversed.push(`${encodeURIComponent(current.name)}[${siblingIndex}]`);
2173
+ current = parent;
2174
+ }
2175
+ if (current !== root) {
2176
+ throw new Error(`glTF animation target "${target.name}" is outside the loaded model root.`);
2177
+ }
2178
+ reversed.reverse();
2179
+ return Object.freeze(reversed);
2180
+ }
2181
+ function copyFiniteValues(source, path) {
2182
+ const out = new Float32Array(source.length);
2183
+ for (let index = 0; index < source.length; index++) {
2184
+ const value = source[index];
2185
+ if (!Number.isFinite(value))
2186
+ throw new RangeError(`${path}[${index}] must be finite.`);
2187
+ out[index] = value;
2188
+ }
2189
+ return out;
2190
+ }
2191
+ function validateTimes(times, duration, path) {
2192
+ if (times.length === 0)
2193
+ throw new RangeError(`${path} must contain at least one key.`);
2194
+ for (let index = 0; index < times.length; index++) {
2195
+ const value = times[index];
2196
+ if ((index > 0 && value <= times[index - 1]) || value < 0 || value > duration) {
2197
+ throw new RangeError(`${path} must be strictly increasing and within clip duration.`);
2198
+ }
2199
+ }
2200
+ }
2201
+ function validateTrackValues(times, values, valueSize, interpolation, path) {
2202
+ const stride = interpolation === 'cubic-spline' ? valueSize * 3 : valueSize;
2203
+ const expected = times.length * stride;
2204
+ if (values.length !== expected) {
2205
+ throw new RangeError(`${path}.values requires ${expected} values; received ${values.length}.`);
2206
+ }
2207
+ }
2208
+ function commitTargetState(state, transformChanged, weightsChanged) {
2209
+ if (transformChanged) {
2210
+ state.target.transform.localMatrix = composeTrsMatrix(state.translation, state.rotation, state.scale, state.matrix);
2211
+ }
2212
+ if (weightsChanged)
2213
+ applyMorphWeights(state.target, state.weights);
2214
+ }
2215
+ function updateSkinnedPrimitives(primitives) {
2216
+ for (const primitive of primitives)
2217
+ updateSkinnedPrimitive(primitive);
2218
+ }
2219
+ function copyValues(source, target, count) {
2220
+ for (let index = 0; index < count; index++)
2221
+ target[index] = source[index] ?? 0;
2222
+ }
2223
+ function normalizeQuaternion(value) {
2224
+ const x = value[0] ?? 0;
2225
+ const y = value[1] ?? 0;
2226
+ const z = value[2] ?? 0;
2227
+ const w = value[3] ?? 1;
2228
+ const length = Math.hypot(x, y, z, w);
2229
+ if (length <= Number.EPSILON) {
2230
+ value.set([0, 0, 0, 1]);
2231
+ return;
2232
+ }
2233
+ value[0] = x / length;
2234
+ value[1] = y / length;
2235
+ value[2] = z / length;
2236
+ value[3] = w / length;
2237
+ }
2238
+
2239
+ export { Animation3DError as A, GltfAnimation3DRuntime as G, createGltfAnimation3DRuntime as a, animation3DMixerRuntime as b, createGltfAnimation3DClips as c, Animation3DPoseBuffer as d, Animation3DMixer as e };
2240
+ //# sourceMappingURL=GltfAnimation3DAdapter-Cu3rQQsW.js.map