@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,845 @@
1
+ import {
2
+ ANIMATION_VECTOR_SHAPE_EXTENSION_ID,
3
+ HYA_STATE_MACHINE_EXTENSION_ID,
4
+ parseHyaStateMachineExtension,
5
+ type AnimationAudioComponent,
6
+ type AnimationComponent,
7
+ type AnimationNode,
8
+ type AnimationParticle2DComponent,
9
+ type AnimationSprite2DComponent,
10
+ type AnimationVectorShapeComponent,
11
+ type AnimationVectorValueTrack,
12
+ type HyaAnimationClipRange,
13
+ type HyaStateMachineExtension,
14
+ type ParsedAnimation,
15
+ type ParsedAnimationTrack,
16
+ } from '@haiyue/animation-spec';
17
+ import type {
18
+ Animation2DBinding,
19
+ Animation2DClip,
20
+ Animation2DDiscreteTrack,
21
+ Animation2DEffectCue,
22
+ Animation2DNumericTrack,
23
+ } from './runtime/mixer/index.js';
24
+ import {
25
+ AnimationStateMachineChannelError,
26
+ assertAudioStateMachineCompatible,
27
+ hyaStateMachineChannelCapability,
28
+ type AnimationStateMachineChannelDiagnostic,
29
+ } from '../animation-state-machine/AnimationStateMachineChannels.js';
30
+
31
+ // HYA v2 keeps track times in Float32 while clip ranges remain JSON numbers.
32
+ // At ordinary multi-second timestamps the Float32 ULP is already larger than
33
+ // 1e-7, so boundary comparisons must absorb that representation difference.
34
+ const TIME_EPSILON = 1e-6;
35
+
36
+ /** Returns the canonical built-in state-machine payload from a parsed HYA document. */
37
+ export function getHyaStateMachineExtension(
38
+ animation: ParsedAnimation,
39
+ ): HyaStateMachineExtension {
40
+ const value = animation.extensions[HYA_STATE_MACHINE_EXTENSION_ID];
41
+ if (value === undefined) {
42
+ throw new RangeError(
43
+ `HYA document does not contain the built-in "${HYA_STATE_MACHINE_EXTENSION_ID}" extension.`,
44
+ );
45
+ }
46
+ return parseHyaStateMachineExtension(value, animation.duration);
47
+ }
48
+
49
+ /**
50
+ * Adapts named ranges from one HYA composition into source-independent 2D
51
+ * clips. The returned clips share no scene objects; one pose is applied to one
52
+ * Animation2DRuntime hierarchy by the state-machine runtime.
53
+ */
54
+ export function createHyaAnimation2DClips(
55
+ animation: ParsedAnimation,
56
+ extension: HyaStateMachineExtension = getHyaStateMachineExtension(animation),
57
+ ): readonly Animation2DClip[] {
58
+ assertChannelCompatibleDocument(animation, extension);
59
+ const nodesById = new Map(animation.nodes.map(node => [node.id, node]));
60
+ return Object.freeze(extension.clips.map(range => createClip(animation, range, nodesById)));
61
+ }
62
+
63
+ export interface HyaAnimation2DEffectPayload {
64
+ readonly targetId: string;
65
+ readonly slot: number;
66
+ readonly sourcePath: string;
67
+ readonly component: Readonly<AnimationParticle2DComponent | AnimationAudioComponent>;
68
+ }
69
+
70
+ function createClip(
71
+ animation: ParsedAnimation,
72
+ range: HyaAnimationClipRange,
73
+ nodesById: ReadonlyMap<string, Readonly<AnimationNode>>,
74
+ ): Animation2DClip {
75
+ const runtimeDuration = Math.fround(range.duration);
76
+ const tracks: Array<Animation2DNumericTrack | Animation2DDiscreteTrack> = [];
77
+ const effects: Animation2DEffectCue[] = [];
78
+ for (let index = 0; index < animation.tracks.length; index++) {
79
+ const source = animation.tracks[index]!;
80
+ const node = nodesById.get(source.node)!;
81
+ tracks.push(sliceNumericTrack(source, node, range, runtimeDuration, index));
82
+ }
83
+ for (let index = 0; index < animation.nodes.length; index++) {
84
+ const node = animation.nodes[index]!;
85
+ tracks.push(createVisibilityTrack(node, range, animation.duration, index));
86
+ appendComponentChannels(node, index, range, runtimeDuration, tracks, effects, animation.duration);
87
+ }
88
+ return Object.freeze({
89
+ format: 'haiyue-animation2d-clip@1',
90
+ id: range.id,
91
+ name: range.name ?? range.id,
92
+ duration: runtimeDuration,
93
+ tracks: Object.freeze(tracks),
94
+ ...(effects.length === 0 ? {} : { effects: Object.freeze(effects) }),
95
+ });
96
+ }
97
+
98
+ function appendComponentChannels(
99
+ node: Readonly<AnimationNode>,
100
+ nodeIndex: number,
101
+ range: HyaAnimationClipRange,
102
+ runtimeDuration: number,
103
+ tracks: Array<Animation2DNumericTrack | Animation2DDiscreteTrack>,
104
+ effects: Animation2DEffectCue[],
105
+ compositionDuration: number,
106
+ ): void {
107
+ let particleSlot = 0;
108
+ let audioSlot = 0;
109
+ const components = node.components ?? [];
110
+ for (let componentIndex = 0; componentIndex < components.length; componentIndex++) {
111
+ const component = components[componentIndex]!;
112
+ const path = `$.nodes[${nodeIndex}].components[${componentIndex}]`;
113
+ if (component.type === 'sprite2d') {
114
+ const sprite = component as Readonly<AnimationSprite2DComponent>;
115
+ if (!sprite.uvRectTrack) continue;
116
+ tracks.push(sliceStepVectorTrack(
117
+ sprite.uvRectTrack,
118
+ range,
119
+ runtimeDuration,
120
+ `${range.id}:component:${nodeIndex}:${componentIndex}:sprite-uv`,
121
+ Object.freeze({
122
+ id: `${node.id}.components.${componentIndex}.sprite.uv-rect`,
123
+ targetId: node.id,
124
+ path: `components.${componentIndex}.sprite.uv-rect`,
125
+ strategy: 'discrete',
126
+ defaultValue: Object.freeze([...(sprite.uvRect ?? [0, 0, 1, 1])]),
127
+ }),
128
+ ));
129
+ } else if (component.type === ANIMATION_VECTOR_SHAPE_EXTENSION_ID) {
130
+ const vector = component as Readonly<AnimationVectorShapeComponent>;
131
+ if (!vector.morph) continue;
132
+ tracks.push(sliceComponentNumericTrack(
133
+ vector.morph,
134
+ range,
135
+ runtimeDuration,
136
+ `${range.id}:component:${nodeIndex}:${componentIndex}:vector-morph`,
137
+ Object.freeze({
138
+ id: `${node.id}.components.${componentIndex}.vector.morph`,
139
+ targetId: node.id,
140
+ path: `components.${componentIndex}.vector.morph`,
141
+ strategy: 'continuous',
142
+ valueSize: vector.morph.valueSize,
143
+ defaultValue: vector.morphRelative
144
+ ? new Float32Array(vector.morph.valueSize)
145
+ : Object.freeze(Array.from(vector.values)),
146
+ }),
147
+ `${path}.morph`,
148
+ ));
149
+ } else if (component.type === 'org.haiyue.vector-path-morph@1') {
150
+ const morph = component as unknown as AnimationVectorValueTrack;
151
+ tracks.push(sliceComponentNumericTrack(
152
+ morph,
153
+ range,
154
+ runtimeDuration,
155
+ `${range.id}:component:${nodeIndex}:${componentIndex}:path-morph`,
156
+ Object.freeze({
157
+ id: `${node.id}.components.${componentIndex}.path.morph`,
158
+ targetId: node.id,
159
+ path: `components.${componentIndex}.path.morph`,
160
+ strategy: 'continuous',
161
+ valueSize: morph.valueSize,
162
+ defaultValue: Object.freeze(Array.from(morph.values).slice(0, morph.valueSize)),
163
+ }),
164
+ path,
165
+ ));
166
+ }
167
+
168
+ if (component.type === 'particle2d') {
169
+ appendEffectCue(
170
+ 'particle',
171
+ component as Readonly<AnimationParticle2DComponent>,
172
+ particleSlot++,
173
+ node,
174
+ path,
175
+ range,
176
+ effects,
177
+ compositionDuration,
178
+ );
179
+ } else if (component.type === 'audio') {
180
+ appendEffectCue(
181
+ 'audio',
182
+ component as Readonly<AnimationAudioComponent>,
183
+ audioSlot++,
184
+ node,
185
+ path,
186
+ range,
187
+ effects,
188
+ compositionDuration,
189
+ );
190
+ }
191
+ }
192
+ }
193
+
194
+ function appendEffectCue(
195
+ kind: 'particle' | 'audio',
196
+ component: Readonly<AnimationParticle2DComponent | AnimationAudioComponent>,
197
+ slot: number,
198
+ node: Readonly<AnimationNode>,
199
+ sourcePath: string,
200
+ range: HyaAnimationClipRange,
201
+ effects: Animation2DEffectCue[],
202
+ compositionDuration: number,
203
+ ): void {
204
+ const nodeStart = node.start ?? 0;
205
+ const nodeEnd = nodeStart + (node.duration ?? compositionDuration - nodeStart);
206
+ const start = Math.max(nodeStart, range.start);
207
+ const end = Math.min(nodeEnd, range.start + range.duration);
208
+ if (end <= start + TIME_EPSILON) return;
209
+ const payload: HyaAnimation2DEffectPayload = Object.freeze({
210
+ targetId: node.id,
211
+ slot,
212
+ sourcePath,
213
+ component,
214
+ });
215
+ effects.push(Object.freeze({
216
+ id: `${range.id}:${kind}:${node.id}:${slot}`,
217
+ kind,
218
+ start: Math.max(0, Math.fround(start - range.start)),
219
+ end: Math.min(Math.fround(range.duration), Math.fround(end - range.start)),
220
+ loopBehavior: kind === 'particle' ? 'restart' : 'continue',
221
+ payload,
222
+ }));
223
+ }
224
+
225
+ function sliceNumericTrack(
226
+ source: ParsedAnimationTrack,
227
+ node: Readonly<AnimationNode>,
228
+ range: HyaAnimationClipRange,
229
+ runtimeDuration: number,
230
+ sourceIndex: number,
231
+ ): Animation2DNumericTrack {
232
+ const globalTimes = collectTrackTimes(source, range);
233
+ const times = new Float32Array(globalTimes.length);
234
+ const values = new Float32Array(globalTimes.length * source.valueSize);
235
+ for (let key = 0; key < globalTimes.length; key++) {
236
+ times[key] = key === 0
237
+ ? 0
238
+ : key === globalTimes.length - 1
239
+ ? runtimeDuration
240
+ : Math.min(runtimeDuration, Math.max(0, Math.fround(globalTimes[key]! - range.start)));
241
+ sampleTrack(source, globalTimes[key]!, values, key * source.valueSize);
242
+ }
243
+ assertStrictFloatTimes(times, `${range.id}:${source.node}:${source.property}`);
244
+
245
+ const binding = createNumericBinding(source, node);
246
+ const interpolation = source.times.length < 2 ? 'step' : source.interpolation;
247
+ const easings = interpolation === 'cubic-bezier'
248
+ ? sliceEasings(source, globalTimes)
249
+ : undefined;
250
+ const spatialTangents = source.spatialTangents
251
+ ? sliceSpatialTangents(source, globalTimes)
252
+ : undefined;
253
+ return Object.freeze({
254
+ id: `${range.id}:track:${sourceIndex}`,
255
+ binding,
256
+ interpolation,
257
+ times,
258
+ values,
259
+ ...(easings ? { easings } : {}),
260
+ ...(spatialTangents ? { spatialTangents } : {}),
261
+ });
262
+ }
263
+
264
+ function createNumericBinding(
265
+ track: ParsedAnimationTrack,
266
+ node: Readonly<AnimationNode>,
267
+ ): Animation2DBinding {
268
+ const transform = node.transform;
269
+ const path = track.property === 'opacity'
270
+ ? 'opacity'
271
+ : `transform.${track.property}`;
272
+ const defaultValue = track.property === 'position'
273
+ ? transform?.position ?? [0, 0]
274
+ : track.property === 'scale'
275
+ ? transform?.scale ?? [1, 1]
276
+ : track.property === 'rotation'
277
+ ? [transform?.rotation ?? 0]
278
+ : [transform?.opacity ?? 1];
279
+ return Object.freeze({
280
+ id: `${node.id}.${path}`,
281
+ targetId: node.id,
282
+ path,
283
+ strategy: track.property === 'rotation' ? 'rotation' : 'continuous',
284
+ valueSize: track.valueSize,
285
+ defaultValue: Object.freeze(Array.from(defaultValue)),
286
+ });
287
+ }
288
+
289
+ function sliceComponentNumericTrack(
290
+ source: Readonly<AnimationVectorValueTrack>,
291
+ range: HyaAnimationClipRange,
292
+ runtimeDuration: number,
293
+ id: string,
294
+ binding: Animation2DBinding,
295
+ debugPath: string,
296
+ ): Animation2DNumericTrack {
297
+ const track = vectorTrackAsParsedTrack(source, debugPath);
298
+ const globalTimes = collectTrackTimes(track, range);
299
+ const times = new Float32Array(globalTimes.length);
300
+ const values = new Float32Array(globalTimes.length * source.valueSize);
301
+ for (let key = 0; key < globalTimes.length; key++) {
302
+ times[key] = key === 0
303
+ ? 0
304
+ : key === globalTimes.length - 1
305
+ ? runtimeDuration
306
+ : Math.min(runtimeDuration, Math.max(0, Math.fround(globalTimes[key]! - range.start)));
307
+ sampleTrack(track, globalTimes[key]!, values, key * source.valueSize);
308
+ }
309
+ assertStrictFloatTimes(times, id);
310
+ const interpolation = source.times.length < 2 ? 'step' : source.interpolation;
311
+ const easings = interpolation === 'cubic-bezier'
312
+ ? sliceEasings(track, globalTimes)
313
+ : undefined;
314
+ return Object.freeze({
315
+ id,
316
+ binding,
317
+ interpolation,
318
+ times,
319
+ values,
320
+ ...(easings ? { easings } : {}),
321
+ });
322
+ }
323
+
324
+ function sliceStepVectorTrack(
325
+ source: Readonly<AnimationVectorValueTrack>,
326
+ range: HyaAnimationClipRange,
327
+ runtimeDuration: number,
328
+ id: string,
329
+ binding: Animation2DBinding,
330
+ ): Animation2DDiscreteTrack {
331
+ const globalTimes = collectVectorTrackTimes(source, range);
332
+ const times = new Float32Array(globalTimes.length);
333
+ const values: unknown[] = [];
334
+ for (let key = 0; key < globalTimes.length; key++) {
335
+ times[key] = key === 0
336
+ ? 0
337
+ : key === globalTimes.length - 1
338
+ ? runtimeDuration
339
+ : Math.min(runtimeDuration, Math.max(0, Math.fround(globalTimes[key]! - range.start)));
340
+ const frame = findArraySegment(source.times, globalTimes[key]!);
341
+ const offset = frame * source.valueSize;
342
+ const sampled = new Float32Array(source.valueSize);
343
+ for (let component = 0; component < source.valueSize; component++) {
344
+ sampled[component] = source.values[offset + component] ?? 0;
345
+ }
346
+ values.push(sampled);
347
+ }
348
+ assertStrictFloatTimes(times, id);
349
+ return Object.freeze({
350
+ id,
351
+ binding,
352
+ interpolation: 'step',
353
+ times,
354
+ values: Object.freeze(values),
355
+ });
356
+ }
357
+
358
+ function collectVectorTrackTimes(
359
+ track: Readonly<AnimationVectorValueTrack>,
360
+ range: HyaAnimationClipRange,
361
+ ): number[] {
362
+ const end = range.start + range.duration;
363
+ const result = [range.start];
364
+ for (let index = 0; index < track.times.length; index++) {
365
+ const time = track.times[index]!;
366
+ if (time > range.start + TIME_EPSILON && time < end - TIME_EPSILON) result.push(time);
367
+ }
368
+ result.push(end);
369
+ return result;
370
+ }
371
+
372
+ function vectorTrackAsParsedTrack(
373
+ source: Readonly<AnimationVectorValueTrack>,
374
+ debugPath: string,
375
+ ): ParsedAnimationTrack {
376
+ return {
377
+ node: debugPath,
378
+ property: 'opacity',
379
+ interpolation: source.interpolation,
380
+ times: source.times instanceof Float32Array ? source.times : Float32Array.from(source.times),
381
+ values: source.values instanceof Float32Array ? source.values : Float32Array.from(source.values),
382
+ valueSize: source.valueSize as ParsedAnimationTrack['valueSize'],
383
+ ...(source.easings === undefined ? {} : {
384
+ easings: source.easings instanceof Float32Array
385
+ ? source.easings
386
+ : Float32Array.from(source.easings),
387
+ }),
388
+ };
389
+ }
390
+
391
+ function findArraySegment(times: ArrayLike<number>, time: number): number {
392
+ if (times.length <= 1 || time <= times[0]!) return 0;
393
+ if (time >= times[times.length - 1]!) return times.length - 1;
394
+ let low = 0;
395
+ let high = times.length - 1;
396
+ while (low + 1 < high) {
397
+ const middle = (low + high) >>> 1;
398
+ if (times[middle]! <= time) low = middle;
399
+ else high = middle;
400
+ }
401
+ return low;
402
+ }
403
+
404
+ function createVisibilityTrack(
405
+ node: Readonly<AnimationNode>,
406
+ range: HyaAnimationClipRange,
407
+ compositionDuration: number,
408
+ nodeIndex: number,
409
+ ): Animation2DDiscreteTrack {
410
+ const nodeStart = node.start ?? 0;
411
+ const nodeEnd = nodeStart + (node.duration ?? compositionDuration - nodeStart);
412
+ const globalTimes = [range.start];
413
+ if (nodeStart > range.start + TIME_EPSILON && nodeStart < range.start + range.duration - TIME_EPSILON) {
414
+ globalTimes.push(nodeStart);
415
+ }
416
+ if (nodeEnd > range.start + TIME_EPSILON && nodeEnd < range.start + range.duration - TIME_EPSILON) {
417
+ globalTimes.push(nodeEnd);
418
+ }
419
+ globalTimes.sort((left, right) => left - right);
420
+ const times = new Float32Array(globalTimes.length);
421
+ const values = new Array<boolean>(globalTimes.length);
422
+ for (let index = 0; index < globalTimes.length; index++) {
423
+ const globalTime = globalTimes[index]!;
424
+ times[index] = Math.fround(globalTime - range.start);
425
+ values[index] = globalTime >= nodeStart - TIME_EPSILON && globalTime < nodeEnd - TIME_EPSILON;
426
+ }
427
+ assertStrictFloatTimes(times, `${range.id}:${node.id}:visibility`);
428
+ return Object.freeze({
429
+ id: `${range.id}:visibility:${nodeIndex}`,
430
+ binding: Object.freeze({
431
+ id: `${node.id}.visibility`,
432
+ targetId: node.id,
433
+ path: 'visibility',
434
+ strategy: 'discrete',
435
+ defaultValue: true,
436
+ }),
437
+ interpolation: 'step',
438
+ times,
439
+ values: Object.freeze(values),
440
+ });
441
+ }
442
+
443
+ function collectTrackTimes(
444
+ track: ParsedAnimationTrack,
445
+ range: HyaAnimationClipRange,
446
+ ): number[] {
447
+ const end = range.start + range.duration;
448
+ const result = [range.start];
449
+ for (let index = 0; index < track.times.length; index++) {
450
+ const time = track.times[index]!;
451
+ if (time > range.start + TIME_EPSILON && time < end - TIME_EPSILON) result.push(time);
452
+ }
453
+ result.push(end);
454
+ return result;
455
+ }
456
+
457
+ function sampleTrack(
458
+ track: ParsedAnimationTrack,
459
+ time: number,
460
+ output: Float32Array,
461
+ outputOffset: number,
462
+ ): void {
463
+ const segment = findSegment(track.times, time);
464
+ const startOffset = segment * track.valueSize;
465
+ const endOffset = Math.min(segment + 1, track.times.length - 1) * track.valueSize;
466
+ let progress = 0;
467
+ if (segment < track.times.length - 1 && track.interpolation !== 'step') {
468
+ const start = track.times[segment]!;
469
+ const end = track.times[segment + 1]!;
470
+ const linear = Math.min(1, Math.max(0, (time - start) / Math.max(TIME_EPSILON, end - start)));
471
+ progress = track.interpolation === 'cubic-bezier'
472
+ ? cubicBezierYForX(track.easings!, segment, linear)
473
+ : linear;
474
+ }
475
+ if (track.spatialTangents && segment < track.times.length - 1) {
476
+ const tangentOffset = segment * 4;
477
+ for (let component = 0; component < 2; component++) {
478
+ const start = track.values[startOffset + component]!;
479
+ const end = track.values[endOffset + component]!;
480
+ output[outputOffset + component] = spatialCoordinate(
481
+ progress,
482
+ start,
483
+ start + track.spatialTangents[tangentOffset + component]!,
484
+ end + track.spatialTangents[tangentOffset + 2 + component]!,
485
+ end,
486
+ );
487
+ }
488
+ return;
489
+ }
490
+ for (let component = 0; component < track.valueSize; component++) {
491
+ const start = track.values[startOffset + component]!;
492
+ output[outputOffset + component] = start
493
+ + (track.values[endOffset + component]! - start) * progress;
494
+ }
495
+ }
496
+
497
+ function sliceSpatialTangents(track: ParsedAnimationTrack, globalTimes: readonly number[]): Float32Array {
498
+ const result = new Float32Array(Math.max(0, globalTimes.length - 1) * 4);
499
+ for (let interval = 0; interval < globalTimes.length - 1; interval++) {
500
+ const startTime = globalTimes[interval]!;
501
+ const endTime = globalTimes[interval + 1]!;
502
+ const midpoint = (startTime + endTime) * 0.5;
503
+ const sourceSegment = findSegment(track.times, midpoint);
504
+ if (sourceSegment >= track.times.length - 1) continue;
505
+ const sourceStartTime = track.times[sourceSegment]!;
506
+ const sourceEndTime = track.times[sourceSegment + 1]!;
507
+ if (endTime <= sourceStartTime + TIME_EPSILON || startTime >= sourceEndTime - TIME_EPSILON) continue;
508
+ const linearStart = clamp01((startTime - sourceStartTime) / (sourceEndTime - sourceStartTime));
509
+ const linearEnd = clamp01((endTime - sourceStartTime) / (sourceEndTime - sourceStartTime));
510
+ const progressStart = track.interpolation === 'cubic-bezier'
511
+ ? cubicBezierYForX(track.easings!, sourceSegment, linearStart)
512
+ : linearStart;
513
+ const progressEnd = track.interpolation === 'cubic-bezier'
514
+ ? cubicBezierYForX(track.easings!, sourceSegment, linearEnd)
515
+ : linearEnd;
516
+ const valueOffset = sourceSegment * 2;
517
+ const tangentOffset = sourceSegment * 4;
518
+ const delta = progressEnd - progressStart;
519
+ for (let component = 0; component < 2; component++) {
520
+ const start = track.values[valueOffset + component]!;
521
+ const end = track.values[valueOffset + 2 + component]!;
522
+ const control1 = start + track.spatialTangents![tangentOffset + component]!;
523
+ const control2 = end + track.spatialTangents![tangentOffset + 2 + component]!;
524
+ const derivativeStart = spatialDerivative(progressStart, start, control1, control2, end);
525
+ const derivativeEnd = spatialDerivative(progressEnd, start, control1, control2, end);
526
+ result[interval * 4 + component] = derivativeStart * delta / 3;
527
+ result[interval * 4 + 2 + component] = -derivativeEnd * delta / 3;
528
+ }
529
+ }
530
+ return result;
531
+ }
532
+
533
+ function spatialCoordinate(value: number, start: number, first: number, second: number, end: number): number {
534
+ const inverse = 1 - value;
535
+ return inverse * inverse * inverse * start
536
+ + 3 * inverse * inverse * value * first
537
+ + 3 * inverse * value * value * second
538
+ + value * value * value * end;
539
+ }
540
+
541
+ function spatialDerivative(value: number, start: number, first: number, second: number, end: number): number {
542
+ const inverse = 1 - value;
543
+ return 3 * inverse * inverse * (first - start)
544
+ + 6 * inverse * value * (second - first)
545
+ + 3 * value * value * (end - second);
546
+ }
547
+
548
+ function sliceEasings(track: ParsedAnimationTrack, globalTimes: readonly number[]): Float32Array {
549
+ const result = new Float32Array(Math.max(0, globalTimes.length - 1) * 4);
550
+ for (let interval = 0; interval < globalTimes.length - 1; interval++) {
551
+ const startTime = globalTimes[interval]!;
552
+ const endTime = globalTimes[interval + 1]!;
553
+ const firstSourceTime = track.times[0]!;
554
+ const lastSourceTime = track.times[track.times.length - 1]!;
555
+ if (track.times.length < 2
556
+ || endTime <= firstSourceTime + TIME_EPSILON
557
+ || startTime >= lastSourceTime - TIME_EPSILON) {
558
+ result.set([1 / 3, 1 / 3, 2 / 3, 2 / 3], interval * 4);
559
+ continue;
560
+ }
561
+ const sourceSegment = findSegment(track.times, (startTime + endTime) * 0.5);
562
+ const sourceStart = track.times[sourceSegment]!;
563
+ const sourceEnd = track.times[sourceSegment + 1]!;
564
+ const duration = sourceEnd - sourceStart;
565
+ const xStart = Math.min(1, Math.max(0, (startTime - sourceStart) / duration));
566
+ const xEnd = Math.min(1, Math.max(0, (endTime - sourceStart) / duration));
567
+ const easingOffset = sourceSegment * 4;
568
+ const subcurve = sliceCubicBezier(
569
+ track.easings![easingOffset]!,
570
+ track.easings![easingOffset + 1]!,
571
+ track.easings![easingOffset + 2]!,
572
+ track.easings![easingOffset + 3]!,
573
+ xStart,
574
+ xEnd,
575
+ track,
576
+ sourceSegment,
577
+ );
578
+ result.set(subcurve, interval * 4);
579
+ }
580
+ return result;
581
+ }
582
+
583
+ function sliceCubicBezier(
584
+ x1: number,
585
+ y1: number,
586
+ x2: number,
587
+ y2: number,
588
+ xStart: number,
589
+ xEnd: number,
590
+ track: ParsedAnimationTrack,
591
+ sourceSegment: number,
592
+ ): readonly [number, number, number, number] {
593
+ if (xStart <= TIME_EPSILON && xEnd >= 1 - TIME_EPSILON) return [x1, y1, x2, y2];
594
+ const t0 = solveCubicX(xStart, x1, x2);
595
+ const t1 = solveCubicX(xEnd, x1, x2);
596
+ const start = cubicPoint(t0, x1, y1, x2, y2);
597
+ const end = cubicPoint(t1, x1, y1, x2, y2);
598
+ const delta = t1 - t0;
599
+ const startDerivative = cubicPointDerivative(t0, x1, y1, x2, y2);
600
+ const endDerivative = cubicPointDerivative(t1, x1, y1, x2, y2);
601
+ const control1 = [start[0] + startDerivative[0] * delta / 3, start[1] + startDerivative[1] * delta / 3] as const;
602
+ const control2 = [end[0] - endDerivative[0] * delta / 3, end[1] - endDerivative[1] * delta / 3] as const;
603
+ const width = end[0] - start[0];
604
+ const height = end[1] - start[1];
605
+ if (Math.abs(height) <= 1e-8) {
606
+ if (sourceSegmentHasMotion(track, sourceSegment)) {
607
+ throw new RangeError(
608
+ `HYA cubic track "${track.node}.${track.property}" cannot be range-sliced at a zero-progress easing interval without losing fidelity.`,
609
+ );
610
+ }
611
+ return [1 / 3, 1 / 3, 2 / 3, 2 / 3];
612
+ }
613
+ return [
614
+ clamp01((control1[0] - start[0]) / width),
615
+ (control1[1] - start[1]) / height,
616
+ clamp01((control2[0] - start[0]) / width),
617
+ (control2[1] - start[1]) / height,
618
+ ];
619
+ }
620
+
621
+ function sourceSegmentHasMotion(track: ParsedAnimationTrack, segment: number): boolean {
622
+ const start = segment * track.valueSize;
623
+ const end = (segment + 1) * track.valueSize;
624
+ for (let component = 0; component < track.valueSize; component++) {
625
+ if (Math.abs(track.values[start + component]! - track.values[end + component]!) > 1e-8) return true;
626
+ }
627
+ return false;
628
+ }
629
+
630
+ function findSegment(times: Float32Array, time: number): number {
631
+ if (times.length <= 1 || time <= times[0]!) return 0;
632
+ if (time >= times[times.length - 1]!) return times.length - 1;
633
+ let low = 0;
634
+ let high = times.length - 1;
635
+ while (low + 1 < high) {
636
+ const middle = (low + high) >>> 1;
637
+ if (times[middle]! <= time) low = middle;
638
+ else high = middle;
639
+ }
640
+ return low;
641
+ }
642
+
643
+ function cubicBezierYForX(easings: Float32Array, segment: number, x: number): number {
644
+ const offset = segment * 4;
645
+ const x1 = easings[offset]!;
646
+ const y1 = easings[offset + 1]!;
647
+ const x2 = easings[offset + 2]!;
648
+ const y2 = easings[offset + 3]!;
649
+ return cubicCoordinate(solveCubicX(x, x1, x2), y1, y2);
650
+ }
651
+
652
+ function solveCubicX(x: number, x1: number, x2: number): number {
653
+ let parameter = x;
654
+ for (let iteration = 0; iteration < 6; iteration++) {
655
+ const estimate = cubicCoordinate(parameter, x1, x2) - x;
656
+ const derivative = cubicDerivative(parameter, x1, x2);
657
+ if (Math.abs(derivative) < 1e-7) break;
658
+ parameter = clamp01(parameter - estimate / derivative);
659
+ }
660
+ let low = 0;
661
+ let high = 1;
662
+ for (let iteration = 0; iteration < 18; iteration++) {
663
+ const estimate = cubicCoordinate(parameter, x1, x2);
664
+ if (Math.abs(estimate - x) < 1e-8) break;
665
+ if (estimate < x) low = parameter;
666
+ else high = parameter;
667
+ parameter = (low + high) * 0.5;
668
+ }
669
+ return parameter;
670
+ }
671
+
672
+ function cubicPoint(
673
+ value: number,
674
+ x1: number,
675
+ y1: number,
676
+ x2: number,
677
+ y2: number,
678
+ ): readonly [number, number] {
679
+ return [cubicCoordinate(value, x1, x2), cubicCoordinate(value, y1, y2)];
680
+ }
681
+
682
+ function cubicPointDerivative(
683
+ value: number,
684
+ x1: number,
685
+ y1: number,
686
+ x2: number,
687
+ y2: number,
688
+ ): readonly [number, number] {
689
+ return [cubicDerivative(value, x1, x2), cubicDerivative(value, y1, y2)];
690
+ }
691
+
692
+ function cubicCoordinate(value: number, first: number, second: number): number {
693
+ const inverse = 1 - value;
694
+ return 3 * inverse * inverse * value * first
695
+ + 3 * inverse * value * value * second
696
+ + value * value * value;
697
+ }
698
+
699
+ function cubicDerivative(value: number, first: number, second: number): number {
700
+ const inverse = 1 - value;
701
+ return 3 * inverse * inverse * first
702
+ + 6 * inverse * value * (second - first)
703
+ + 3 * value * value * (1 - second);
704
+ }
705
+
706
+ function assertChannelCompatibleDocument(
707
+ animation: ParsedAnimation,
708
+ extension: HyaStateMachineExtension,
709
+ ): void {
710
+ let hasAudio = false;
711
+ for (let nodeIndex = 0; nodeIndex < animation.nodes.length; nodeIndex++) {
712
+ const node = animation.nodes[nodeIndex]!;
713
+ const components = node.components ?? [];
714
+ for (let componentIndex = 0; componentIndex < components.length; componentIndex++) {
715
+ const component = components[componentIndex]!;
716
+ const path = `$.nodes[${nodeIndex}].components[${componentIndex}]`;
717
+ if (component.type === 'audio') hasAudio = true;
718
+ const unsupported = unsupportedInlineChannel(component, path);
719
+ if (unsupported) throw new AnimationStateMachineChannelError(unsupported);
720
+ }
721
+ for (let effectIndex = 0; effectIndex < (node.effects?.length ?? 0); effectIndex++) {
722
+ const effect = node.effects![effectIndex]! as unknown as Readonly<Record<string, unknown>>;
723
+ if (Object.keys(effect).some(key => key.endsWith('Track'))) {
724
+ throw new AnimationStateMachineChannelError(unsupportedDiagnostic(
725
+ 'visual-effect',
726
+ `$.nodes[${nodeIndex}].effects[${effectIndex}]`,
727
+ 'Animated visual-effect properties are not yet writable from the shared pose buffer.',
728
+ ));
729
+ }
730
+ }
731
+ const composites = node.composite
732
+ ? ('layers' in node.composite ? node.composite.layers : [node.composite])
733
+ : [];
734
+ for (let compositeIndex = 0; compositeIndex < composites.length; compositeIndex++) {
735
+ if (composites[compositeIndex]!.expansionTrack) {
736
+ throw new AnimationStateMachineChannelError(unsupportedDiagnostic(
737
+ 'composite-expansion',
738
+ `$.nodes[${nodeIndex}].composite.layers[${compositeIndex}].expansionTrack`,
739
+ 'Animated composite expansion is not yet writable from the shared pose buffer.',
740
+ ));
741
+ }
742
+ }
743
+ }
744
+ if (hasAudio) {
745
+ assertAudioStateMachineCompatible(
746
+ extension.stateMachine,
747
+ `$.extensions.${HYA_STATE_MACHINE_EXTENSION_ID}.stateMachine`,
748
+ );
749
+ }
750
+ }
751
+
752
+ function unsupportedInlineChannel(
753
+ component: Readonly<AnimationComponent>,
754
+ path: string,
755
+ ): AnimationStateMachineChannelDiagnostic | null {
756
+ if (component.type === 'shape2d'
757
+ || component.type === 'path2d'
758
+ || component.type === 'sprite2d'
759
+ || component.type === 'particle2d'
760
+ || component.type === 'audio'
761
+ || component.type === 'org.haiyue.vector-stroke@1'
762
+ || component.type === 'org.haiyue.vector-path-morph@1') return null;
763
+
764
+ if (component.type === ANIMATION_VECTOR_SHAPE_EXTENSION_ID) {
765
+ const vector = component as Readonly<AnimationVectorShapeComponent>;
766
+ if (vectorHasUnsupportedInlineTrack(vector)) {
767
+ return unsupportedDiagnostic(
768
+ 'vector-paint',
769
+ path,
770
+ 'Vector paint and modifier tracks are not yet writable from the shared pose buffer; vector morph is supported.',
771
+ );
772
+ }
773
+ return null;
774
+ }
775
+ if (component.type === 'text2d') {
776
+ const text = component as Readonly<Record<string, unknown>>;
777
+ const documents = text.documents as readonly unknown[] | undefined;
778
+ const animators = text.animators as readonly Readonly<Record<string, unknown>>[] | undefined;
779
+ if ((documents?.length ?? 0) > 1 || animators?.some(recordHasNestedTrack)) {
780
+ return unsupportedDiagnostic(
781
+ 'text-animator',
782
+ path,
783
+ 'Animated text documents and animator tracks are not yet writable from the shared pose buffer.',
784
+ );
785
+ }
786
+ return null;
787
+ }
788
+ return unsupportedDiagnostic(
789
+ 'visual-effect',
790
+ path,
791
+ `Component "${component.type}" has no registered state-machine channel strategy.`,
792
+ );
793
+ }
794
+
795
+ function vectorHasUnsupportedInlineTrack(vector: Readonly<AnimationVectorShapeComponent>): boolean {
796
+ const fill = vector.fill;
797
+ if (fill) {
798
+ if (fill.kind === 'solid') {
799
+ if (fill.colorTrack || fill.opacityTrack) return true;
800
+ } else if (fill.startTrack || fill.endTrack || fill.stopsTrack || fill.opacityTrack) return true;
801
+ }
802
+ const stroke = vector.stroke;
803
+ if (stroke && (stroke.colorTrack || stroke.opacityTrack || stroke.widthTrack || stroke.dashOffsetTrack
804
+ || (stroke.gradient && (stroke.gradient.startTrack || stroke.gradient.endTrack
805
+ || stroke.gradient.stopsTrack || stroke.gradient.opacityTrack)))) return true;
806
+ return vector.modifiers?.some(modifier => modifier.kind === 'round-corners'
807
+ ? modifier.radiusTrack !== undefined
808
+ : modifier.startTrack !== undefined || modifier.endTrack !== undefined
809
+ || modifier.offsetTrack !== undefined) ?? false;
810
+ }
811
+
812
+ function recordHasNestedTrack(record: Readonly<Record<string, unknown>>): boolean {
813
+ if (Object.keys(record).some(key => key.endsWith('Track'))) return true;
814
+ return Object.values(record).some(value => value !== null && typeof value === 'object'
815
+ && !Array.isArray(value)
816
+ && recordHasNestedTrack(value as Readonly<Record<string, unknown>>));
817
+ }
818
+
819
+ function unsupportedDiagnostic(
820
+ channelId: 'vector-paint' | 'text-animator' | 'visual-effect' | 'composite-expansion',
821
+ path: string,
822
+ message: string,
823
+ ): AnimationStateMachineChannelDiagnostic {
824
+ return Object.freeze({
825
+ code: hyaStateMachineChannelCapability(channelId).diagnosticCode!,
826
+ severity: 'error',
827
+ channelId,
828
+ path,
829
+ message,
830
+ });
831
+ }
832
+
833
+ function assertStrictFloatTimes(times: Float32Array, trackId: string): void {
834
+ for (let index = 1; index < times.length; index++) {
835
+ if (times[index]! <= times[index - 1]!) {
836
+ throw new RangeError(
837
+ `HYA state-machine clip "${trackId}" has time keys that collapse after Float32 conversion.`,
838
+ );
839
+ }
840
+ }
841
+ }
842
+
843
+ function clamp01(value: number): number {
844
+ return Math.min(1, Math.max(0, value));
845
+ }