@haiyue/extensions 0.1.0 → 0.2.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 (269) hide show
  1. package/README.md +25 -0
  2. package/dist/animation/Animation2DComponent.d.ts +18 -0
  3. package/dist/animation/Animation2DExtensionRegistry.d.ts +7 -0
  4. package/dist/animation/Animation2DRenderSystem.d.ts +6 -0
  5. package/dist/animation/Animation2DRuntime.d.ts +6 -2
  6. package/dist/animation/AnimationTextRasterizer.d.ts +4 -0
  7. package/dist/animation/AnimationVisual2D.d.ts +58 -2
  8. package/dist/animation/interaction/InteractionRuntime.d.ts +83 -0
  9. package/dist/animation/interaction/runtime-types.d.ts +257 -0
  10. package/dist/animation.d.ts +3 -1
  11. package/dist/animation.js +551 -59
  12. package/dist/animation3d.js +1 -1
  13. package/dist/canvas-text.js +1 -1
  14. package/dist/chunks/2d-ui-animation-2d.generated-FpziAeGJ.js +4 -0
  15. package/dist/chunks/2d-ui-indexed-sprite.generated-DmKR0CC3.js +4 -0
  16. package/dist/chunks/Animation2DStateMachine-BHcA6Cu3.js +18212 -0
  17. package/dist/chunks/{AnimationStateMachineController-BIcAh-sT.js → AnimationStateMachineController-4sKaqh9E.js} +2 -2
  18. package/dist/chunks/AnimationVisual2D-8y3qp0_U.js +230 -0
  19. package/dist/chunks/{RenderSystem2DBase-DDT61dAu.js → RenderSystem2DBase-CF2s00vg.js} +2 -2
  20. package/dist/controls/VirtualJoystickControls.d.ts +46 -0
  21. package/dist/controls/contracts.d.ts +91 -0
  22. package/dist/controls.d.ts +2 -0
  23. package/dist/controls.js +358 -0
  24. package/dist/deformable-animation/runtime/DeformableMesh2DRuntimeExtension.d.ts +11 -0
  25. package/dist/deformable-animation/runtime/DeformableMesh2DSampler.d.ts +11 -0
  26. package/dist/deformable-animation.d.ts +5 -0
  27. package/dist/deformable-animation.js +316 -0
  28. package/dist/experimental-indexed-sprite.d.ts +5 -0
  29. package/dist/experimental-indexed-sprite.js +482 -0
  30. package/dist/gltf-worker-runtime.js +9 -9
  31. package/dist/hya-state-machine.js +3 -2
  32. package/dist/index.js +1 -1
  33. package/dist/indexed-sprite/AtlasLayout.d.ts +2 -0
  34. package/dist/indexed-sprite/IndexedSpriteRenderer.d.ts +23 -0
  35. package/dist/indexed-sprite/contracts.d.ts +89 -0
  36. package/dist/internal/2d-ui-shader-artifact.js +232 -23
  37. package/dist/ray-tracing/acceleration/bvh.d.ts +37 -0
  38. package/dist/ray-tracing/acceleration/index.d.ts +6 -0
  39. package/dist/ray-tracing/acceleration/pack.d.ts +152 -0
  40. package/dist/ray-tracing/acceleration/query.d.ts +4 -0
  41. package/dist/ray-tracing/acceleration/runtime.d.ts +30 -0
  42. package/dist/ray-tracing/acceleration/tlas.d.ts +4 -0
  43. package/dist/ray-tracing/acceleration/types.d.ts +165 -0
  44. package/dist/ray-tracing/denoise/index.d.ts +3 -0
  45. package/dist/ray-tracing/denoise/layout.d.ts +9 -0
  46. package/dist/ray-tracing/denoise/runtime.d.ts +23 -0
  47. package/dist/ray-tracing/denoise/types.d.ts +28 -0
  48. package/dist/ray-tracing/hybrid/contract.d.ts +2 -0
  49. package/dist/ray-tracing/hybrid/index.d.ts +5 -0
  50. package/dist/ray-tracing/hybrid/layout.d.ts +17 -0
  51. package/dist/ray-tracing/hybrid/plan.d.ts +2 -0
  52. package/dist/ray-tracing/hybrid/runtime.d.ts +31 -0
  53. package/dist/ray-tracing/hybrid/shaders/hybrid-artifact.generated.d.ts +625 -0
  54. package/dist/ray-tracing/hybrid/types.d.ts +144 -0
  55. package/dist/ray-tracing/lifecycle/device-recovery.d.ts +31 -0
  56. package/dist/ray-tracing/lifecycle/index.d.ts +2 -0
  57. package/dist/ray-tracing/lifecycle/types.d.ts +37 -0
  58. package/dist/ray-tracing/material/index.d.ts +2 -0
  59. package/dist/ray-tracing/material/pack.d.ts +5 -0
  60. package/dist/ray-tracing/material/types.d.ts +63 -0
  61. package/dist/ray-tracing/reference/index.d.ts +122 -0
  62. package/dist/ray-tracing/renderer/index.d.ts +7 -0
  63. package/dist/ray-tracing/renderer/layout.d.ts +20 -0
  64. package/dist/ray-tracing/renderer/plan.d.ts +2 -0
  65. package/dist/ray-tracing/renderer/reference.d.ts +17 -0
  66. package/dist/ray-tracing/renderer/runtime.d.ts +39 -0
  67. package/dist/ray-tracing/renderer/scene.d.ts +4 -0
  68. package/dist/ray-tracing/renderer/types.d.ts +115 -0
  69. package/dist/ray-tracing/sampling/index.d.ts +8 -0
  70. package/dist/ray-tracing/sampling/layout.d.ts +12 -0
  71. package/dist/ray-tracing/sampling/plan.d.ts +5 -0
  72. package/dist/ray-tracing/sampling/revision.d.ts +15 -0
  73. package/dist/ray-tracing/sampling/runtime.d.ts +44 -0
  74. package/dist/ray-tracing/sampling/sequence.d.ts +2 -0
  75. package/dist/ray-tracing/sampling/types.d.ts +96 -0
  76. package/dist/ray-tracing/scene/index.d.ts +41 -0
  77. package/dist/ray-tracing/traversal/index.d.ts +4 -0
  78. package/dist/ray-tracing/traversal/layout.d.ts +16 -0
  79. package/dist/ray-tracing/traversal/plan.d.ts +2 -0
  80. package/dist/ray-tracing/traversal/runtime.d.ts +25 -0
  81. package/dist/ray-tracing/traversal/types.d.ts +79 -0
  82. package/dist/ray-tracing/worker/client.d.ts +25 -0
  83. package/dist/ray-tracing/worker/index.d.ts +5 -0
  84. package/dist/ray-tracing/worker/runtime.d.ts +11 -0
  85. package/dist/ray-tracing/worker/serialization.d.ts +16 -0
  86. package/dist/ray-tracing/worker/source.d.ts +1 -0
  87. package/dist/ray-tracing/worker/types.d.ts +91 -0
  88. package/dist/ray-tracing.d.ts +17 -0
  89. package/dist/ray-tracing.js +7666 -0
  90. package/dist/spine.js +1 -1
  91. package/dist/tilemap.js +1 -1
  92. package/package.json +26 -23
  93. package/dist/animation/Animation2DStateMachineVisualRuntime.d.ts +0 -27
  94. package/dist/animation/Particle2DDescriptorAdapter.d.ts +0 -5
  95. package/dist/animation3d/Animation3DResource.d.ts +0 -28
  96. package/dist/animation3d/hya/HyaAnimation3DStateMachineRuntime.d.ts +0 -65
  97. package/dist/animation3d/hya/index.d.ts +0 -4
  98. package/dist/animation3d/runtime/integration/Animation3DStateMachineMixerAdapter.d.ts +0 -81
  99. package/dist/animation3d/runtime/integration/Animation3DStateMachineMixerIntegration.d.ts +0 -39
  100. package/dist/animation3d/runtime/integration/index.d.ts +0 -2
  101. package/dist/animation3d/runtime/mixer/Action.d.ts +0 -150
  102. package/dist/animation3d/runtime/mixer/Animation3DMixerRuntimeStore.d.ts +0 -6
  103. package/dist/animation3d/runtime/mixer/Mixer.d.ts +0 -59
  104. package/dist/animation3d/runtime/mixer/PoseBuffer.d.ts +0 -1
  105. package/dist/animation3d/runtime/mixer/TrackSampler.d.ts +0 -24
  106. package/dist/animation3d/runtime/mixer/index.d.ts +0 -4
  107. package/dist/animation3d/runtime/state-machine/Animation3DBlendTreeWeights.d.ts +0 -1
  108. package/dist/animation3d/runtime/state-machine/Animation3DStateMachineCompiler.d.ts +0 -1
  109. package/dist/animation3d/runtime/state-machine/Animation3DStateMachineController.d.ts +0 -1
  110. package/dist/animation3d/runtime/state-machine/Animation3DStateMachineMixerPort.d.ts +0 -1
  111. package/dist/animation3d/runtime/state-machine/index.d.ts +0 -6
  112. package/dist/benchmark.d.ts +0 -30
  113. package/dist/chunks/2d-ui-animation-2d.generated-D4aHzFU2.js +0 -4
  114. package/dist/chunks/Animation2DStateMachine-9IonguNa.js +0 -3245
  115. package/dist/gltf/GltfAccessorReader.d.ts +0 -7
  116. package/dist/gltf/GltfDracoDecoder.d.ts +0 -4
  117. package/dist/gltf/GltfLoaderErrors.d.ts +0 -4
  118. package/dist/gltf/GltfMaterialDescriptor.d.ts +0 -23
  119. package/dist/gltf/GltfMaterialLoader.d.ts +0 -6
  120. package/dist/gltf-worker-runtime.d.ts +0 -7
  121. package/dist/plugins/pluginUtils.d.ts +0 -20
  122. package/dist/plugins.d.ts +0 -6
  123. package/dist/shaders/generated/2d-ui-artifact.generated.d.ts +0 -658
  124. package/dist/spine/Spine2DGpuRenderer.d.ts +0 -65
  125. package/dist/spine/Spine2DRuntime.d.ts +0 -103
  126. package/dist/spine/SpineAnimationBoneTimelines.d.ts +0 -8
  127. package/dist/spine/SpineFloatBuilder.d.ts +0 -15
  128. package/dist/spine/SpinePathConstraintSolver.d.ts +0 -32
  129. package/dist/spine/SpineSkeletonRuntime.d.ts +0 -185
  130. package/dist/spine/SpineSlotAnimation.d.ts +0 -17
  131. package/dist/spine/SpineTimelineRuntime.d.ts +0 -25
  132. package/dist/spine/SpineVertexBuilder.d.ts +0 -61
  133. package/dist/tilemap/Tilemap2DRenderer.d.ts +0 -24
  134. package/dist/utils/arrayAccess.d.ts +0 -4
  135. package/dist/utils/camera2d.d.ts +0 -12
  136. package/src/animation/Animation2DComponent.ts +0 -135
  137. package/src/animation/Animation2DExtensionRegistry.ts +0 -52
  138. package/src/animation/Animation2DRenderSystem.ts +0 -1026
  139. package/src/animation/Animation2DRuntime.ts +0 -1326
  140. package/src/animation/Animation2DStateMachine.ts +0 -39
  141. package/src/animation/Animation2DStateMachineComponent.ts +0 -228
  142. package/src/animation/Animation2DStateMachineRuntime.ts +0 -141
  143. package/src/animation/Animation2DStateMachineSystem.ts +0 -60
  144. package/src/animation/Animation2DStateMachineVisualRuntime.ts +0 -282
  145. package/src/animation/Animation2DSystem.ts +0 -72
  146. package/src/animation/AnimationAssetLoader.ts +0 -84
  147. package/src/animation/AnimationAudioClip.ts +0 -140
  148. package/src/animation/AnimationPathTessellator.ts +0 -691
  149. package/src/animation/AnimationTextRasterizer.ts +0 -340
  150. package/src/animation/AnimationVisual2D.ts +0 -153
  151. package/src/animation/HyaAnimation2DClipAdapter.ts +0 -845
  152. package/src/animation/Particle2DDescriptorAdapter.ts +0 -35
  153. package/src/animation/runtime/mixer/Action.ts +0 -876
  154. package/src/animation/runtime/mixer/Mixer.ts +0 -505
  155. package/src/animation/runtime/mixer/PoseBuffer.ts +0 -105
  156. package/src/animation/runtime/mixer/Sampler.ts +0 -252
  157. package/src/animation/runtime/mixer/Types.ts +0 -138
  158. package/src/animation/runtime/mixer/index.ts +0 -31
  159. package/src/animation-state-machine/AnimationStateMachine.ts +0 -146
  160. package/src/animation-state-machine/AnimationStateMachineChannels.ts +0 -116
  161. package/src/animation-state-machine/runtime/Animation2DStateMachineMixerAdapter.ts +0 -197
  162. package/src/animation-state-machine/runtime/AnimationStateMachineBlendTreeWeights.ts +0 -503
  163. package/src/animation-state-machine/runtime/AnimationStateMachineCompiler.ts +0 -763
  164. package/src/animation-state-machine/runtime/AnimationStateMachineController.ts +0 -987
  165. package/src/animation-state-machine/runtime/AnimationStateMachineMixerPort.ts +0 -54
  166. package/src/animation-state-machine/runtime/index.ts +0 -47
  167. package/src/animation.ts +0 -33
  168. package/src/animation3d/Animation3DAction.ts +0 -73
  169. package/src/animation3d/Animation3DBinding.ts +0 -116
  170. package/src/animation3d/Animation3DClip.ts +0 -22
  171. package/src/animation3d/Animation3DError.ts +0 -34
  172. package/src/animation3d/Animation3DMixer.ts +0 -82
  173. package/src/animation3d/Animation3DPose.ts +0 -100
  174. package/src/animation3d/Animation3DPoseBuffer.ts +0 -85
  175. package/src/animation3d/Animation3DResource.ts +0 -49
  176. package/src/animation3d/Animation3DStateMachine.ts +0 -126
  177. package/src/animation3d/Animation3DStateMachineRuntime.ts +0 -191
  178. package/src/animation3d/Animation3DTrack.ts +0 -29
  179. package/src/animation3d/hya/HyaAnimation3DRuntime.ts +0 -756
  180. package/src/animation3d/hya/HyaAnimation3DStateMachineRuntime.ts +0 -618
  181. package/src/animation3d/hya/HyaAnimation3DTypes.ts +0 -168
  182. package/src/animation3d/hya/index.ts +0 -10
  183. package/src/animation3d/index.ts +0 -76
  184. package/src/animation3d/runtime/integration/Animation3DStateMachineMixerAdapter.ts +0 -325
  185. package/src/animation3d/runtime/integration/Animation3DStateMachineMixerIntegration.ts +0 -158
  186. package/src/animation3d/runtime/integration/index.ts +0 -9
  187. package/src/animation3d/runtime/mixer/Action.ts +0 -833
  188. package/src/animation3d/runtime/mixer/Animation3DMixerRuntimeStore.ts +0 -26
  189. package/src/animation3d/runtime/mixer/Mixer.ts +0 -562
  190. package/src/animation3d/runtime/mixer/PoseBuffer.ts +0 -1
  191. package/src/animation3d/runtime/mixer/TrackSampler.ts +0 -399
  192. package/src/animation3d/runtime/mixer/index.ts +0 -8
  193. package/src/animation3d/runtime/state-machine/Animation3DBlendTreeWeights.ts +0 -4
  194. package/src/animation3d/runtime/state-machine/Animation3DStateMachineCompiler.ts +0 -1
  195. package/src/animation3d/runtime/state-machine/Animation3DStateMachineController.ts +0 -1
  196. package/src/animation3d/runtime/state-machine/Animation3DStateMachineMixerPort.ts +0 -1
  197. package/src/animation3d/runtime/state-machine/index.ts +0 -45
  198. package/src/animation3d.ts +0 -5
  199. package/src/benchmark.ts +0 -168
  200. package/src/canvas-text/CanvasText2DRenderSystem.ts +0 -292
  201. package/src/canvas-text/CanvasTextComponent.ts +0 -47
  202. package/src/canvas-text.ts +0 -4
  203. package/src/experimental-gltf-worker.ts +0 -18
  204. package/src/experimental-spine-worker.ts +0 -9
  205. package/src/gltf/GltfAccessorReader.ts +0 -271
  206. package/src/gltf/GltfAnimation3DAdapter.ts +0 -680
  207. package/src/gltf/GltfAnimationRuntime.ts +0 -392
  208. package/src/gltf/GltfAssetWorkerClient.ts +0 -136
  209. package/src/gltf/GltfCompatibilityReport.ts +0 -214
  210. package/src/gltf/GltfConservativeBounds.ts +0 -219
  211. package/src/gltf/GltfDracoDecoder.ts +0 -251
  212. package/src/gltf/GltfExtensionAdapter.ts +0 -340
  213. package/src/gltf/GltfLoaderContract.ts +0 -333
  214. package/src/gltf/GltfLoaderErrors.ts +0 -59
  215. package/src/gltf/GltfMaterialDescriptor.ts +0 -134
  216. package/src/gltf/GltfMaterialEncoder.ts +0 -173
  217. package/src/gltf/GltfMaterialLoader.ts +0 -293
  218. package/src/gltf/GltfModelComponent.ts +0 -115
  219. package/src/gltf/GltfModelSystem.ts +0 -208
  220. package/src/gltf/GltfPlugin.ts +0 -84
  221. package/src/gltf/GltfSchema.ts +0 -229
  222. package/src/gltf/GltfUvSemanticPlanner.ts +0 -166
  223. package/src/gltf/gltfLoader.ts +0 -910
  224. package/src/gltf-animation3d.ts +0 -10
  225. package/src/gltf-worker-runtime.ts +0 -10
  226. package/src/gltf.ts +0 -56
  227. package/src/grid/Grid2DComponent.ts +0 -69
  228. package/src/grid.ts +0 -2
  229. package/src/hya-state-machine.ts +0 -19
  230. package/src/index.ts +0 -2
  231. package/src/plugins/pluginUtils.ts +0 -50
  232. package/src/plugins.ts +0 -6
  233. package/src/shaders/generated/2d-ui-animation-2d.generated.wgsl +0 -242
  234. package/src/shaders/generated/2d-ui-artifact.generated.ts +0 -867
  235. package/src/shaders/generated/2d-ui-canvas-text-2d.generated.wgsl +0 -38
  236. package/src/shaders/generated/2d-ui-spine2d.generated.wgsl +0 -41
  237. package/src/shaders/generated/2d-ui-tilemap2d.generated.wgsl +0 -36
  238. package/src/spine/Spine2DComponent.ts +0 -168
  239. package/src/spine/Spine2DGpuRenderer.ts +0 -449
  240. package/src/spine/Spine2DRenderSystem.ts +0 -199
  241. package/src/spine/Spine2DRuntime.ts +0 -1529
  242. package/src/spine/SpineAnimationBoneTimelines.ts +0 -30
  243. package/src/spine/SpineAssetParser.ts +0 -20
  244. package/src/spine/SpineAssetWorkerClient.ts +0 -112
  245. package/src/spine/SpineAssetWorkerContract.ts +0 -4
  246. package/src/spine/SpineAtlasParser.ts +0 -127
  247. package/src/spine/SpineFloatBuilder.ts +0 -66
  248. package/src/spine/SpinePathConstraintSolver.ts +0 -582
  249. package/src/spine/SpinePlugin.ts +0 -76
  250. package/src/spine/SpineSkeletonRuntime.ts +0 -521
  251. package/src/spine/SpineSlotAnimation.ts +0 -195
  252. package/src/spine/SpineTimelineRuntime.ts +0 -453
  253. package/src/spine/SpineVertexBuilder.ts +0 -686
  254. package/src/spine.ts +0 -7
  255. package/src/tilemap/Tilemap2DComponent.ts +0 -232
  256. package/src/tilemap/Tilemap2DRenderSystem.ts +0 -58
  257. package/src/tilemap/Tilemap2DRenderer.ts +0 -289
  258. package/src/tilemap/TilemapPlugin.ts +0 -75
  259. package/src/tilemap.ts +0 -6
  260. package/src/tween/Tween2DComponent.ts +0 -83
  261. package/src/tween/Tween2DSystem.ts +0 -78
  262. package/src/tween.ts +0 -4
  263. package/src/types/earcut.d.ts +0 -7
  264. package/src/types/wgsl.d.ts +0 -4
  265. package/src/utils/RenderSystem2DBase.ts +0 -164
  266. package/src/utils/arrayAccess.ts +0 -17
  267. package/src/utils/camera2d.ts +0 -68
  268. package/src/utils/render2dGpu.ts +0 -132
  269. package/test/fixtures/gltf/animation-characterization.gltf +0 -344
@@ -1,1326 +0,0 @@
1
- import {
2
- ANIMATION_VECTOR_SHAPE_EXTENSION_ID,
3
- AnimationFormatError,
4
- HYA_STATE_MACHINE_EXTENSION_ID,
5
- type AnimationAudioComponent,
6
- type AnimationComponent,
7
- type AnimationComposite,
8
- type AnimationCompositeLayer,
9
- type AnimationLayerEffect,
10
- type AnimationNode,
11
- type AnimationPath2DComponent,
12
- type AnimationParticle2DComponent,
13
- type AnimationShape2DComponent,
14
- type AnimationSprite2DComponent,
15
- type AnimationText2DComponent,
16
- type AnimationVectorGradientPaint,
17
- type AnimationVectorShapeComponent,
18
- type AnimationVectorValueTrack,
19
- type ParsedAnimation,
20
- type ParsedAnimationTrack,
21
- } from '@haiyue/animation-spec';
22
- import { Entity, Geometry2D, Transform2D } from '@haiyue/engine';
23
- import { ParticleEmitter2D } from '@haiyue/engine/components';
24
- import { createRect2D } from '@haiyue/engine/geometry';
25
- import type { AssetHandle, AssetManager } from '@haiyue/engine/assets';
26
- import type { Animation2DRuntimeStats } from './Animation2DComponent.js';
27
- import type { Animation2DExtensionInstance, Animation2DExtensionRegistry } from './Animation2DExtensionRegistry.js';
28
- import {
29
- applyVectorPathModifiers,
30
- tessellateAnimationPath,
31
- tessellateVectorStrokePath,
32
- sampleVectorPathMorph,
33
- type VectorPathMorphComponent,
34
- type VectorStrokePathComponent,
35
- } from './AnimationPathTessellator.js';
36
- import { AnimationVisual2D } from './AnimationVisual2D.js';
37
- import { AnimationTextRasterizer } from './AnimationTextRasterizer.js';
38
- import { AnimationAudioClip } from './AnimationAudioClip.js';
39
- import { createParticle2DEmitter } from './Particle2DDescriptorAdapter.js';
40
-
41
- export interface Animation2DRuntimeVisual {
42
- readonly component: AnimationVisual2D;
43
- readonly sourceComponentIndex: number;
44
- readonly color: [number, number, number, number];
45
- readonly baseAlpha: number;
46
- lastAlpha: number;
47
- readonly pathMorph?: VectorPathMorphComponent;
48
- lastMorphTime?: number;
49
- readonly vectorShape?: AnimationVectorShapeComponent;
50
- readonly vectorPaint?: 'fill' | 'stroke';
51
- styleAlpha: number;
52
- lastVectorTime?: number;
53
- readonly textRasterizer?: AnimationTextRasterizer;
54
- readonly sourceEffects?: readonly Readonly<AnimationLayerEffect>[];
55
- lastEffectTime?: number;
56
- readonly compositeExpansionTracks?: readonly (Readonly<AnimationVectorValueTrack> | undefined)[];
57
- lastCompositeTime?: number;
58
- readonly spriteUvRectTrack?: AnimationVectorValueTrack;
59
- spriteUvRectFrame?: number;
60
- }
61
-
62
- type CoreVisualComponent =
63
- | AnimationShape2DComponent
64
- | AnimationPath2DComponent
65
- | AnimationSprite2DComponent
66
- | AnimationText2DComponent
67
- | AnimationVectorShapeComponent
68
- | VectorPathMorphComponent
69
- | VectorStrokePathComponent;
70
-
71
- interface DeferredCoreVisual {
72
- readonly component: CoreVisualComponent;
73
- readonly componentIndex: number;
74
- readonly order: number;
75
- readonly sourceGroup: string | undefined;
76
- readonly composite: Readonly<AnimationComposite> | undefined;
77
- readonly effects: readonly Readonly<AnimationLayerEffect>[] | undefined;
78
- }
79
-
80
- let nextRuntimeInstanceId = 1;
81
-
82
- export interface Animation2DRuntimeNode {
83
- readonly source: Readonly<AnimationNode>;
84
- readonly entity: Entity;
85
- readonly content: Entity;
86
- readonly transform: Transform2D;
87
- readonly visuals: Animation2DRuntimeVisual[];
88
- readonly deferredVisuals: DeferredCoreVisual[];
89
- readonly extensions: Animation2DExtensionInstance[];
90
- readonly particles: ParticleEmitter2D[];
91
- readonly audio: AnimationAudioClip[];
92
- readonly initialX: number;
93
- readonly initialY: number;
94
- readonly initialRotation: number;
95
- readonly initialScaleX: number;
96
- readonly initialScaleY: number;
97
- readonly initialOpacity: number;
98
- opacity: number;
99
- }
100
-
101
- export class Animation2DRuntime {
102
- private readonly _root: Entity;
103
- protected readonly _nodes: Animation2DRuntimeNode[] = [];
104
- protected readonly _nodesById = new Map<string, Animation2DRuntimeNode>();
105
- private readonly _tracksByNode = new Map<string, ParsedAnimationTrack[]>();
106
- private readonly _trackCursors: Int32Array;
107
- private readonly _trackIndices = new Map<ParsedAnimationTrack, number>();
108
- protected readonly _opacityMemo = new Map<string, number>();
109
- private readonly _abortController = new AbortController();
110
- private readonly _assetHandles: AssetHandle<unknown>[] = [];
111
- private readonly _fontLoads = new Map<string, {
112
- state: 'loading' | 'loaded' | 'failed';
113
- readonly rasterizers: Set<AnimationTextRasterizer>;
114
- }>();
115
- private readonly _fontFaces: FontFace[] = [];
116
- private readonly _instanceId = nextRuntimeInstanceId++;
117
- private _visualCount = 0;
118
- private _unsupportedComponentCount = 0;
119
- private _pendingResourceCount = 0;
120
- private _failedResourceCount = 0;
121
- private _textCount = 0;
122
- private _particleCount = 0;
123
- private _audioCount = 0;
124
- private _destroyed = false;
125
- protected _playing = false;
126
- protected _speed = 1;
127
- protected _lastAppliedTime = Number.NaN;
128
-
129
- get stats(): Animation2DRuntimeStats {
130
- return Object.freeze({
131
- nodeCount: this._nodes.length,
132
- visualCount: this._visualCount,
133
- unsupportedComponentCount: this._unsupportedComponentCount,
134
- pendingResourceCount: this._pendingResourceCount,
135
- failedResourceCount: this._failedResourceCount,
136
- textCount: this._textCount,
137
- particleCount: this._particleCount,
138
- audioCount: this._audioCount,
139
- });
140
- }
141
-
142
- constructor(
143
- owner: Entity,
144
- private readonly _animation: ParsedAnimation,
145
- runtimeExtensions?: Animation2DExtensionRegistry,
146
- private readonly _assetManager?: AssetManager,
147
- ) {
148
- for (const id of _animation.extensionsRequired) {
149
- if (id === HYA_STATE_MACHINE_EXTENSION_ID) continue;
150
- if (!runtimeExtensions?.has(id)) {
151
- throw new AnimationFormatError(
152
- 'E_ANIMATION_MISSING_EXTENSION',
153
- `Required animation runtime extension "${id}" is not registered.`,
154
- '$.extensionsRequired',
155
- );
156
- }
157
- }
158
- this._root = new Entity(`${_animation.name ?? 'Animation'} runtime`);
159
- this._root.addComponent(new Transform2D({ x: -_animation.canvas.width / 2, y: _animation.canvas.height / 2 }));
160
- owner.addChild(this._root);
161
-
162
- const compositeSources = new Set(_animation.nodes.flatMap(node => compositeLayers(node.composite).map(layer => layer.source)));
163
- const sourceGroupByNode = resolveSourceGroups(_animation.nodes, compositeSources);
164
- const compositeByNode = resolveInheritedComposites(_animation.nodes, sourceGroupByNode);
165
- const effectsByNode = resolveInheritedEffects(_animation.nodes, sourceGroupByNode);
166
- const sourceNodesById = new Map(_animation.nodes.map(node => [node.id, node]));
167
- let renderOrder = 0;
168
- for (const node of _animation.nodes) {
169
- const transformSource = node.transform;
170
- const position = transformSource?.position ?? [0, 0];
171
- const scale = transformSource?.scale ?? [1, 1];
172
- const transform = new Transform2D({
173
- x: position[0],
174
- y: -position[1],
175
- rotation: -(transformSource?.rotation ?? 0),
176
- scaleX: scale[0],
177
- scaleY: scale[1],
178
- });
179
- const entity = new Entity(node.name ?? node.id).addComponent(transform);
180
- const anchor = transformSource?.anchor ?? [0, 0];
181
- const content = new Entity(`${node.name ?? node.id} anchor`).addComponent(new Transform2D({
182
- x: -anchor[0],
183
- y: anchor[1],
184
- }));
185
- entity.addChild(content);
186
- const runtimeNode: Animation2DRuntimeNode = {
187
- source: node,
188
- entity,
189
- content,
190
- transform,
191
- visuals: [],
192
- deferredVisuals: [],
193
- extensions: [],
194
- particles: [],
195
- audio: [],
196
- initialX: transform.x,
197
- initialY: transform.y,
198
- initialRotation: transform.rotation,
199
- initialScaleX: transform.scaleX,
200
- initialScaleY: transform.scaleY,
201
- initialOpacity: transformSource?.opacity ?? 1,
202
- opacity: transformSource?.opacity ?? 1,
203
- };
204
- const sourceComponents = node.components ?? [];
205
- for (let componentIndex = 0; componentIndex < sourceComponents.length; componentIndex++) {
206
- const component = sourceComponents[componentIndex]!;
207
- if (isParticle2DComponent(component)) {
208
- // Particle2D is rendered by the engine's independent pass. Do not silently
209
- // bypass animation alpha-composite semantics until both renderers share a graph item protocol.
210
- if (sourceGroupByNode.has(node.id) || compositeByNode.has(node.id)) {
211
- this._unsupportedComponentCount++;
212
- continue;
213
- }
214
- const emitter = createParticle2DEmitter(component);
215
- content.addChild(new Entity(`${entity.name} particles`).addComponent(emitter));
216
- runtimeNode.particles.push(emitter);
217
- this._visualCount++;
218
- this._particleCount++;
219
- if (component.resource) this._loadTexture(component.resource, handle => { emitter.textureSource = handle; });
220
- continue;
221
- }
222
- if (isAudioComponent(component)) {
223
- const resource = this._animation.resources.find(candidate => candidate.id === component.resource);
224
- if (!resource || resource.type !== 'audio') {
225
- this._unsupportedComponentCount++;
226
- continue;
227
- }
228
- runtimeNode.audio.push(new AnimationAudioClip(resource.uri, component, node, this._animation.duration));
229
- this._audioCount++;
230
- continue;
231
- }
232
- if (!isCoreVisualComponent(component)) {
233
- const handler = runtimeExtensions?.get(component.type);
234
- if (!handler) {
235
- this._unsupportedComponentCount++;
236
- continue;
237
- }
238
- try {
239
- const instance = handler.create({ animation: _animation, node, component, parent: content });
240
- if (instance) {
241
- runtimeNode.extensions.push(instance);
242
- this._visualCount++;
243
- }
244
- } catch (error) {
245
- this._abortController.abort();
246
- for (const handle of this._assetHandles) handle.release();
247
- this._assetHandles.length = 0;
248
- for (const createdNode of [...this._nodes, runtimeNode]) {
249
- for (const audio of createdNode.audio) audio.destroy();
250
- for (const instance of createdNode.extensions) instance.destroy?.();
251
- }
252
- this._root.destroy();
253
- throw error;
254
- }
255
- continue;
256
- }
257
- const deferred: DeferredCoreVisual = {
258
- component,
259
- componentIndex,
260
- order: renderOrder++,
261
- sourceGroup: sourceGroupByNode.get(node.id),
262
- composite: compositeByNode.get(node.id),
263
- effects: effectsByNode.get(node.id),
264
- };
265
- this._visualCount++;
266
- if (component.type === 'text2d') this._textCount++;
267
- if (isSourceNodeActiveAt(node, 0, sourceNodesById, _animation.duration)) {
268
- this._materializeCoreVisual(runtimeNode, deferred);
269
- } else {
270
- runtimeNode.deferredVisuals.push(deferred);
271
- }
272
- }
273
- this._nodes.push(runtimeNode);
274
- this._nodesById.set(node.id, runtimeNode);
275
- }
276
- for (const node of this._nodes) {
277
- const parent = node.source.parent ? this._nodesById.get(node.source.parent)?.content : this._root;
278
- (parent ?? this._root).addChild(node.entity);
279
- }
280
- for (let index = 0; index < _animation.tracks.length; index++) {
281
- const track = _animation.tracks[index]!;
282
- let tracks = this._tracksByNode.get(track.node);
283
- if (!tracks) this._tracksByNode.set(track.node, tracks = []);
284
- tracks.push(track);
285
- this._trackIndices.set(track, index);
286
- }
287
- this._trackCursors = new Int32Array(_animation.tracks.length);
288
- }
289
-
290
- apply(time: number, playing = this._playing, speed = this._speed, forceParticleSeek = false): void {
291
- const discontinuity = forceParticleSeek || (
292
- !Number.isFinite(this._lastAppliedTime)
293
- ? time > 1e-6
294
- : time + 1e-6 < this._lastAppliedTime || Math.abs(time - this._lastAppliedTime) > 0.25
295
- );
296
- this._playing = playing;
297
- this._speed = speed;
298
- this._opacityMemo.clear();
299
- for (const node of this._nodes) {
300
- const start = node.source.start ?? 0;
301
- const end = start + (node.source.duration ?? this._animation.duration);
302
- node.entity.disabled = time < start || time > end;
303
- node.transform.setPosition(node.initialX, node.initialY);
304
- node.transform.rotation = node.initialRotation;
305
- node.transform.setScale(node.initialScaleX, node.initialScaleY);
306
- node.opacity = node.initialOpacity;
307
- for (const track of this._tracksByNode.get(node.source.id) ?? []) this._applyTrack(node, track, time);
308
- }
309
- for (const node of this._nodes) {
310
- const opacity = this._resolveOpacity(node);
311
- if (node.deferredVisuals.length > 0 && isRuntimeNodeActive(node, this._nodesById)) {
312
- for (const deferred of node.deferredVisuals.splice(0)) this._materializeCoreVisual(node, deferred);
313
- }
314
- for (const particle of node.particles) {
315
- if (discontinuity) particle.seek(Math.max(0, time - (node.source.start ?? 0)));
316
- particle.opacity = opacity;
317
- particle.playing = playing && !node.entity.disabled;
318
- }
319
- for (const audio of node.audio) audio.sync(time, playing && !node.entity.disabled, speed, opacity);
320
- for (const visual of node.visuals) {
321
- if (visual.spriteUvRectTrack) {
322
- const frame = findVectorTrackFrame(visual.spriteUvRectTrack, time);
323
- if (frame !== visual.spriteUvRectFrame) {
324
- const offset = frame * 4;
325
- visual.component.setUvRect([
326
- visual.spriteUvRectTrack.values[offset]!,
327
- visual.spriteUvRectTrack.values[offset + 1]!,
328
- visual.spriteUvRectTrack.values[offset + 2]!,
329
- visual.spriteUvRectTrack.values[offset + 3]!,
330
- ]);
331
- visual.spriteUvRectFrame = frame;
332
- }
333
- }
334
- visual.textRasterizer?.setTime(time);
335
- if (visual.pathMorph && visual.lastMorphTime !== time) {
336
- const next = tessellateAnimationPath(sampleVectorPathMorph(visual.pathMorph, time));
337
- visual.component.geometry.setData(next.positions, next.indices);
338
- visual.component.revision++;
339
- visual.lastMorphTime = time;
340
- }
341
- if (visual.vectorShape && visual.lastVectorTime !== undefined && visual.lastVectorTime !== time) {
342
- updateVectorVisual(visual, time);
343
- visual.lastVectorTime = time;
344
- }
345
- if (visual.sourceEffects && visual.lastEffectTime !== time) {
346
- updateVisualEffects(visual.component, visual.sourceEffects, time);
347
- visual.component.revision++;
348
- visual.lastEffectTime = time;
349
- }
350
- if (visual.compositeExpansionTracks && visual.lastCompositeTime !== time) {
351
- for (let index = 0; index < visual.compositeExpansionTracks.length; index++) {
352
- const track = visual.compositeExpansionTracks[index];
353
- if (track) visual.component.setCompositeExpansion(index, sampleVectorTrack(track, time)[0] ?? 0);
354
- }
355
- visual.lastCompositeTime = time;
356
- }
357
- const alpha = visual.styleAlpha * opacity;
358
- if (Math.abs(alpha - visual.lastAlpha) < 1e-6) continue;
359
- visual.color[3] = alpha;
360
- visual.component.revision++;
361
- visual.lastAlpha = alpha;
362
- }
363
- for (const extension of node.extensions) {
364
- if (extension.apply) extension.apply(time, opacity);
365
- else extension.setOpacity?.(opacity);
366
- }
367
- }
368
- this._lastAppliedTime = time;
369
- }
370
-
371
- destroy(): void {
372
- if (this._destroyed) return;
373
- this._destroyed = true;
374
- this._abortController.abort();
375
- for (const handle of this._assetHandles) handle.release();
376
- this._assetHandles.length = 0;
377
- if (typeof document !== 'undefined' && document.fonts) {
378
- for (const face of this._fontFaces) document.fonts.delete(face);
379
- }
380
- this._fontFaces.length = 0;
381
- this._fontLoads.clear();
382
- for (const node of this._nodes) {
383
- for (const visual of node.visuals) visual.textRasterizer?.destroy();
384
- for (const audio of node.audio) audio.destroy();
385
- node.audio.length = 0;
386
- for (const extension of node.extensions) extension.destroy?.();
387
- node.extensions.length = 0;
388
- }
389
- this._root.destroy();
390
- this._nodes.length = 0;
391
- this._nodesById.clear();
392
- this._tracksByNode.clear();
393
- this._trackIndices.clear();
394
- }
395
-
396
- setPlaying(playing: boolean): void {
397
- this._playing = playing;
398
- if (playing) return;
399
- for (const node of this._nodes) {
400
- for (const particle of node.particles) particle.playing = false;
401
- for (const audio of node.audio) audio.pause();
402
- }
403
- }
404
-
405
- private _loadTexture(resourceId: string, apply: (handle: AssetHandle<GPUTexture>) => void): void {
406
- const resource = this._animation.resources.find(candidate => candidate.id === resourceId);
407
- if (!resource || resource.type !== 'image' || !this._assetManager) {
408
- this._failedResourceCount++;
409
- return;
410
- }
411
- this._pendingResourceCount++;
412
- let request: Promise<AssetHandle<GPUTexture>>;
413
- try {
414
- request = this._assetManager.loadTexture(resource.uri, {
415
- label: `Animation2D:${resource.id}`,
416
- format: resource.colorSpace === 'linear' ? 'rgba8unorm' : 'rgba8unorm-srgb',
417
- cacheKey: resource.integrity ?? resource.uri,
418
- signal: this._abortController.signal,
419
- });
420
- } catch {
421
- this._pendingResourceCount--;
422
- this._failedResourceCount++;
423
- return;
424
- }
425
- void request.then(handle => {
426
- if (this._destroyed) { handle.release(); return; }
427
- this._assetHandles.push(handle);
428
- apply(handle);
429
- }).catch(() => {
430
- if (!this._destroyed) this._failedResourceCount++;
431
- }).finally(() => {
432
- this._pendingResourceCount = Math.max(0, this._pendingResourceCount - 1);
433
- });
434
- }
435
-
436
- protected _materializeCoreVisual(node: Animation2DRuntimeNode, deferred: DeferredCoreVisual): void {
437
- let visual: ReturnType<typeof createCoreVisual>;
438
- try {
439
- visual = createCoreVisual(
440
- deferred.component,
441
- deferred.componentIndex,
442
- this._instanceId,
443
- node.source,
444
- deferred.order,
445
- deferred.sourceGroup,
446
- deferred.composite,
447
- deferred.effects,
448
- );
449
- } catch {
450
- this._unsupportedComponentCount++;
451
- return;
452
- }
453
- node.content.addChild(new Entity(`${node.entity.name} visual`).addComponent(visual.component));
454
- node.visuals.push(visual.runtime);
455
- if (deferred.component.type === 'sprite2d') {
456
- this._loadTexture(deferred.component.resource, handle => visual.component.setTextureHandle(handle));
457
- } else if (deferred.component.type === 'text2d' && visual.runtime.textRasterizer) {
458
- this._loadTextFonts(deferred.component, visual.runtime.textRasterizer);
459
- }
460
- }
461
-
462
- private _loadTextFonts(component: Readonly<AnimationText2DComponent>, rasterizer: AnimationTextRasterizer): void {
463
- const descriptors = [
464
- component.fontResource ? {
465
- resource: component.fontResource,
466
- family: component.fontFamily ?? 'sans-serif',
467
- style: component.fontStyle ?? 'normal',
468
- weight: component.fontWeight ?? 400,
469
- } : null,
470
- ...(component.documents ?? []).map(document => document.fontResource ? {
471
- resource: document.fontResource,
472
- family: document.fontFamily ?? component.fontFamily ?? 'sans-serif',
473
- style: document.fontStyle ?? component.fontStyle ?? 'normal',
474
- weight: document.fontWeight ?? component.fontWeight ?? 400,
475
- } : null),
476
- ].filter((value): value is NonNullable<typeof value> => value !== null);
477
- for (const descriptor of descriptors) this._loadFont(descriptor, rasterizer);
478
- }
479
-
480
- private _loadFont(
481
- descriptor: { resource: string; family: string; style: 'normal' | 'italic'; weight: string | number },
482
- rasterizer: AnimationTextRasterizer,
483
- ): void {
484
- const key = `${descriptor.resource}:${descriptor.family}:${descriptor.style}:${descriptor.weight}`;
485
- const existing = this._fontLoads.get(key);
486
- if (existing) {
487
- if (existing.state === 'loaded') rasterizer.invalidateFont();
488
- else if (existing.state === 'loading') existing.rasterizers.add(rasterizer);
489
- return;
490
- }
491
- const load = { state: 'loading' as const, rasterizers: new Set([rasterizer]) };
492
- this._fontLoads.set(key, load);
493
- const resource = this._animation.resources.find(candidate => candidate.id === descriptor.resource);
494
- if (!resource || resource.type !== 'binary' || !this._assetManager || typeof FontFace === 'undefined'
495
- || typeof document === 'undefined' || !document.fonts) {
496
- this._fontLoads.set(key, { ...load, state: 'failed' });
497
- this._failedResourceCount++;
498
- return;
499
- }
500
- this._pendingResourceCount++;
501
- let retainedHandle: AssetHandle<ArrayBuffer> | null = null;
502
- void this._assetManager.load<ArrayBuffer>(
503
- `Animation2D.font:${resource.integrity ?? resource.uri}`,
504
- async signal => {
505
- const response = await fetch(resource.uri, signal ? { signal } : {});
506
- if (!response.ok) throw new Error(`Font request failed with HTTP ${response.status}.`);
507
- return response.arrayBuffer();
508
- },
509
- () => {},
510
- { signal: this._abortController.signal },
511
- ).then(async handle => {
512
- retainedHandle = handle;
513
- if (this._destroyed) { handle.release(); return; }
514
- const face = new FontFace(descriptor.family, handle.value, {
515
- style: descriptor.style,
516
- weight: String(descriptor.weight),
517
- });
518
- await face.load();
519
- if (this._destroyed) { handle.release(); return; }
520
- document.fonts.add(face);
521
- this._fontFaces.push(face);
522
- this._assetHandles.push(handle);
523
- retainedHandle = null;
524
- const current = this._fontLoads.get(key);
525
- if (current) {
526
- current.state = 'loaded';
527
- for (const target of current.rasterizers) target.invalidateFont();
528
- current.rasterizers.clear();
529
- }
530
- }).catch(() => {
531
- retainedHandle?.release();
532
- if (!this._destroyed) {
533
- const current = this._fontLoads.get(key);
534
- if (current) {
535
- current.state = 'failed';
536
- current.rasterizers.clear();
537
- }
538
- this._failedResourceCount++;
539
- }
540
- }).finally(() => {
541
- this._pendingResourceCount = Math.max(0, this._pendingResourceCount - 1);
542
- });
543
- }
544
-
545
- private _applyTrack(node: Animation2DRuntimeNode, track: ParsedAnimationTrack, time: number): void {
546
- const trackIndex = this._trackIndices.get(track)!;
547
- const cursor = findFrame(track.times, time, this._trackCursors[trackIndex] ?? 0);
548
- this._trackCursors[trackIndex] = cursor;
549
- const progress = sampleProgress(track, cursor, time);
550
- const offset = cursor * track.valueSize;
551
- const nextOffset = Math.min(cursor + 1, track.times.length - 1) * track.valueSize;
552
- let x = mix(track.values[offset]!, track.values[nextOffset]!, progress);
553
- let y = track.valueSize === 2 ? mix(track.values[offset + 1]!, track.values[nextOffset + 1]!, progress) : 0;
554
- if (track.property === 'position' && track.spatialTangents && cursor < track.times.length - 1) {
555
- const tangentOffset = cursor * 4;
556
- x = spatialBezier(
557
- track.values[offset]!,
558
- track.values[offset]! + track.spatialTangents[tangentOffset]!,
559
- track.values[nextOffset]! + track.spatialTangents[tangentOffset + 2]!,
560
- track.values[nextOffset]!,
561
- progress,
562
- );
563
- y = spatialBezier(
564
- track.values[offset + 1]!,
565
- track.values[offset + 1]! + track.spatialTangents[tangentOffset + 1]!,
566
- track.values[nextOffset + 1]! + track.spatialTangents[tangentOffset + 3]!,
567
- track.values[nextOffset + 1]!,
568
- progress,
569
- );
570
- }
571
- switch (track.property) {
572
- case 'position': node.transform.setPosition(x, -y); break;
573
- case 'rotation': node.transform.rotation = -x; break;
574
- case 'scale': node.transform.setScale(x, y); break;
575
- case 'opacity': node.opacity = clamp(x, 0, 1); break;
576
- }
577
- }
578
-
579
- protected _resolveOpacity(node: Animation2DRuntimeNode): number {
580
- const cached = this._opacityMemo.get(node.source.id);
581
- if (cached !== undefined) return cached;
582
- const parent = node.source.parent ? this._nodesById.get(node.source.parent) : undefined;
583
- const result = clamp(node.opacity * (parent ? this._resolveOpacity(parent) : 1), 0, 1);
584
- this._opacityMemo.set(node.source.id, result);
585
- return result;
586
- }
587
-
588
- protected _isNodeActive(node: Animation2DRuntimeNode): boolean {
589
- return isRuntimeNodeActive(node, this._nodesById);
590
- }
591
-
592
- /** @internal Applies state-machine-only component channels to the shared visual instance. */
593
- protected _applyStateMachineVisualChannel(
594
- node: Animation2DRuntimeNode,
595
- path: string,
596
- value: unknown,
597
- ): boolean {
598
- const match = /^components\.(\d+)\.(sprite\.uv-rect|vector\.morph|path\.morph)$/u.exec(path);
599
- if (!match) return false;
600
- const componentIndex = Number(match[1]);
601
- if (node.deferredVisuals.length > 0 && this._isNodeActive(node)) {
602
- const deferred = node.deferredVisuals.filter(item => item.componentIndex === componentIndex);
603
- node.deferredVisuals.splice(0, node.deferredVisuals.length, ...node.deferredVisuals.filter(
604
- item => item.componentIndex !== componentIndex,
605
- ));
606
- for (const item of deferred) this._materializeCoreVisual(node, item);
607
- }
608
- const visuals = node.visuals.filter(visual => visual.sourceComponentIndex === componentIndex);
609
- if (match[2] === 'sprite.uv-rect') {
610
- const uv = numericStateMachineValue(value, path, 4);
611
- for (const visual of visuals) visual.component.setUvRect([uv[0]!, uv[1]!, uv[2]!, uv[3]!]);
612
- return true;
613
- }
614
- const morph = numericStateMachineValue(value, path, 1);
615
- for (const visual of visuals) {
616
- if (match[2] === 'path.morph' && visual.pathMorph) {
617
- const next = tessellateAnimationPath({
618
- type: 'path2d',
619
- commands: visual.pathMorph.commands,
620
- values: morph,
621
- fill: visual.pathMorph.fill,
622
- fillRule: visual.pathMorph.fillRule,
623
- ...(visual.pathMorph.tolerance === undefined ? {} : { tolerance: visual.pathMorph.tolerance }),
624
- });
625
- visual.component.geometry.setData(next.positions, next.indices);
626
- visual.component.revision++;
627
- } else if (match[2] === 'vector.morph' && visual.vectorShape) {
628
- const shape = visual.vectorShape;
629
- let pathValues = morph;
630
- if (shape.morphRelative) {
631
- pathValues = new Float32Array(morph.length);
632
- for (let index = 0; index < morph.length; index++) {
633
- pathValues[index] = morph[index]! + (shape.values[index] ?? 0);
634
- }
635
- }
636
- updateVectorVisual(visual, 0, pathValues);
637
- }
638
- }
639
- return true;
640
- }
641
- }
642
-
643
- function numericStateMachineValue(value: unknown, bindingPath: string, minimumSize: number): Float32Array {
644
- if (!(value instanceof Float32Array) || value.length < minimumSize) {
645
- throw new TypeError(
646
- `Animation2D state-machine binding "${bindingPath}" requires at least ${minimumSize} Float32 values.`,
647
- );
648
- }
649
- return value;
650
- }
651
-
652
- function createEllipseGeometry(width: number, height: number, centerX: number, centerY: number): Geometry2D {
653
- const segments = 48;
654
- const positions = new Float32Array((segments + 1) * 2);
655
- positions[0] = centerX;
656
- positions[1] = centerY;
657
- for (let index = 0; index < segments; index++) {
658
- const angle = index / segments * Math.PI * 2;
659
- positions[(index + 1) * 2] = centerX + Math.cos(angle) * width / 2;
660
- positions[(index + 1) * 2 + 1] = centerY + Math.sin(angle) * height / 2;
661
- }
662
- const indices = new Uint16Array(segments * 3);
663
- for (let index = 0; index < segments; index++) {
664
- indices[index * 3] = 0;
665
- indices[index * 3 + 1] = index + 1;
666
- indices[index * 3 + 2] = index + 2 > segments ? 1 : index + 2;
667
- }
668
- return new Geometry2D(positions, indices);
669
- }
670
-
671
- function isShape2DComponent(component: AnimationComponent): component is AnimationShape2DComponent {
672
- return component.type === 'shape2d'
673
- && (component as Partial<AnimationShape2DComponent>).size !== undefined
674
- && (component as Partial<AnimationShape2DComponent>).fill !== undefined;
675
- }
676
-
677
- function isPath2DComponent(component: AnimationComponent): component is AnimationPath2DComponent {
678
- return component.type === 'path2d'
679
- && typeof (component as Partial<AnimationPath2DComponent>).commands === 'string'
680
- && (component as Partial<AnimationPath2DComponent>).values !== undefined;
681
- }
682
-
683
- function isSprite2DComponent(component: AnimationComponent): component is AnimationSprite2DComponent {
684
- return component.type === 'sprite2d'
685
- && typeof (component as Partial<AnimationSprite2DComponent>).resource === 'string'
686
- && (component as Partial<AnimationSprite2DComponent>).size !== undefined;
687
- }
688
-
689
- function isText2DComponent(component: AnimationComponent): component is AnimationText2DComponent {
690
- return component.type === 'text2d'
691
- && typeof (component as Partial<AnimationText2DComponent>).text === 'string'
692
- && (component as Partial<AnimationText2DComponent>).size !== undefined;
693
- }
694
-
695
- function isParticle2DComponent(component: AnimationComponent): component is AnimationParticle2DComponent {
696
- return component.type === 'particle2d'
697
- && typeof (component as Partial<AnimationParticle2DComponent>).maxParticles === 'number';
698
- }
699
-
700
- function isAudioComponent(component: AnimationComponent): component is AnimationAudioComponent {
701
- return component.type === 'audio' && typeof (component as Partial<AnimationAudioComponent>).resource === 'string';
702
- }
703
-
704
- function isVectorStrokePathComponent(component: AnimationComponent): component is VectorStrokePathComponent {
705
- const candidate = component as Partial<VectorStrokePathComponent>;
706
- const inlinePath = typeof candidate.commands === 'string'
707
- && /^[MLQCZ]+$/.test(candidate.commands)
708
- && candidate.commands[0] === 'M'
709
- && Array.isArray(candidate.values)
710
- && candidate.values.length === strokePathValueCount(candidate.commands)
711
- && candidate.values.every(value => typeof value === 'number' && Number.isFinite(value));
712
- const sourcePath = typeof candidate.sourceComponent === 'number'
713
- && Number.isSafeInteger(candidate.sourceComponent)
714
- && candidate.sourceComponent >= 0;
715
- return component.type === 'org.haiyue.vector-stroke@1'
716
- && (inlinePath || sourcePath)
717
- && Array.isArray(candidate.color)
718
- && candidate.color.length === 4
719
- && candidate.color.every(value => typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1)
720
- && typeof candidate.width === 'number' && Number.isFinite(candidate.width) && candidate.width > 0
721
- && (candidate.lineCap === 'butt' || candidate.lineCap === 'round' || candidate.lineCap === 'square')
722
- && (candidate.lineJoin === 'miter' || candidate.lineJoin === 'round' || candidate.lineJoin === 'bevel')
723
- && typeof candidate.miterLimit === 'number' && Number.isFinite(candidate.miterLimit) && candidate.miterLimit >= 1
724
- && (candidate.tolerance === undefined
725
- || (typeof candidate.tolerance === 'number' && Number.isFinite(candidate.tolerance) && candidate.tolerance > 0));
726
- }
727
-
728
- function isVectorPathMorphComponent(component: AnimationComponent): component is VectorPathMorphComponent {
729
- const candidate = component as Partial<VectorPathMorphComponent>;
730
- return component.type === 'org.haiyue.vector-path-morph@1'
731
- && typeof candidate.commands === 'string'
732
- && /^[MLQCZ]+$/.test(candidate.commands)
733
- && (Array.isArray(candidate.times) || candidate.times instanceof Float32Array) && candidate.times.length > 0
734
- && Array.from(candidate.times).every(value => typeof value === 'number' && Number.isFinite(value))
735
- && (Array.isArray(candidate.values) || candidate.values instanceof Float32Array)
736
- && typeof candidate.valueSize === 'number' && Number.isSafeInteger(candidate.valueSize) && candidate.valueSize > 0
737
- && candidate.values.length === candidate.times.length * candidate.valueSize
738
- && Array.from(candidate.values).every(value => typeof value === 'number' && Number.isFinite(value))
739
- && (candidate.interpolation === 'step' || candidate.interpolation === 'linear' || candidate.interpolation === 'cubic-bezier')
740
- && Array.isArray(candidate.fill) && candidate.fill.length === 4;
741
- }
742
-
743
- function strokePathValueCount(commands: string): number {
744
- let count = 0;
745
- for (const command of commands) count += command === 'M' || command === 'L' ? 2 : command === 'Q' ? 4 : command === 'C' ? 6 : 0;
746
- return count;
747
- }
748
-
749
- function isCoreVisualComponent(component: AnimationComponent): component is CoreVisualComponent {
750
- return isShape2DComponent(component) || isPath2DComponent(component) || isSprite2DComponent(component)
751
- || isText2DComponent(component) || isVectorShapeComponent(component)
752
- || isVectorStrokePathComponent(component) || isVectorPathMorphComponent(component);
753
- }
754
-
755
- function isVectorShapeComponent(component: AnimationComponent): component is AnimationVectorShapeComponent {
756
- return component.type === ANIMATION_VECTOR_SHAPE_EXTENSION_ID
757
- && typeof (component as Partial<AnimationVectorShapeComponent>).commands === 'string';
758
- }
759
-
760
- function createCoreVisual(
761
- component: CoreVisualComponent,
762
- componentIndex: number,
763
- instanceId: number,
764
- node: Readonly<AnimationNode>,
765
- order: number,
766
- sourceGroup: string | undefined,
767
- composite: Readonly<AnimationComposite> | undefined,
768
- effects: readonly Readonly<AnimationLayerEffect>[] | undefined,
769
- ): { component: AnimationVisual2D; runtime: Animation2DRuntimeVisual } {
770
- let geometry: Geometry2D;
771
- let color: [number, number, number, number];
772
- let textMaterial: AnimationTextRasterizer | null = null;
773
- let gradient: import('./AnimationVisual2D').AnimationVisualGradient | null = null;
774
- let vectorPaint: 'fill' | 'stroke' | undefined;
775
- if (component.type === 'path2d') {
776
- geometry = tessellateAnimationPath(component);
777
- color = [...component.fill];
778
- } else if (component.type === 'org.haiyue.vector-path-morph@1') {
779
- geometry = tessellateAnimationPath(sampleVectorPathMorph(component, component.times[0] ?? 0));
780
- color = [...component.fill];
781
- } else if (component.type === 'org.haiyue.vector-stroke@1') {
782
- geometry = tessellateVectorStrokePath(resolveVectorStrokePath(component, node));
783
- color = [...component.color];
784
- } else if (component.type === ANIMATION_VECTOR_SHAPE_EXTENSION_ID) {
785
- const initialTime = component.morph?.times[0] ?? firstVectorModifierTime(component) ?? 0;
786
- const pathValues = sampleVectorShapePath(component, initialTime);
787
- const resolvedPath = resolveVectorPath(component, pathValues, initialTime, component.fill !== undefined);
788
- if (component.fill) {
789
- geometry = resolvedPath.commands.length === 0 ? emptyGeometry2D() : tessellateRuntimeFillPath({
790
- type: 'path2d', commands: resolvedPath.commands, values: resolvedPath.values,
791
- fill: [1, 1, 1, 1], fillRule: component.fillRule ?? 'nonzero',
792
- ...(component.tolerance === undefined ? {} : { tolerance: component.tolerance }),
793
- });
794
- vectorPaint = 'fill';
795
- if (component.fill.kind === 'solid') color = [...component.fill.color];
796
- else {
797
- color = [1, 1, 1, 1];
798
- gradient = createRuntimeGradient(component.fill);
799
- }
800
- } else {
801
- const stroke = component.stroke!;
802
- geometry = resolvedPath.commands.length === 0 ? emptyGeometry2D() : tessellateVectorStrokePath({
803
- type: 'org.haiyue.vector-stroke@1', commands: resolvedPath.commands, values: resolvedPath.values,
804
- color: stroke.color, width: stroke.width, lineCap: stroke.lineCap, lineJoin: stroke.lineJoin,
805
- miterLimit: stroke.miterLimit,
806
- ...(component.tolerance === undefined ? {} : { tolerance: component.tolerance }),
807
- ...(stroke.dash ? { dash: stroke.dash } : {}),
808
- ...(stroke.dashOffset === undefined ? {} : { dashOffset: stroke.dashOffset }),
809
- }, true);
810
- vectorPaint = 'stroke';
811
- color = stroke.gradient ? [1, 1, 1, 1] : [...stroke.color];
812
- if (stroke.gradient) gradient = createRuntimeGradient(stroke.gradient);
813
- }
814
- } else {
815
- const position = component.position ?? [0, 0];
816
- const centerX = position[0];
817
- const centerY = -position[1];
818
- geometry = component.type === 'shape2d' && component.shape === 'ellipse'
819
- ? createEllipseGeometry(component.size[0], component.size[1], centerX, centerY)
820
- : createRect2D({ width: component.size[0], height: component.size[1], x: centerX, y: centerY });
821
- color = component.type === 'shape2d' ? [...component.fill]
822
- : component.type === 'sprite2d' ? [...(component.tint ?? [1, 1, 1, 1])] : [1, 1, 1, 1];
823
- if (component.type === 'text2d') {
824
- textMaterial = new AnimationTextRasterizer(component);
825
- }
826
- }
827
- const visual = new AnimationVisual2D({
828
- geometry,
829
- color,
830
- instanceId,
831
- nodeId: sourceGroup ?? node.id,
832
- order,
833
- sourceOnly: sourceGroup !== undefined,
834
- ...(composite ? { composite } : {}),
835
- ...(component.type === 'sprite2d' ? { requiresTexture: true, uvRect: component.uvRect ?? [0, 0, 1, 1] } : {}),
836
- ...(component.type === 'text2d' ? { requiresTexture: true, textMaterial } : {}),
837
- ...(gradient ? { gradient } : {}),
838
- ...(effects?.length ? { effects: createVisualEffects(effects, 0) } : {}),
839
- });
840
- const initialStyleAlpha = component.type === ANIMATION_VECTOR_SHAPE_EXTENSION_ID
841
- ? initialVectorStyleAlpha(component)
842
- : visual.color[3];
843
- const compositeExpansionTracks = composite
844
- ? compositeLayers(composite).map(layer => layer.expansionTrack)
845
- : [];
846
- return {
847
- component: visual,
848
- runtime: {
849
- component: visual,
850
- sourceComponentIndex: componentIndex,
851
- color: visual.color,
852
- baseAlpha: visual.color[3],
853
- styleAlpha: initialStyleAlpha,
854
- lastAlpha: Number.NaN,
855
- ...(component.type === 'org.haiyue.vector-path-morph@1' ? { pathMorph: component, lastMorphTime: component.times[0] ?? 0 } : {}),
856
- ...(component.type === ANIMATION_VECTOR_SHAPE_EXTENSION_ID ? {
857
- vectorShape: component,
858
- vectorPaint: vectorPaint!,
859
- ...(isVectorShapeDynamic(component) ? { lastVectorTime: Number.NaN } : {}),
860
- } : {}),
861
- ...(textMaterial ? { textRasterizer: textMaterial } : {}),
862
- ...(effects?.length ? { sourceEffects: effects, lastEffectTime: 0 } : {}),
863
- ...(compositeExpansionTracks.some(track => track !== undefined) ? {
864
- compositeExpansionTracks,
865
- lastCompositeTime: Number.NaN,
866
- } : {}),
867
- ...(component.type === 'sprite2d' && component.uvRectTrack ? {
868
- spriteUvRectTrack: component.uvRectTrack,
869
- spriteUvRectFrame: -1,
870
- } : {}),
871
- },
872
- };
873
- }
874
-
875
- function createVisualEffects(
876
- effects: readonly Readonly<AnimationLayerEffect>[],
877
- time: number,
878
- ): import('./AnimationVisual2D').AnimationVisualEffect[] {
879
- return effects.map(effect => ({ kind: effect.kind, values: sampleEffectValues(effect, time) }));
880
- }
881
-
882
- function updateVisualEffects(
883
- visual: AnimationVisual2D,
884
- effects: readonly Readonly<AnimationLayerEffect>[],
885
- time: number,
886
- ): void {
887
- const count = Math.min(visual.effects.length, effects.length);
888
- for (let index = 0; index < count; index++) {
889
- const sampled = sampleEffectValues(effects[index]!, time);
890
- const target = visual.effects[index]!.values;
891
- if (target.length === sampled.length) target.set(sampled);
892
- }
893
- }
894
-
895
- function sampleEffectValues(effect: Readonly<AnimationLayerEffect>, time: number): Float32Array {
896
- switch (effect.kind) {
897
- case 'tint': {
898
- const black = effect.blackTrack ? sampleVectorTrack(effect.blackTrack, time) : effect.black;
899
- const white = effect.whiteTrack ? sampleVectorTrack(effect.whiteTrack, time) : effect.white;
900
- const amount = effect.amountTrack ? sampleVectorTrack(effect.amountTrack, time)[0] ?? effect.amount : effect.amount;
901
- return new Float32Array([
902
- black[0] ?? effect.black[0], black[1] ?? effect.black[1], black[2] ?? effect.black[2],
903
- white[0] ?? effect.white[0], white[1] ?? effect.white[1], white[2] ?? effect.white[2],
904
- clamp(amount, 0, 1),
905
- ]);
906
- }
907
- case 'fill': {
908
- const color = effect.colorTrack ? sampleVectorTrack(effect.colorTrack, time) : effect.color;
909
- const opacity = effect.opacityTrack ? sampleVectorTrack(effect.opacityTrack, time)[0] ?? effect.opacity ?? 1 : effect.opacity ?? 1;
910
- return new Float32Array([
911
- color[0] ?? effect.color[0], color[1] ?? effect.color[1], color[2] ?? effect.color[2], color[3] ?? effect.color[3],
912
- clamp(opacity, 0, 1),
913
- ]);
914
- }
915
- case 'opacity': {
916
- const opacity = effect.opacityTrack ? sampleVectorTrack(effect.opacityTrack, time)[0] ?? effect.opacity : effect.opacity;
917
- return new Float32Array([clamp(opacity, 0, 1)]);
918
- }
919
- case 'color-matrix':
920
- return new Float32Array(effect.matrixTrack ? sampleVectorTrack(effect.matrixTrack, time) : effect.matrix);
921
- case 'blur': {
922
- const radius = effect.radiusTrack ? sampleVectorTrack(effect.radiusTrack, time) : effect.radius;
923
- return new Float32Array([Math.max(0, radius[0] ?? effect.radius[0]), Math.max(0, radius[1] ?? effect.radius[1])]);
924
- }
925
- case 'drop-shadow': {
926
- const color = effect.colorTrack ? sampleVectorTrack(effect.colorTrack, time) : effect.color;
927
- const opacity = effect.opacityTrack ? sampleVectorTrack(effect.opacityTrack, time)[0] ?? effect.opacity : effect.opacity;
928
- const offset = effect.offsetTrack ? sampleVectorTrack(effect.offsetTrack, time) : effect.offset;
929
- const blur = effect.blurTrack ? sampleVectorTrack(effect.blurTrack, time)[0] ?? effect.blur : effect.blur;
930
- return new Float32Array([
931
- color[0] ?? effect.color[0], color[1] ?? effect.color[1], color[2] ?? effect.color[2], color[3] ?? effect.color[3],
932
- clamp(opacity, 0, 1), offset[0] ?? effect.offset[0], offset[1] ?? effect.offset[1], Math.max(0, blur),
933
- ]);
934
- }
935
- }
936
- }
937
-
938
- function updateVectorVisual(
939
- visual: Animation2DRuntimeVisual,
940
- time: number,
941
- pathOverride?: Readonly<Float32Array>,
942
- ): void {
943
- const shape = visual.vectorShape!;
944
- const paint = visual.vectorPaint!;
945
- const pathValues = pathOverride ?? sampleVectorShapePath(shape, time);
946
- let geometryChanged = shape.morph !== undefined || hasAnimatedVectorModifier(shape);
947
- if (paint === 'fill') {
948
- const fill = shape.fill!;
949
- if (geometryChanged) {
950
- const resolvedPath = resolveVectorPath(shape, pathValues, time, true);
951
- const next = !resolvedPath.commands.length ? emptyGeometry2D() : tessellateRuntimeFillPath({
952
- type: 'path2d', commands: resolvedPath.commands, values: resolvedPath.values,
953
- fill: [1, 1, 1, 1], fillRule: shape.fillRule ?? 'nonzero',
954
- ...(shape.tolerance === undefined ? {} : { tolerance: shape.tolerance }),
955
- });
956
- visual.component.geometry.setData(next.positions, next.indices);
957
- }
958
- if (fill.kind === 'solid') {
959
- const sampledColor = fill.colorTrack ? sampleVectorTrack(fill.colorTrack, time) : fill.color;
960
- visual.color[0] = sampledColor[0] ?? fill.color[0];
961
- visual.color[1] = sampledColor[1] ?? fill.color[1];
962
- visual.color[2] = sampledColor[2] ?? fill.color[2];
963
- const sampledOpacity = fill.opacityTrack
964
- ? sampleVectorTrack(fill.opacityTrack, time)[0] ?? fill.opacity ?? 1
965
- : fill.opacity ?? 1;
966
- visual.styleAlpha = clamp((sampledColor[3] ?? fill.color[3]) * sampledOpacity, 0, 1);
967
- } else {
968
- updateRuntimeGradient(visual, fill, time);
969
- visual.styleAlpha = 1;
970
- }
971
- } else {
972
- const stroke = shape.stroke!;
973
- const width = stroke.widthTrack ? sampleVectorTrack(stroke.widthTrack, time)[0] ?? stroke.width : stroke.width;
974
- const dashOffset = stroke.dashOffsetTrack
975
- ? sampleVectorTrack(stroke.dashOffsetTrack, time)[0] ?? stroke.dashOffset ?? 0
976
- : stroke.dashOffset;
977
- geometryChanged ||= stroke.widthTrack !== undefined || stroke.dashOffsetTrack !== undefined;
978
- if (geometryChanged) {
979
- const resolvedPath = resolveVectorPath(shape, pathValues, time, false);
980
- const next = !resolvedPath.commands.length ? emptyGeometry2D() : tessellateVectorStrokePath({
981
- type: 'org.haiyue.vector-stroke@1', commands: resolvedPath.commands, values: resolvedPath.values,
982
- color: stroke.color, width, lineCap: stroke.lineCap, lineJoin: stroke.lineJoin,
983
- miterLimit: stroke.miterLimit,
984
- ...(shape.tolerance === undefined ? {} : { tolerance: shape.tolerance }),
985
- ...(stroke.dash ? { dash: stroke.dash } : {}),
986
- ...(dashOffset === undefined ? {} : { dashOffset }),
987
- }, true);
988
- visual.component.geometry.setData(next.positions, next.indices);
989
- }
990
- const sampledColor = stroke.colorTrack ? sampleVectorTrack(stroke.colorTrack, time) : stroke.color;
991
- visual.color[0] = stroke.gradient ? 1 : sampledColor[0] ?? stroke.color[0];
992
- visual.color[1] = stroke.gradient ? 1 : sampledColor[1] ?? stroke.color[1];
993
- visual.color[2] = stroke.gradient ? 1 : sampledColor[2] ?? stroke.color[2];
994
- const sampledOpacity = stroke.opacityTrack
995
- ? sampleVectorTrack(stroke.opacityTrack, time)[0] ?? stroke.opacity ?? 1
996
- : stroke.opacity ?? 1;
997
- visual.styleAlpha = stroke.gradient ? 1 : clamp((sampledColor[3] ?? stroke.color[3]) * sampledOpacity, 0, 1);
998
- if (stroke.gradient) updateRuntimeGradient(visual, stroke.gradient, time);
999
- }
1000
- visual.component.revision++;
1001
- }
1002
-
1003
- function sampleVectorShapePath(
1004
- shape: Readonly<AnimationVectorShapeComponent>,
1005
- time: number,
1006
- ): Float32Array {
1007
- if (!shape.morph) return shape.values instanceof Float32Array
1008
- ? shape.values
1009
- : new Float32Array(shape.values);
1010
- const sampled = sampleVectorTrack(shape.morph, time);
1011
- if (!shape.morphRelative) return sampled;
1012
- for (let index = 0; index < sampled.length; index++) {
1013
- sampled[index] = (sampled[index] ?? 0) + (shape.values[index] ?? 0);
1014
- }
1015
- return sampled;
1016
- }
1017
-
1018
- function createRuntimeGradient(paint: AnimationVectorGradientPaint): import('./AnimationVisual2D').AnimationVisualGradient {
1019
- return {
1020
- kind: paint.kind === 'linear-gradient' ? 'linear' : 'radial',
1021
- start: [paint.start[0], -paint.start[1]],
1022
- end: [paint.end[0], -paint.end[1]],
1023
- stops: new Float32Array(paint.stops),
1024
- opacity: paint.opacity ?? 1,
1025
- };
1026
- }
1027
-
1028
- function updateRuntimeGradient(visual: Animation2DRuntimeVisual, paint: AnimationVectorGradientPaint, time: number): void {
1029
- const gradient = visual.component.gradient ??= createRuntimeGradient(paint);
1030
- const start = paint.startTrack ? sampleVectorTrack(paint.startTrack, time) : paint.start;
1031
- const end = paint.endTrack ? sampleVectorTrack(paint.endTrack, time) : paint.end;
1032
- const stops = paint.stopsTrack ? sampleVectorTrack(paint.stopsTrack, time) : paint.stops;
1033
- gradient.start[0] = start[0] ?? paint.start[0];
1034
- gradient.start[1] = -(start[1] ?? paint.start[1]);
1035
- gradient.end[0] = end[0] ?? paint.end[0];
1036
- gradient.end[1] = -(end[1] ?? paint.end[1]);
1037
- if (gradient.stops.length !== stops.length) gradient.stops = new Float32Array(stops);
1038
- else gradient.stops.set(stops);
1039
- gradient.opacity = paint.opacityTrack
1040
- ? sampleVectorTrack(paint.opacityTrack, time)[0] ?? paint.opacity ?? 1
1041
- : paint.opacity ?? 1;
1042
- }
1043
-
1044
- function initialVectorStyleAlpha(component: AnimationVectorShapeComponent): number {
1045
- if (component.fill) return component.fill.kind === 'solid'
1046
- ? component.fill.color[3] * (component.fill.opacity ?? 1)
1047
- : 1;
1048
- const stroke = component.stroke!;
1049
- return stroke.gradient ? 1 : stroke.color[3] * (stroke.opacity ?? 1);
1050
- }
1051
-
1052
- function isVectorShapeDynamic(component: AnimationVectorShapeComponent): boolean {
1053
- if (component.morph || hasAnimatedVectorModifier(component)) return true;
1054
- if (component.fill) return component.fill.kind === 'solid'
1055
- ? component.fill.colorTrack !== undefined || component.fill.opacityTrack !== undefined
1056
- : component.fill.startTrack !== undefined || component.fill.endTrack !== undefined
1057
- || component.fill.stopsTrack !== undefined || component.fill.opacityTrack !== undefined;
1058
- const stroke = component.stroke!;
1059
- return stroke.colorTrack !== undefined || stroke.opacityTrack !== undefined || stroke.widthTrack !== undefined
1060
- || stroke.dashOffsetTrack !== undefined || (stroke.gradient !== undefined && (
1061
- stroke.gradient.startTrack !== undefined || stroke.gradient.endTrack !== undefined
1062
- || stroke.gradient.stopsTrack !== undefined || stroke.gradient.opacityTrack !== undefined
1063
- ));
1064
- }
1065
-
1066
- function resolveVectorPath(
1067
- component: AnimationVectorShapeComponent,
1068
- values: readonly number[] | Float32Array,
1069
- time: number,
1070
- closeOpen: boolean,
1071
- ) {
1072
- if (!component.modifiers?.length) return { commands: component.commands, values: new Float32Array(values) };
1073
- return applyVectorPathModifiers(
1074
- component.commands,
1075
- values,
1076
- component.tolerance ?? 0.35,
1077
- component.modifiers.map(modifier => modifier.kind === 'round-corners'
1078
- ? {
1079
- kind: modifier.kind,
1080
- radius: modifier.radiusTrack ? sampleVectorTrack(modifier.radiusTrack, time)[0] ?? modifier.radius : modifier.radius,
1081
- }
1082
- : {
1083
- kind: modifier.kind,
1084
- start: modifier.startTrack ? sampleVectorTrack(modifier.startTrack, time)[0] ?? modifier.start : modifier.start,
1085
- end: modifier.endTrack ? sampleVectorTrack(modifier.endTrack, time)[0] ?? modifier.end : modifier.end,
1086
- offset: modifier.offsetTrack ? sampleVectorTrack(modifier.offsetTrack, time)[0] ?? modifier.offset : modifier.offset,
1087
- mode: modifier.mode,
1088
- }),
1089
- closeOpen,
1090
- );
1091
- }
1092
-
1093
- function hasAnimatedVectorModifier(component: AnimationVectorShapeComponent): boolean {
1094
- return component.modifiers?.some(modifier => modifier.kind === 'round-corners'
1095
- ? modifier.radiusTrack !== undefined
1096
- : modifier.startTrack !== undefined || modifier.endTrack !== undefined || modifier.offsetTrack !== undefined) ?? false;
1097
- }
1098
-
1099
- function firstVectorModifierTime(component: AnimationVectorShapeComponent): number | undefined {
1100
- for (const modifier of component.modifiers ?? []) {
1101
- const track = modifier.kind === 'round-corners'
1102
- ? modifier.radiusTrack
1103
- : modifier.startTrack ?? modifier.endTrack ?? modifier.offsetTrack;
1104
- if (track) return track.times[0];
1105
- }
1106
- return undefined;
1107
- }
1108
-
1109
- function emptyGeometry2D(): Geometry2D {
1110
- // Geometry2D deliberately rejects empty buffers; a zero-area triangle keeps
1111
- // the renderer contract valid while producing no fragments for an empty trim.
1112
- return new Geometry2D(new Float32Array(6), new Uint16Array([0, 1, 2]));
1113
- }
1114
-
1115
- function tessellateRuntimeFillPath(component: AnimationPath2DComponent): Geometry2D {
1116
- try {
1117
- return tessellateAnimationPath(component);
1118
- } catch (error) {
1119
- if (error instanceof RangeError && (
1120
- error.message === 'Animation path contains no drawable closed contour.'
1121
- || error.message === 'Animation path tessellation produced no triangles.'
1122
- )) return emptyGeometry2D();
1123
- throw error;
1124
- }
1125
- }
1126
-
1127
- function sampleVectorTrack(track: AnimationVectorValueTrack, time: number): Float32Array {
1128
- const frameCount = track.times.length;
1129
- if (frameCount === 0 || track.valueSize <= 0) return new Float32Array();
1130
- const frame = findVectorTrackFrame(track, time);
1131
- const next = Math.min(frame + 1, frameCount - 1);
1132
- let progress = 0;
1133
- if (next !== frame && track.interpolation !== 'step') {
1134
- const start = track.times[frame]!;
1135
- const end = track.times[next]!;
1136
- progress = clamp((time - start) / Math.max(end - start, 1e-8), 0, 1);
1137
- if (track.interpolation === 'cubic-bezier' && track.easings) {
1138
- const offset = frame * 4;
1139
- progress = cubicBezierYForX(
1140
- progress,
1141
- track.easings[offset] ?? 0.333,
1142
- track.easings[offset + 1] ?? 0.333,
1143
- track.easings[offset + 2] ?? 0.667,
1144
- track.easings[offset + 3] ?? 0.667,
1145
- );
1146
- }
1147
- }
1148
- const result = new Float32Array(track.valueSize);
1149
- const fromOffset = frame * track.valueSize;
1150
- const toOffset = next * track.valueSize;
1151
- for (let index = 0; index < track.valueSize; index++) {
1152
- const from = track.values[fromOffset + index] ?? 0;
1153
- result[index] = mix(from, track.values[toOffset + index] ?? from, progress);
1154
- }
1155
- return result;
1156
- }
1157
-
1158
- function findVectorTrackFrame(track: AnimationVectorValueTrack, time: number): number {
1159
- let low = 0;
1160
- let high = track.times.length - 1;
1161
- while (low < high) {
1162
- const middle = Math.ceil((low + high) / 2);
1163
- if (track.times[middle]! <= time) low = middle;
1164
- else high = middle - 1;
1165
- }
1166
- return low;
1167
- }
1168
-
1169
- function isSourceNodeActiveAt(
1170
- node: Readonly<AnimationNode>,
1171
- time: number,
1172
- nodesById: ReadonlyMap<string, Readonly<AnimationNode>>,
1173
- animationDuration: number,
1174
- ): boolean {
1175
- let cursor: Readonly<AnimationNode> | undefined = node;
1176
- while (cursor) {
1177
- const start = cursor.start ?? 0;
1178
- const end = start + (cursor.duration ?? animationDuration);
1179
- if (time < start || time > end) return false;
1180
- cursor = cursor.parent ? nodesById.get(cursor.parent) : undefined;
1181
- }
1182
- return true;
1183
- }
1184
-
1185
- function isRuntimeNodeActive(
1186
- node: Animation2DRuntimeNode,
1187
- nodesById: ReadonlyMap<string, Animation2DRuntimeNode>,
1188
- ): boolean {
1189
- let cursor: Animation2DRuntimeNode | undefined = node;
1190
- while (cursor) {
1191
- if (cursor.entity.disabled) return false;
1192
- cursor = cursor.source.parent ? nodesById.get(cursor.source.parent) : undefined;
1193
- }
1194
- return true;
1195
- }
1196
-
1197
- function resolveVectorStrokePath(
1198
- component: VectorStrokePathComponent,
1199
- node: Readonly<AnimationNode>,
1200
- ): VectorStrokePathComponent {
1201
- if (typeof component.commands === 'string' && component.values !== undefined) return component;
1202
- const sourceIndex = component.sourceComponent;
1203
- const source = typeof sourceIndex === 'number' ? node.components?.[sourceIndex] : undefined;
1204
- if (!source || !isPath2DComponent(source)) throw new TypeError('Lottie stroke extension references a missing path2d source component.');
1205
- return { ...component, commands: source.commands, values: source.values };
1206
- }
1207
-
1208
- function resolveSourceGroups(nodes: readonly Readonly<AnimationNode>[], sources: ReadonlySet<string>): Map<string, string> {
1209
- const byId = new Map(nodes.map(node => [node.id, node]));
1210
- const result = new Map<string, string>();
1211
- for (const node of nodes) {
1212
- let cursor: Readonly<AnimationNode> | undefined = node;
1213
- while (cursor) {
1214
- if (sources.has(cursor.id)) { result.set(node.id, cursor.id); break; }
1215
- cursor = cursor.parent ? byId.get(cursor.parent) : undefined;
1216
- }
1217
- }
1218
- return result;
1219
- }
1220
-
1221
- function resolveInheritedEffects(
1222
- nodes: readonly Readonly<AnimationNode>[],
1223
- sourceGroups: ReadonlyMap<string, string>,
1224
- ): Map<string, readonly Readonly<AnimationLayerEffect>[]> {
1225
- const byId = new Map(nodes.map(node => [node.id, node]));
1226
- const result = new Map<string, readonly Readonly<AnimationLayerEffect>[]>();
1227
- for (const node of nodes) {
1228
- const sourceBoundary = sourceGroups.get(node.id);
1229
- let cursor: Readonly<AnimationNode> | undefined = node;
1230
- while (cursor) {
1231
- if (cursor.effects?.length) { result.set(node.id, cursor.effects); break; }
1232
- if (cursor.id === sourceBoundary) break;
1233
- cursor = cursor.parent ? byId.get(cursor.parent) : undefined;
1234
- }
1235
- }
1236
- return result;
1237
- }
1238
-
1239
- function resolveInheritedComposites(
1240
- nodes: readonly Readonly<AnimationNode>[],
1241
- sourceGroups: ReadonlyMap<string, string>,
1242
- ): Map<string, Readonly<AnimationComposite>> {
1243
- const byId = new Map(nodes.map(node => [node.id, node]));
1244
- const result = new Map<string, Readonly<AnimationComposite>>();
1245
- for (const node of nodes) {
1246
- const sourceBoundary = sourceGroups.get(node.id);
1247
- let cursor: Readonly<AnimationNode> | undefined = node;
1248
- while (cursor) {
1249
- if (cursor.composite) { result.set(node.id, cursor.composite); break; }
1250
- if (cursor.id === sourceBoundary) break;
1251
- cursor = cursor.parent ? byId.get(cursor.parent) : undefined;
1252
- }
1253
- }
1254
- return result;
1255
- }
1256
-
1257
- function compositeLayers(composite: Readonly<AnimationComposite> | undefined): readonly Readonly<AnimationCompositeLayer>[] {
1258
- if (!composite) return [];
1259
- return 'layers' in composite ? composite.layers : [composite];
1260
- }
1261
-
1262
- function findFrame(times: Float32Array, time: number, previous: number): number {
1263
- if (times.length <= 1 || time <= times[0]!) return 0;
1264
- if (time >= times[times.length - 1]!) return times.length - 1;
1265
- let cursor = Math.min(previous, times.length - 2);
1266
- if (time >= times[cursor]! && time < times[cursor + 1]!) return cursor;
1267
- let low = 0;
1268
- let high = times.length - 1;
1269
- while (low + 1 < high) {
1270
- const middle = (low + high) >>> 1;
1271
- if (times[middle]! <= time) low = middle;
1272
- else high = middle;
1273
- }
1274
- return low;
1275
- }
1276
-
1277
- function sampleProgress(track: ParsedAnimationTrack, frame: number, time: number): number {
1278
- if (frame >= track.times.length - 1 || track.interpolation === 'step') return 0;
1279
- const start = track.times[frame]!;
1280
- const end = track.times[frame + 1]!;
1281
- const linear = clamp((time - start) / Math.max(end - start, 1e-8), 0, 1);
1282
- if (track.interpolation !== 'cubic-bezier' || !track.easings) return linear;
1283
- const offset = frame * 4;
1284
- return cubicBezierYForX(
1285
- linear,
1286
- track.easings[offset]!, track.easings[offset + 1]!,
1287
- track.easings[offset + 2]!, track.easings[offset + 3]!,
1288
- );
1289
- }
1290
-
1291
- function cubicBezierYForX(x: number, x1: number, y1: number, x2: number, y2: number): number {
1292
- let t = x;
1293
- for (let iteration = 0; iteration < 5; iteration++) {
1294
- const estimate = bezier(t, x1, x2) - x;
1295
- const derivative = bezierDerivative(t, x1, x2);
1296
- if (Math.abs(derivative) < 1e-5) break;
1297
- t = clamp(t - estimate / derivative, 0, 1);
1298
- }
1299
- return bezier(t, y1, y2);
1300
- }
1301
-
1302
- function bezier(t: number, a: number, b: number): number {
1303
- const inverse = 1 - t;
1304
- return 3 * inverse * inverse * t * a + 3 * inverse * t * t * b + t * t * t;
1305
- }
1306
-
1307
- function bezierDerivative(t: number, a: number, b: number): number {
1308
- const inverse = 1 - t;
1309
- return 3 * inverse * inverse * a + 6 * inverse * t * (b - a) + 3 * t * t * (1 - b);
1310
- }
1311
-
1312
- function mix(a: number, b: number, t: number): number {
1313
- return a + (b - a) * t;
1314
- }
1315
-
1316
- function spatialBezier(start: number, control1: number, control2: number, end: number, t: number): number {
1317
- const inverse = 1 - t;
1318
- return inverse * inverse * inverse * start
1319
- + 3 * inverse * inverse * t * control1
1320
- + 3 * inverse * t * t * control2
1321
- + t * t * t * end;
1322
- }
1323
-
1324
- function clamp(value: number, min: number, max: number): number {
1325
- return Math.min(max, Math.max(min, value));
1326
- }