@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,1171 @@
1
+ import { parseAnimation, ANIMATION_MIME_TYPE } from '@haiyue/animation-spec';
2
+ import { Component, System } from '@haiyue/engine';
3
+ import { UniqueCheckType, ComponentLifecycleFlags } from '@haiyue/engine/ecs';
4
+ import { A as Animation2DRuntime, a as AnimationVisual2D } from './chunks/Animation2DStateMachine-9IonguNa.js';
5
+ export { c as createAnimation2DStateMachineController, t as tessellateAnimationPath } from './chunks/Animation2DStateMachine-9IonguNa.js';
6
+ import { requireEngineDevice, getExtensionGPUResourceTracker, beginRenderCommandPass, cloneRenderPassDescriptor, alignUp4, estimateTextureBytes, getExtensionSharedRendererResource } from '@haiyue/engine/extension-authoring';
7
+ import { R as RenderSystem2DBase, d as destroyCamera2DGpu, c as createCamera2DGpu, i as createCamera2DLayout } from './chunks/RenderSystem2DBase-DDT61dAu.js';
8
+ import { A as Animation2dWgsl } from './chunks/2d-ui-animation-2d.generated-D4aHzFU2.js';
9
+ export { A as AnimationStateMachineController, a as AnimationStateMachineValidationError, c as compileAnimationStateMachine, v as validateAnimationStateMachineDefinition } from './chunks/AnimationStateMachineController-BIcAh-sT.js';
10
+ import '@haiyue/engine/geometry';
11
+ import '@haiyue/engine/components';
12
+ import 'wgpu-matrix';
13
+
14
+ class Animation2DComponent extends Component {
15
+ static UniqueCheckType = UniqueCheckType.SYMBOL | UniqueCheckType.REPLACE;
16
+ static UniqueSymbol = Symbol.for('Animation2DComponent');
17
+ static Lifecycle = ComponentLifecycleFlags.EntityRemoveComponent | ComponentLifecycleFlags.EntityRemoveFromWorld;
18
+ animation;
19
+ playing;
20
+ loop;
21
+ speed;
22
+ currentTime;
23
+ completed = false;
24
+ runtimeExtensions;
25
+ _runtime = null;
26
+ _needsApply = true;
27
+ _forceParticleSeek = false;
28
+ constructor(source, options = {}) {
29
+ super('Animation2DComponent');
30
+ this.animation = isParsed(source) ? source : parseAnimation(source);
31
+ this.playing = options.autoplay ?? true;
32
+ this.loop = options.loop ?? this.animation.endBehavior === 'loop';
33
+ this.speed = finiteSpeed(options.speed ?? 1);
34
+ this.currentTime = clampTime(options.startTime ?? 0, this.animation.duration);
35
+ this.runtimeExtensions = options.runtimeExtensions;
36
+ }
37
+ get runtimeStats() {
38
+ return this._runtime?.stats ?? {
39
+ nodeCount: 0,
40
+ visualCount: 0,
41
+ unsupportedComponentCount: 0,
42
+ pendingResourceCount: 0,
43
+ failedResourceCount: 0,
44
+ textCount: 0,
45
+ particleCount: 0,
46
+ audioCount: 0,
47
+ };
48
+ }
49
+ play() {
50
+ if (this.completed) {
51
+ this.currentTime = 0;
52
+ this._needsApply = true;
53
+ this._forceParticleSeek = true;
54
+ }
55
+ this.playing = true;
56
+ this.completed = false;
57
+ this._runtime?.setPlaying(true);
58
+ return this;
59
+ }
60
+ pause() {
61
+ this.playing = false;
62
+ this._runtime?.setPlaying(false);
63
+ return this;
64
+ }
65
+ seek(seconds) {
66
+ this.currentTime = clampTime(seconds, this.animation.duration);
67
+ this.completed = false;
68
+ this._needsApply = true;
69
+ this._forceParticleSeek = true;
70
+ return this;
71
+ }
72
+ setSpeed(speed) {
73
+ this.speed = finiteSpeed(speed);
74
+ return this;
75
+ }
76
+ onEntityRemoveComponent(_entity, component) {
77
+ if (component === this)
78
+ this._disposeRuntime();
79
+ }
80
+ onEntityRemoveFromWorld(_entity, _world) {
81
+ this._disposeRuntime();
82
+ }
83
+ clone() {
84
+ const clone = new Animation2DComponent(this.animation, {
85
+ autoplay: this.playing,
86
+ loop: this.loop,
87
+ speed: this.speed,
88
+ startTime: this.currentTime,
89
+ ...(this.runtimeExtensions ? { runtimeExtensions: this.runtimeExtensions } : {}),
90
+ });
91
+ clone.disabled = this.disabled;
92
+ return clone;
93
+ }
94
+ _disposeRuntime() {
95
+ this._runtime?.destroy();
96
+ this._runtime = null;
97
+ }
98
+ }
99
+ function isParsed(source) {
100
+ return typeof source === 'object' && source !== null && 'source' in source;
101
+ }
102
+ function finiteSpeed(value) {
103
+ if (!Number.isFinite(value) || value < 0)
104
+ throw new RangeError('Animation2DComponent speed must be finite and non-negative.');
105
+ return value;
106
+ }
107
+ function clampTime(value, duration) {
108
+ if (!Number.isFinite(value))
109
+ throw new RangeError('Animation2DComponent time must be finite.');
110
+ return Math.max(0, Math.min(duration, value));
111
+ }
112
+
113
+ class Animation2DSystem extends System {
114
+ _assetManager;
115
+ constructor(options = {}) {
116
+ super({ all: [Animation2DComponent] });
117
+ this.name = 'Animation2DSystem';
118
+ if (options.priority !== undefined)
119
+ this.priority = options.priority;
120
+ this._assetManager = options.assetManager;
121
+ }
122
+ update(world, _time, delta) {
123
+ if (this.disabled)
124
+ return this;
125
+ const entities = this.entitySet.get(world);
126
+ if (!entities)
127
+ return this;
128
+ for (const entity of entities)
129
+ this._updateEntity(entity, delta);
130
+ return this;
131
+ }
132
+ destroy() {
133
+ for (const world of this.usedBy) {
134
+ const entities = this.entitySet.get(world);
135
+ if (!entities)
136
+ continue;
137
+ for (const entity of entities)
138
+ entity.getComponent(Animation2DComponent)?._disposeRuntime();
139
+ }
140
+ return super.destroy();
141
+ }
142
+ _updateEntity(entity, delta) {
143
+ const component = entity.getComponent(Animation2DComponent);
144
+ if (!component)
145
+ return;
146
+ if (component.disabled) {
147
+ component._disposeRuntime();
148
+ return;
149
+ }
150
+ if (component.completed && component.animation.endBehavior === 'destroy' && !component.playing)
151
+ return;
152
+ component._runtime ??= new Animation2DRuntime(entity, component.animation, component.runtimeExtensions, this._assetManager);
153
+ if (component.playing && delta > 0 && component.speed > 0) {
154
+ component.currentTime += delta * 0.001 * component.speed;
155
+ if (component.currentTime >= component.animation.duration) {
156
+ if (component.loop) {
157
+ component.currentTime %= component.animation.duration;
158
+ component._forceParticleSeek = true;
159
+ }
160
+ else {
161
+ component.currentTime = component.animation.duration;
162
+ component.playing = false;
163
+ component.completed = true;
164
+ if (component.animation.endBehavior === 'destroy') {
165
+ component._disposeRuntime();
166
+ component._needsApply = false;
167
+ return;
168
+ }
169
+ }
170
+ }
171
+ component._needsApply = true;
172
+ }
173
+ if (!component._needsApply)
174
+ return;
175
+ component._runtime.apply(component.currentTime, component.playing, component.speed, component._forceParticleSeek);
176
+ component._needsApply = false;
177
+ component._forceParticleSeek = false;
178
+ }
179
+ }
180
+
181
+ const SHADER = Animation2dWgsl;
182
+ const MAX_COMPOSITE_LAYERS = 8;
183
+ const COMPOSITE_PARAMS_OFFSET = 28;
184
+ const COMPOSITE_EXPANSION_OFFSET = COMPOSITE_PARAMS_OFFSET + MAX_COMPOSITE_LAYERS * 4;
185
+ const GRADIENT_PARAMS_OFFSET = COMPOSITE_EXPANSION_OFFSET + MAX_COMPOSITE_LAYERS;
186
+ const GRADIENT_GEOMETRY_OFFSET = GRADIENT_PARAMS_OFFSET + 4;
187
+ const GRADIENT_COLORS_OFFSET = GRADIENT_GEOMETRY_OFFSET + 4;
188
+ const GRADIENT_OFFSETS_OFFSET = GRADIENT_COLORS_OFFSET + 8 * 4;
189
+ const EFFECT_KINDS_OFFSET = GRADIENT_OFFSETS_OFFSET + 8;
190
+ const EFFECT_DATA_OFFSET = EFFECT_KINDS_OFFSET + 8;
191
+ const EFFECT_FLOATS = 24;
192
+ const MAX_EFFECTS = 8;
193
+ const OBJECT_FLOATS = EFFECT_DATA_OFFSET + MAX_EFFECTS * EFFECT_FLOATS;
194
+ const OBJECT_BYTES = OBJECT_FLOATS * 4;
195
+ function getAnimation2DSharedGpu(device, tracker) {
196
+ return getExtensionSharedRendererResource(device, 'Animation2D.sharedGpu:v1', () => createAnimation2DSharedGpu(device, tracker));
197
+ }
198
+ function createAnimation2DSharedGpu(device, tracker) {
199
+ const cameraLayout = createCamera2DLayout(device);
200
+ const objectLayout = device.createBindGroupLayout({
201
+ entries: [{
202
+ binding: 0,
203
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
204
+ buffer: { type: 'uniform' },
205
+ }],
206
+ });
207
+ const textureLayout = device.createBindGroupLayout({ entries: [
208
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },
209
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },
210
+ ] });
211
+ const compositeLayout = device.createBindGroupLayout({ entries: [
212
+ ...Array.from({ length: MAX_COMPOSITE_LAYERS }, (_, binding) => ({
213
+ binding,
214
+ visibility: GPUShaderStage.FRAGMENT,
215
+ texture: { sampleType: 'float' },
216
+ })),
217
+ { binding: MAX_COMPOSITE_LAYERS, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },
218
+ ] });
219
+ const sampler = device.createSampler({ magFilter: 'linear', minFilter: 'linear' });
220
+ const shader = device.createShaderModule({ label: 'Animation2D.shader', code: SHADER });
221
+ const pipelineLayout = device.createPipelineLayout({
222
+ bindGroupLayouts: [cameraLayout, objectLayout, textureLayout, compositeLayout],
223
+ });
224
+ const createTextureBindGroup = (texture) => device.createBindGroup({
225
+ layout: textureLayout,
226
+ entries: [
227
+ { binding: 0, resource: texture.createView() },
228
+ { binding: 1, resource: sampler },
229
+ ],
230
+ });
231
+ const whiteTexture = device.createTexture({
232
+ size: [1, 1],
233
+ format: 'rgba8unorm',
234
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
235
+ });
236
+ tracker?.trackTexture(whiteTexture, 'Animation2D.sharedWhiteTexture', 4);
237
+ device.queue.writeTexture({ texture: whiteTexture }, new Uint8Array([255, 255, 255, 255]), { bytesPerRow: 4 }, [1, 1]);
238
+ const whiteBindGroup = createTextureBindGroup(whiteTexture);
239
+ const emptyCompositeBindGroup = device.createBindGroup({
240
+ layout: compositeLayout,
241
+ entries: [
242
+ ...Array.from({ length: MAX_COMPOSITE_LAYERS }, (_, binding) => ({
243
+ binding,
244
+ resource: whiteTexture.createView(),
245
+ })),
246
+ { binding: MAX_COMPOSITE_LAYERS, resource: sampler },
247
+ ],
248
+ });
249
+ const transparentTexture = device.createTexture({
250
+ size: [1, 1],
251
+ format: 'rgba8unorm',
252
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
253
+ });
254
+ tracker?.trackTexture(transparentTexture, 'Animation2D.sharedTransparentTexture', 4);
255
+ device.queue.writeTexture({ texture: transparentTexture }, new Uint8Array([255, 255, 255, 0]), { bytesPerRow: 4 }, [1, 1]);
256
+ const transparentBindGroup = createTextureBindGroup(transparentTexture);
257
+ return {
258
+ cameraLayout,
259
+ objectLayout,
260
+ textureLayout,
261
+ compositeLayout,
262
+ sampler,
263
+ shader,
264
+ pipelineLayout,
265
+ pipelines: new Map(),
266
+ whiteTexture,
267
+ whiteBindGroup,
268
+ emptyCompositeBindGroup,
269
+ transparentTexture,
270
+ transparentBindGroup,
271
+ destroy() {
272
+ tracker?.untrackTexture(whiteTexture);
273
+ tracker?.untrackTexture(transparentTexture);
274
+ whiteTexture.destroy();
275
+ transparentTexture.destroy();
276
+ this.pipelines.clear();
277
+ },
278
+ };
279
+ }
280
+ class Animation2DRenderSystem extends RenderSystem2DBase {
281
+ maxMaskTargets;
282
+ rendererReady = false;
283
+ cameraGpu;
284
+ objectLayout;
285
+ textureLayout;
286
+ compositeLayout;
287
+ sampler;
288
+ whiteTexture;
289
+ whiteBindGroup;
290
+ emptyCompositeBindGroup;
291
+ transparentTexture;
292
+ transparentBindGroup;
293
+ shader;
294
+ pipelineLayout;
295
+ pipelines;
296
+ entityGpu = new Map();
297
+ geometryGpu = new Map();
298
+ textureBindGroups = new WeakMap();
299
+ maskTargets = new Map();
300
+ effectTargets = new Map();
301
+ items = [];
302
+ sourceItems = new Map();
303
+ sourceGroupPool = [];
304
+ activeMaskTargets = new Map();
305
+ compositeBindGroups = new Map();
306
+ liveGeometryIds = new Set();
307
+ _visualCount = 0;
308
+ _maskTargetCount = 0;
309
+ _compositeLayerCount = 0;
310
+ _droppedCompositeCount = 0;
311
+ _maskPixels = 0;
312
+ _effectTargetCount = 0;
313
+ _droppedEffectCount = 0;
314
+ _effectPixels = 0;
315
+ constructor(engine, cameraEntity, options = {}) {
316
+ // Optional extension subpaths can be bundled as independent module graphs.
317
+ // Query by the stable global component symbol so a visual created by the
318
+ // HYA state-machine subpath is visible to the animation renderer subpath.
319
+ super({ all: [AnimationVisual2D.UniqueSymbol] }, engine, cameraEntity, { ...options, loadOp: options.loadOp ?? 'load' }, 'Animation2DRenderSystem');
320
+ this.maxMaskTargets = positiveInteger(options.maxMaskTargets ?? 32, 'maxMaskTargets');
321
+ }
322
+ get stats() {
323
+ return Object.freeze({
324
+ visualCount: this._visualCount,
325
+ maskTargetCount: this._maskTargetCount,
326
+ compositeLayerCount: this._compositeLayerCount,
327
+ droppedCompositeCount: this._droppedCompositeCount,
328
+ maskPixels: this._maskPixels,
329
+ effectTargetCount: this._effectTargetCount,
330
+ droppedEffectCount: this._droppedEffectCount,
331
+ effectPixels: this._effectPixels,
332
+ });
333
+ }
334
+ get renderPipelineOptions() {
335
+ return { pass: 'isolated', loadOp: this.loadOp, sort: this.priority };
336
+ }
337
+ record(world, context) {
338
+ if (this.disabled)
339
+ return this;
340
+ if (!this.rendererReady)
341
+ this.prepare();
342
+ if (!this.writeCameraBuffer(context.device.queue, this.cameraGpu.buffer, context))
343
+ return this;
344
+ const liveEntities = this.beginLiveEntityTracking();
345
+ this.items.length = 0;
346
+ const entities = this.entitySet.get(world);
347
+ if (entities)
348
+ for (const entity of entities) {
349
+ if (!this.isEntityRenderable(entity))
350
+ continue;
351
+ const visual = entity.getComponent(AnimationVisual2D.UniqueSymbol);
352
+ if (!visual)
353
+ continue;
354
+ this.items.push({ entity, visual });
355
+ this.markEntityLive(entity);
356
+ }
357
+ this.items.sort(compareItems);
358
+ const frame = context.frameData?.frameId ?? 0;
359
+ this.sourceItems.clear();
360
+ let sourceGroupCount = 0;
361
+ for (const item of this.items) {
362
+ if (!item.visual.sourceOnly)
363
+ continue;
364
+ const key = visualNodeKey(item.visual);
365
+ let group = this.sourceItems.get(key);
366
+ if (!group) {
367
+ group = this.sourceGroupPool[sourceGroupCount] ?? [];
368
+ group.length = 0;
369
+ this.sourceGroupPool[sourceGroupCount++] = group;
370
+ this.sourceItems.set(key, group);
371
+ }
372
+ group.push(item);
373
+ }
374
+ const maskTargets = this.activeMaskTargets;
375
+ maskTargets.clear();
376
+ let maskPixels = 0;
377
+ let maskCount = 0;
378
+ let droppedCompositeCount = 0;
379
+ let compositeLayerCount = 0;
380
+ let effectCount = 0;
381
+ let droppedEffectCount = 0;
382
+ let effectPixels = 0;
383
+ for (const sourceKey of orderSourceGroups(this.sourceItems)) {
384
+ const sources = this.sourceItems.get(sourceKey);
385
+ if (maskCount >= this.maxMaskTargets)
386
+ break;
387
+ const targetKey = `${context.view?.key ?? 'default'}:${sourceKey}`;
388
+ const target = this.getMaskTarget(targetKey, context, frame);
389
+ maskPixels += target.width * target.height;
390
+ maskCount++;
391
+ let initialized = false;
392
+ for (const source of sources) {
393
+ const resolved = this.resolveCompositeBindGroup(source.visual, maskTargets, context.view?.key ?? 'default');
394
+ if (!resolved) {
395
+ droppedCompositeCount++;
396
+ continue;
397
+ }
398
+ compositeLayerCount += source.visual.compositeLayers.length;
399
+ const effectTarget = source.visual.effects.length > 0 && !context.passEncoder
400
+ ? this.renderEffectStack(source, context, resolved, frame)
401
+ : null;
402
+ if (effectTarget) {
403
+ effectCount++;
404
+ if (effectPixels === 0)
405
+ effectPixels = effectTarget.width * effectTarget.height * 2;
406
+ }
407
+ else if (source.visual.effects.length > 0)
408
+ droppedEffectCount++;
409
+ const pass = this.beginMaskSegment(context, target, sourceKey, initialized);
410
+ if (effectTarget) {
411
+ pass.setPipeline(this.getPresentPipeline('rgba8unorm', 1, false, false));
412
+ this.drawEffectResult(pass, source.entity, effectTarget.texture);
413
+ }
414
+ else {
415
+ pass.setPipeline(this.getPipeline('rgba8unorm', 1, false, false));
416
+ this.draw(pass, source.entity, source.visual, context, resolved, true);
417
+ }
418
+ pass.end();
419
+ initialized = true;
420
+ }
421
+ if (!initialized)
422
+ this.beginMaskSegment(context, target, sourceKey, false).end();
423
+ maskTargets.set(sourceKey, target);
424
+ }
425
+ const pending = [];
426
+ const outputFormat = context.view?.target.format ?? this.engine.format;
427
+ let firstOutputSegment = true;
428
+ let visualCount = 0;
429
+ const flushOutput = (effectItem, effectTexture) => {
430
+ if (pending.length === 0 && !effectItem && !firstOutputSegment)
431
+ return;
432
+ const { passEncoder, ownsPass } = this.beginOutputSegment(context, firstOutputSegment);
433
+ firstOutputSegment = false;
434
+ applyViewport(passEncoder, context);
435
+ passEncoder.setBindGroup(0, this.cameraGpu.bindGroup);
436
+ for (const entry of pending) {
437
+ passEncoder.setPipeline(this.getPipeline(outputFormat, context.view?.sampleCount ?? this.engine.msaaSamples, true, context.view?.reverseZ ?? this.engine.reverseZ));
438
+ this.draw(passEncoder, entry.item.entity, entry.item.visual, context, entry.composite, false);
439
+ visualCount++;
440
+ }
441
+ pending.length = 0;
442
+ if (effectItem && effectTexture) {
443
+ passEncoder.setPipeline(this.getPresentPipeline(outputFormat, context.view?.sampleCount ?? this.engine.msaaSamples, true, context.view?.reverseZ ?? this.engine.reverseZ));
444
+ this.drawEffectResult(passEncoder, effectItem.entity, effectTexture);
445
+ visualCount++;
446
+ }
447
+ if (ownsPass)
448
+ passEncoder.end();
449
+ };
450
+ for (const item of this.items) {
451
+ const { visual } = item;
452
+ if (visual.sourceOnly)
453
+ continue;
454
+ const resolved = this.resolveCompositeBindGroup(visual, maskTargets, context.view?.key ?? 'default');
455
+ if (!resolved) {
456
+ droppedCompositeCount++;
457
+ continue;
458
+ }
459
+ compositeLayerCount += visual.compositeLayers.length;
460
+ if (visual.effects.length > 0 && !context.passEncoder) {
461
+ const effectTarget = this.renderEffectStack(item, context, resolved, frame);
462
+ effectCount++;
463
+ if (effectPixels === 0)
464
+ effectPixels = effectTarget.width * effectTarget.height * 2;
465
+ flushOutput(item, effectTarget.texture);
466
+ }
467
+ else {
468
+ if (visual.effects.length > 0)
469
+ droppedEffectCount++;
470
+ pending.push({ item, composite: resolved });
471
+ }
472
+ }
473
+ flushOutput();
474
+ this.releaseEntityGpuEntriesNotIn(this.entityGpu, gpu => this.destroyEntityGpu(gpu), liveEntities);
475
+ this.sweepGeometryGpu();
476
+ this.sweepMaskTargets(frame, context);
477
+ this.sweepEffectTargets(frame, context);
478
+ this._visualCount = visualCount;
479
+ this._maskTargetCount = maskCount;
480
+ this._compositeLayerCount = compositeLayerCount;
481
+ this._droppedCompositeCount = droppedCompositeCount;
482
+ this._maskPixels = maskPixels;
483
+ this._effectTargetCount = effectCount > 0 ? 1 : 0;
484
+ this._droppedEffectCount = droppedEffectCount;
485
+ this._effectPixels = effectPixels;
486
+ return this;
487
+ }
488
+ destroy() {
489
+ this.releaseGpuResourcesForRecovery();
490
+ return super.destroy();
491
+ }
492
+ releaseGpuResourcesForRecovery() {
493
+ if (!this.rendererReady)
494
+ return;
495
+ destroyCamera2DGpu(this.cameraGpu);
496
+ this.destroyEntityGpuEntries(this.entityGpu, gpu => this.destroyEntityGpu(gpu));
497
+ for (const gpu of this.geometryGpu.values())
498
+ this.destroyGeometryGpu(gpu);
499
+ this.geometryGpu.clear();
500
+ for (const target of this.maskTargets.values())
501
+ this.destroyMaskTarget(target);
502
+ this.maskTargets.clear();
503
+ for (const target of this.effectTargets.values())
504
+ this.destroyEffectTarget(target);
505
+ this.effectTargets.clear();
506
+ this.activeMaskTargets.clear();
507
+ this.compositeBindGroups.clear();
508
+ this.textureBindGroups = new WeakMap();
509
+ this.rendererReady = false;
510
+ }
511
+ prepare() {
512
+ const device = requireEngineDevice(this.engine);
513
+ const tracker = getExtensionGPUResourceTracker(this.engine);
514
+ const shared = getAnimation2DSharedGpu(device, tracker);
515
+ this.cameraGpu = createCamera2DGpu(device, tracker, shared.cameraLayout);
516
+ this.objectLayout = shared.objectLayout;
517
+ this.textureLayout = shared.textureLayout;
518
+ this.compositeLayout = shared.compositeLayout;
519
+ this.sampler = shared.sampler;
520
+ this.whiteTexture = shared.whiteTexture;
521
+ this.whiteBindGroup = shared.whiteBindGroup;
522
+ this.emptyCompositeBindGroup = shared.emptyCompositeBindGroup;
523
+ this.transparentTexture = shared.transparentTexture;
524
+ this.transparentBindGroup = shared.transparentBindGroup;
525
+ this.shader = shared.shader;
526
+ this.pipelineLayout = shared.pipelineLayout;
527
+ this.pipelines = shared.pipelines;
528
+ this.rendererReady = true;
529
+ }
530
+ beginMaskSegment(context, target, sourceKey, initialized) {
531
+ const pass = context.encoder.beginRenderPass({
532
+ label: `Animation2D.mask:${sourceKey}`,
533
+ colorAttachments: [{
534
+ view: target.view,
535
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
536
+ loadOp: initialized ? 'load' : 'clear',
537
+ storeOp: 'store',
538
+ }],
539
+ });
540
+ applyViewport(pass, context);
541
+ pass.setBindGroup(0, this.cameraGpu.bindGroup);
542
+ return pass;
543
+ }
544
+ beginOutputSegment(context, first) {
545
+ if (first)
546
+ return beginRenderCommandPass(context);
547
+ if (!context.descriptor)
548
+ throw new Error('Animation2D ordered effects require an isolated render-pass descriptor.');
549
+ return { passEncoder: context.encoder.beginRenderPass(cloneRenderPassDescriptor(context.descriptor, 'load')), ownsPass: true };
550
+ }
551
+ getPipeline(format, sampleCount, withDepth, reverseZ) {
552
+ const depthFormat = withDepth ? this.engine.getDepthFormat(reverseZ) : 'none';
553
+ const key = `visual:${format}:${sampleCount}:${depthFormat}:${reverseZ ? 1 : 0}`;
554
+ const cached = this.pipelines.get(key);
555
+ if (cached)
556
+ return cached;
557
+ const pipeline = requireEngineDevice(this.engine).createRenderPipeline({
558
+ label: `Animation2D.pipeline:${key}`,
559
+ layout: this.pipelineLayout,
560
+ vertex: { module: this.shader, entryPoint: 'vs_main', buffers: [{
561
+ arrayStride: 16,
562
+ attributes: [
563
+ { shaderLocation: 0, offset: 0, format: 'float32x2' },
564
+ { shaderLocation: 1, offset: 8, format: 'float32x2' },
565
+ ],
566
+ }] },
567
+ fragment: { module: this.shader, entryPoint: 'fs_main', targets: [{ format, blend: {
568
+ color: { srcFactor: 'src-alpha', dstFactor: 'one-minus-src-alpha', operation: 'add' },
569
+ alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
570
+ } }] },
571
+ primitive: { topology: 'triangle-list', cullMode: 'none' },
572
+ ...(withDepth ? { depthStencil: {
573
+ format: depthFormat,
574
+ depthWriteEnabled: false,
575
+ depthCompare: 'always',
576
+ } } : {}),
577
+ multisample: { count: sampleCount },
578
+ });
579
+ this.pipelines.set(key, pipeline);
580
+ return pipeline;
581
+ }
582
+ getEffectPipeline() {
583
+ const key = 'effect:rgba8unorm';
584
+ const cached = this.pipelines.get(key);
585
+ if (cached)
586
+ return cached;
587
+ const pipeline = requireEngineDevice(this.engine).createRenderPipeline({
588
+ label: 'Animation2D.effectPipeline',
589
+ layout: this.pipelineLayout,
590
+ vertex: { module: this.shader, entryPoint: 'vs_effect' },
591
+ fragment: { module: this.shader, entryPoint: 'fs_effect', targets: [{ format: 'rgba8unorm' }] },
592
+ primitive: { topology: 'triangle-list', cullMode: 'none' },
593
+ });
594
+ this.pipelines.set(key, pipeline);
595
+ return pipeline;
596
+ }
597
+ getPresentPipeline(format, sampleCount, withDepth, reverseZ) {
598
+ const depthFormat = withDepth ? this.engine.getDepthFormat(reverseZ) : 'none';
599
+ const key = `present:${format}:${sampleCount}:${depthFormat}:${reverseZ ? 1 : 0}`;
600
+ const cached = this.pipelines.get(key);
601
+ if (cached)
602
+ return cached;
603
+ const pipeline = requireEngineDevice(this.engine).createRenderPipeline({
604
+ label: `Animation2D.presentPipeline:${key}`,
605
+ layout: this.pipelineLayout,
606
+ vertex: { module: this.shader, entryPoint: 'vs_effect' },
607
+ fragment: { module: this.shader, entryPoint: 'fs_present', targets: [{ format, blend: {
608
+ color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
609
+ alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
610
+ } }] },
611
+ primitive: { topology: 'triangle-list', cullMode: 'none' },
612
+ ...(withDepth ? { depthStencil: {
613
+ format: depthFormat, depthWriteEnabled: false, depthCompare: 'always',
614
+ } } : {}),
615
+ multisample: { count: sampleCount },
616
+ });
617
+ this.pipelines.set(key, pipeline);
618
+ return pipeline;
619
+ }
620
+ draw(pass, entity, visual, context, compositeBindGroup, outputMask) {
621
+ const device = requireEngineDevice(this.engine);
622
+ const object = this.getEntityGpu(entity);
623
+ object.data.set(this.getWorldMatrix2D(entity, context), 0);
624
+ object.data.set(visual.color, 16);
625
+ object.data[20] = visual.compositeLayers.length;
626
+ object.data[21] = outputMask ? 1 : 0;
627
+ object.data[22] = Math.min(MAX_EFFECTS, visual.effects.length);
628
+ object.data[23] = 0;
629
+ object.data.set(visual.uvRect, 24);
630
+ object.data.fill(0, COMPOSITE_PARAMS_OFFSET);
631
+ for (let index = 0; index < visual.compositeLayers.length; index++) {
632
+ const layer = visual.compositeLayers[index];
633
+ const offset = COMPOSITE_PARAMS_OFFSET + index * 4;
634
+ object.data[offset] = compositeModeCode(layer.mode);
635
+ object.data[offset + 1] = compositeOperationCode(layer.operation ?? 'add');
636
+ object.data[offset + 2] = layer.feather?.[0] ?? 0;
637
+ object.data[offset + 3] = layer.feather?.[1] ?? 0;
638
+ object.data[COMPOSITE_EXPANSION_OFFSET + index] = layer.expansion ?? 0;
639
+ }
640
+ const gradient = visual.gradient;
641
+ if (gradient) {
642
+ const stopCount = Math.min(8, gradient.stops.length / 5);
643
+ object.data[GRADIENT_PARAMS_OFFSET] = gradient.kind === 'linear' ? 1 : 2;
644
+ object.data[GRADIENT_PARAMS_OFFSET + 1] = stopCount;
645
+ object.data[GRADIENT_PARAMS_OFFSET + 2] = gradient.opacity;
646
+ object.data.set(gradient.start, GRADIENT_GEOMETRY_OFFSET);
647
+ object.data.set(gradient.end, GRADIENT_GEOMETRY_OFFSET + 2);
648
+ for (let index = 0; index < stopCount; index++) {
649
+ const source = index * 5;
650
+ const colorOffset = GRADIENT_COLORS_OFFSET + index * 4;
651
+ object.data[colorOffset] = gradient.stops[source + 1] ?? 1;
652
+ object.data[colorOffset + 1] = gradient.stops[source + 2] ?? 1;
653
+ object.data[colorOffset + 2] = gradient.stops[source + 3] ?? 1;
654
+ object.data[colorOffset + 3] = gradient.stops[source + 4] ?? 1;
655
+ object.data[GRADIENT_OFFSETS_OFFSET + index] = gradient.stops[source] ?? 0;
656
+ }
657
+ }
658
+ for (let index = 0; index < Math.min(MAX_EFFECTS, visual.effects.length); index++) {
659
+ const effect = visual.effects[index];
660
+ object.data[EFFECT_KINDS_OFFSET + index] = effectKindCode(effect.kind);
661
+ object.data.set(effect.values.subarray(0, EFFECT_FLOATS), EFFECT_DATA_OFFSET + index * EFFECT_FLOATS);
662
+ }
663
+ device.queue.writeBuffer(object.buffer, 0, object.data);
664
+ const geometry = this.getGeometryGpu(visual);
665
+ pass.setBindGroup(1, object.bindGroup);
666
+ pass.setBindGroup(2, this.getVisualTextureBindGroup(entity, visual, object, context));
667
+ pass.setBindGroup(3, compositeBindGroup);
668
+ pass.setVertexBuffer(0, geometry.vertexBuffer);
669
+ if (geometry.indexBuffer && geometry.indexCount > 0) {
670
+ pass.setIndexBuffer(geometry.indexBuffer, geometry.indexFormat);
671
+ pass.drawIndexed(geometry.indexCount);
672
+ }
673
+ else {
674
+ pass.draw(geometry.vertexCount);
675
+ }
676
+ }
677
+ renderEffectStack(item, context, compositeBindGroup, frame) {
678
+ // One reusable ping-pong pair per view. Command ordering presents each result
679
+ // before the next visual overwrites it, so memory is O(view), not O(entities).
680
+ const key = context.view?.key ?? 'default';
681
+ const target = this.getEffectTarget(key, context, frame);
682
+ const sourcePass = context.encoder.beginRenderPass({
683
+ label: `Animation2D.effectSource:${item.entity.id}`,
684
+ colorAttachments: [{
685
+ view: target.views[0], clearValue: { r: 0, g: 0, b: 0, a: 0 }, loadOp: 'clear', storeOp: 'store',
686
+ }],
687
+ });
688
+ applyViewport(sourcePass, context);
689
+ sourcePass.setPipeline(this.getPipeline('rgba8unorm', 1, false, false));
690
+ sourcePass.setBindGroup(0, this.cameraGpu.bindGroup);
691
+ this.draw(sourcePass, item.entity, item.visual, context, compositeBindGroup, false);
692
+ sourcePass.end();
693
+ const object = this.getEntityGpu(item.entity);
694
+ let sourceIndex = 0;
695
+ for (let index = 0; index < Math.min(MAX_EFFECTS, item.visual.effects.length); index++) {
696
+ const outputIndex = sourceIndex === 0 ? 1 : 0;
697
+ const pass = context.encoder.beginRenderPass({
698
+ label: `Animation2D.effect:${item.entity.id}:${index}`,
699
+ colorAttachments: [{
700
+ view: target.views[outputIndex], clearValue: { r: 0, g: 0, b: 0, a: 0 }, loadOp: 'clear', storeOp: 'store',
701
+ }],
702
+ });
703
+ applyViewport(pass, context);
704
+ pass.setPipeline(this.getEffectPipeline());
705
+ pass.setBindGroup(0, this.cameraGpu.bindGroup);
706
+ pass.setBindGroup(1, object.bindGroup);
707
+ pass.setBindGroup(2, this.getTextureBindGroup(target.textures[sourceIndex]));
708
+ pass.setBindGroup(3, this.emptyCompositeBindGroup);
709
+ pass.draw(3, 1, 0, index);
710
+ pass.end();
711
+ sourceIndex = outputIndex;
712
+ }
713
+ return { texture: target.textures[sourceIndex], width: target.width, height: target.height };
714
+ }
715
+ drawEffectResult(pass, entity, texture) {
716
+ pass.setBindGroup(1, this.getEntityGpu(entity).bindGroup);
717
+ pass.setBindGroup(2, this.getTextureBindGroup(texture));
718
+ pass.setBindGroup(3, this.emptyCompositeBindGroup);
719
+ pass.draw(3);
720
+ }
721
+ getEntityGpu(entity) {
722
+ return this.getOrCreateEntityGpu(this.entityGpu, entity, () => {
723
+ const device = requireEngineDevice(this.engine);
724
+ const buffer = device.createBuffer({ size: OBJECT_BYTES, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
725
+ getExtensionGPUResourceTracker(this.engine)?.trackBuffer(buffer, 'Animation2D.objectBuffer', OBJECT_BYTES);
726
+ return {
727
+ buffer,
728
+ bindGroup: device.createBindGroup({ layout: this.objectLayout, entries: [{ binding: 0, resource: { buffer } }] }),
729
+ data: new Float32Array(OBJECT_FLOATS),
730
+ externalTexture: null,
731
+ externalSource: null,
732
+ externalVersion: -1,
733
+ externalWidth: 0,
734
+ externalHeight: 0,
735
+ externalBindGroup: null,
736
+ };
737
+ });
738
+ }
739
+ getVisualTextureBindGroup(entity, visual, object, context) {
740
+ const texture = visual.resolveTexture();
741
+ if (texture)
742
+ return this.getTextureBindGroup(texture);
743
+ const material = visual.textMaterial;
744
+ const source = material?.texture;
745
+ if (material && isExternalImageSource(source)) {
746
+ const size = externalImageSize(source);
747
+ if (!object.externalTexture || object.externalWidth !== size.width || object.externalHeight !== size.height) {
748
+ const retiredTexture = object.externalTexture;
749
+ if (retiredTexture) {
750
+ const retire = () => {
751
+ getExtensionGPUResourceTracker(this.engine)?.untrackTexture(retiredTexture);
752
+ retiredTexture.destroy();
753
+ };
754
+ if (context.afterSubmit) {
755
+ context.afterSubmit(queue => {
756
+ void queue.onSubmittedWorkDone()
757
+ .then(retire)
758
+ .catch(retire);
759
+ });
760
+ }
761
+ else {
762
+ retire();
763
+ }
764
+ }
765
+ object.externalTexture = requireEngineDevice(this.engine).createTexture({
766
+ label: `Animation2D.text:${entity.id}`,
767
+ size: [size.width, size.height],
768
+ format: 'rgba8unorm',
769
+ // WebGPU copyExternalImageToTexture requires RENDER_ATTACHMENT in addition
770
+ // to COPY_DST for browser-backed image sources.
771
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
772
+ });
773
+ getExtensionGPUResourceTracker(this.engine)?.trackTexture(object.externalTexture, 'Animation2D.textTexture', size.width * size.height * 4);
774
+ object.externalBindGroup = this.createTextureBindGroup(object.externalTexture);
775
+ object.externalWidth = size.width;
776
+ object.externalHeight = size.height;
777
+ object.externalVersion = -1;
778
+ }
779
+ if (object.externalSource !== source || object.externalVersion !== material.textureVersion) {
780
+ requireEngineDevice(this.engine).queue.copyExternalImageToTexture({ source }, { texture: object.externalTexture }, [size.width, size.height]);
781
+ object.externalSource = source;
782
+ object.externalVersion = material.textureVersion;
783
+ }
784
+ return object.externalBindGroup;
785
+ }
786
+ return visual.requiresTexture ? this.transparentBindGroup : this.whiteBindGroup;
787
+ }
788
+ getGeometryGpu(visual) {
789
+ const geometry = visual.geometry;
790
+ let gpu = this.geometryGpu.get(geometry.id);
791
+ if (gpu && gpu.version === geometry.version)
792
+ return gpu;
793
+ if (gpu)
794
+ this.destroyGeometryGpu(gpu);
795
+ const vertices = buildVertices(geometry.positions);
796
+ const device = requireEngineDevice(this.engine);
797
+ const vertexBuffer = device.createBuffer({ size: vertices.byteLength, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST });
798
+ device.queue.writeBuffer(vertexBuffer, 0, vertices);
799
+ let indexBuffer = null;
800
+ if (geometry.indices?.length) {
801
+ const allocationBytes = alignUp4(geometry.indices.byteLength);
802
+ indexBuffer = device.createBuffer({ size: allocationBytes, usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST });
803
+ writeBufferAligned(device.queue, indexBuffer, geometry.indices);
804
+ }
805
+ const tracker = getExtensionGPUResourceTracker(this.engine);
806
+ tracker?.trackBuffer(vertexBuffer, 'Animation2D.vertexBuffer', vertices.byteLength);
807
+ if (indexBuffer && geometry.indices)
808
+ tracker?.trackBuffer(indexBuffer, 'Animation2D.indexBuffer', alignUp4(geometry.indices.byteLength));
809
+ gpu = {
810
+ vertexBuffer,
811
+ indexBuffer,
812
+ indexCount: geometry.indices?.length ?? 0,
813
+ vertexCount: geometry.vertexCount,
814
+ indexFormat: geometry.indices instanceof Uint32Array ? 'uint32' : 'uint16',
815
+ version: geometry.version,
816
+ };
817
+ this.geometryGpu.set(geometry.id, gpu);
818
+ return gpu;
819
+ }
820
+ getTextureBindGroup(texture) {
821
+ let bindGroup = this.textureBindGroups.get(texture);
822
+ if (!bindGroup) {
823
+ bindGroup = this.createTextureBindGroup(texture);
824
+ this.textureBindGroups.set(texture, bindGroup);
825
+ }
826
+ return bindGroup;
827
+ }
828
+ createTextureBindGroup(texture) {
829
+ return requireEngineDevice(this.engine).createBindGroup({ layout: this.textureLayout, entries: [
830
+ { binding: 0, resource: texture.createView() },
831
+ { binding: 1, resource: this.sampler },
832
+ ] });
833
+ }
834
+ resolveCompositeBindGroup(visual, targets, viewKey) {
835
+ if (visual.compositeLayers.length === 0)
836
+ return this.emptyCompositeBindGroup;
837
+ const resolved = [];
838
+ for (const key of visual.compositeKeys) {
839
+ const target = targets.get(key);
840
+ if (!target)
841
+ return null;
842
+ resolved.push(target);
843
+ }
844
+ const key = `${viewKey}:${visual.compositeKeys.join('|')}`;
845
+ let bindGroup = this.compositeBindGroups.get(key);
846
+ if (!bindGroup) {
847
+ bindGroup = this.createCompositeBindGroup(resolved.map(target => target.texture));
848
+ this.compositeBindGroups.set(key, bindGroup);
849
+ }
850
+ return bindGroup;
851
+ }
852
+ createCompositeBindGroup(textures) {
853
+ return requireEngineDevice(this.engine).createBindGroup({
854
+ layout: this.compositeLayout,
855
+ entries: [
856
+ ...Array.from({ length: MAX_COMPOSITE_LAYERS }, (_, binding) => ({
857
+ binding,
858
+ resource: (textures[binding] ?? this.whiteTexture).createView(),
859
+ })),
860
+ { binding: MAX_COMPOSITE_LAYERS, resource: this.sampler },
861
+ ],
862
+ });
863
+ }
864
+ getMaskTarget(key, context, frame) {
865
+ const width = context.view?.target.width ?? this.engine.width;
866
+ const height = context.view?.target.height ?? this.engine.height;
867
+ let target = this.maskTargets.get(key);
868
+ if (target && target.width === width && target.height === height) {
869
+ target.lastFrame = frame;
870
+ return target;
871
+ }
872
+ if (target) {
873
+ this.maskTargets.delete(key);
874
+ this.compositeBindGroups.clear();
875
+ const retired = target;
876
+ context.afterSubmit?.(queue => void queue.onSubmittedWorkDone().then(() => this.destroyMaskTarget(retired)).catch(() => this.destroyMaskTarget(retired)));
877
+ if (!context.afterSubmit)
878
+ this.destroyMaskTarget(retired);
879
+ }
880
+ const texture = requireEngineDevice(this.engine).createTexture({
881
+ label: `Animation2D.mask:${key}`,
882
+ size: [width, height],
883
+ format: 'rgba8unorm',
884
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
885
+ });
886
+ getExtensionGPUResourceTracker(this.engine)?.trackTexture(texture, 'Animation2D.maskTexture', estimateTextureBytes([width, height, 1], 'rgba8unorm'));
887
+ target = { texture, view: texture.createView(), width, height, lastFrame: frame };
888
+ this.maskTargets.set(key, target);
889
+ return target;
890
+ }
891
+ getEffectTarget(key, context, frame) {
892
+ const width = context.view?.target.width ?? this.engine.width;
893
+ const height = context.view?.target.height ?? this.engine.height;
894
+ let target = this.effectTargets.get(key);
895
+ if (target && target.width === width && target.height === height) {
896
+ target.lastFrame = frame;
897
+ return target;
898
+ }
899
+ if (target) {
900
+ this.effectTargets.delete(key);
901
+ const retired = target;
902
+ context.afterSubmit?.(queue => void queue.onSubmittedWorkDone().then(() => this.destroyEffectTarget(retired)).catch(() => this.destroyEffectTarget(retired)));
903
+ if (!context.afterSubmit)
904
+ this.destroyEffectTarget(retired);
905
+ }
906
+ const device = requireEngineDevice(this.engine);
907
+ const makeTexture = (index) => device.createTexture({
908
+ label: `Animation2D.effect:${key}:${index}`,
909
+ size: [width, height],
910
+ format: 'rgba8unorm',
911
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
912
+ });
913
+ const textures = [makeTexture(0), makeTexture(1)];
914
+ const bytes = estimateTextureBytes([width, height, 1], 'rgba8unorm');
915
+ const tracker = getExtensionGPUResourceTracker(this.engine);
916
+ tracker?.trackTexture(textures[0], 'Animation2D.effectTexture', bytes);
917
+ tracker?.trackTexture(textures[1], 'Animation2D.effectTexture', bytes);
918
+ target = { textures, views: [textures[0].createView(), textures[1].createView()], width, height, lastFrame: frame };
919
+ this.effectTargets.set(key, target);
920
+ return target;
921
+ }
922
+ sweepMaskTargets(frame, context) {
923
+ let swept = false;
924
+ for (const [key, target] of this.maskTargets) {
925
+ if (target.lastFrame >= frame - 1)
926
+ continue;
927
+ this.maskTargets.delete(key);
928
+ swept = true;
929
+ context.afterSubmit?.(queue => void queue.onSubmittedWorkDone().then(() => this.destroyMaskTarget(target)).catch(() => this.destroyMaskTarget(target)));
930
+ if (!context.afterSubmit)
931
+ this.destroyMaskTarget(target);
932
+ }
933
+ if (swept)
934
+ this.compositeBindGroups.clear();
935
+ }
936
+ sweepEffectTargets(frame, context) {
937
+ for (const [key, target] of this.effectTargets) {
938
+ if (target.lastFrame >= frame - 1)
939
+ continue;
940
+ this.effectTargets.delete(key);
941
+ context.afterSubmit?.(queue => void queue.onSubmittedWorkDone().then(() => this.destroyEffectTarget(target)).catch(() => this.destroyEffectTarget(target)));
942
+ if (!context.afterSubmit)
943
+ this.destroyEffectTarget(target);
944
+ }
945
+ }
946
+ sweepGeometryGpu() {
947
+ this.liveGeometryIds.clear();
948
+ for (const item of this.items)
949
+ this.liveGeometryIds.add(item.visual.geometry.id);
950
+ for (const [id, gpu] of this.geometryGpu)
951
+ if (!this.liveGeometryIds.has(id)) {
952
+ this.destroyGeometryGpu(gpu);
953
+ this.geometryGpu.delete(id);
954
+ }
955
+ }
956
+ destroyEntityGpu(gpu) {
957
+ getExtensionGPUResourceTracker(this.engine)?.untrackBuffer(gpu.buffer);
958
+ gpu.buffer.destroy();
959
+ if (gpu.externalTexture)
960
+ getExtensionGPUResourceTracker(this.engine)?.untrackTexture(gpu.externalTexture);
961
+ gpu.externalTexture?.destroy();
962
+ }
963
+ destroyGeometryGpu(gpu) {
964
+ const tracker = getExtensionGPUResourceTracker(this.engine);
965
+ tracker?.untrackBuffer(gpu.vertexBuffer);
966
+ gpu.vertexBuffer.destroy();
967
+ if (gpu.indexBuffer)
968
+ tracker?.untrackBuffer(gpu.indexBuffer);
969
+ gpu.indexBuffer?.destroy();
970
+ }
971
+ destroyMaskTarget(target) {
972
+ getExtensionGPUResourceTracker(this.engine)?.untrackTexture(target.texture);
973
+ target.texture.destroy();
974
+ }
975
+ destroyEffectTarget(target) {
976
+ const tracker = getExtensionGPUResourceTracker(this.engine);
977
+ for (const texture of target.textures) {
978
+ tracker?.untrackTexture(texture);
979
+ texture.destroy();
980
+ }
981
+ }
982
+ }
983
+ function writeBufferAligned(queue, buffer, source) {
984
+ if (source.byteLength % 4 === 0) {
985
+ queue.writeBuffer(buffer, 0, source);
986
+ return;
987
+ }
988
+ const padded = new Uint8Array(alignUp4(source.byteLength));
989
+ padded.set(new Uint8Array(source.buffer, source.byteOffset, source.byteLength));
990
+ queue.writeBuffer(buffer, 0, padded);
991
+ }
992
+ function isExternalImageSource(value) {
993
+ if (!value || typeof value !== 'object')
994
+ return false;
995
+ return (typeof ImageBitmap !== 'undefined' && value instanceof ImageBitmap)
996
+ || (typeof HTMLCanvasElement !== 'undefined' && value instanceof HTMLCanvasElement)
997
+ || (typeof HTMLImageElement !== 'undefined' && value instanceof HTMLImageElement);
998
+ }
999
+ function externalImageSize(source) {
1000
+ const isImage = typeof HTMLImageElement !== 'undefined' && source instanceof HTMLImageElement;
1001
+ return {
1002
+ width: Math.max(1, isImage ? source.naturalWidth || source.width : source.width),
1003
+ height: Math.max(1, isImage ? source.naturalHeight || source.height : source.height),
1004
+ };
1005
+ }
1006
+ function compareItems(a, b) {
1007
+ return a.visual.instanceId - b.visual.instanceId || a.visual.order - b.visual.order || a.entity.id - b.entity.id;
1008
+ }
1009
+ function orderSourceGroups(groups) {
1010
+ const ordered = [];
1011
+ const states = new Map();
1012
+ const visit = (key) => {
1013
+ if (states.get(key) === 2)
1014
+ return;
1015
+ if (states.get(key) === 1)
1016
+ return;
1017
+ states.set(key, 1);
1018
+ for (const item of groups.get(key) ?? []) {
1019
+ for (const dependency of item.visual.compositeKeys)
1020
+ if (groups.has(dependency))
1021
+ visit(dependency);
1022
+ }
1023
+ states.set(key, 2);
1024
+ ordered.push(key);
1025
+ };
1026
+ for (const key of groups.keys())
1027
+ visit(key);
1028
+ return ordered;
1029
+ }
1030
+ function compositeModeCode(mode) {
1031
+ return mode === 'alpha' ? 0 : mode === 'alpha-inverted' ? 1 : mode === 'luma' ? 2 : 3;
1032
+ }
1033
+ function compositeOperationCode(operation) {
1034
+ return operation === 'add' ? 0 : operation === 'subtract' ? 1 : operation === 'intersect' ? 2 : 3;
1035
+ }
1036
+ function effectKindCode(kind) {
1037
+ return kind === 'tint' ? 1 : kind === 'fill' ? 2 : kind === 'opacity' ? 3
1038
+ : kind === 'color-matrix' ? 4 : kind === 'blur' ? 5 : 6;
1039
+ }
1040
+ function visualNodeKey(visual) { return visual.nodeKey; }
1041
+ function buildVertices(positions) {
1042
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1043
+ for (let i = 0; i < positions.length; i += 2) {
1044
+ const x = positions[i], y = positions[i + 1];
1045
+ minX = Math.min(minX, x);
1046
+ maxX = Math.max(maxX, x);
1047
+ minY = Math.min(minY, y);
1048
+ maxY = Math.max(maxY, y);
1049
+ }
1050
+ const width = Math.max(maxX - minX, 1e-6), height = Math.max(maxY - minY, 1e-6);
1051
+ const out = new Float32Array(positions.length * 2);
1052
+ for (let source = 0, target = 0; source < positions.length; source += 2) {
1053
+ const x = positions[source], y = positions[source + 1];
1054
+ out[target++] = x;
1055
+ out[target++] = y;
1056
+ out[target++] = (x - minX) / width;
1057
+ out[target++] = 1 - (y - minY) / height;
1058
+ }
1059
+ return out;
1060
+ }
1061
+ function applyViewport(pass, context) {
1062
+ const viewport = context.view?.viewport;
1063
+ if (viewport)
1064
+ pass.setViewport(viewport.x, viewport.y, viewport.width, viewport.height, viewport.minDepth ?? 0, viewport.maxDepth ?? 1);
1065
+ const scissor = context.view?.scissor;
1066
+ if (scissor)
1067
+ pass.setScissorRect(scissor.x, scissor.y, scissor.width, scissor.height);
1068
+ }
1069
+ function positiveInteger(value, label) {
1070
+ if (!Number.isSafeInteger(value) || value < 1)
1071
+ throw new RangeError(`${label} must be a positive safe integer.`);
1072
+ return value;
1073
+ }
1074
+
1075
+ const HAIYUE_ANIMATION_ASSET_TYPE = 'animation/haiyue';
1076
+ let nextAnimationLoaderId = 1;
1077
+ /** Creates an AssetManager loader without installing source-format converters in the runtime. */
1078
+ function createAnimationAssetLoader(options = {}) {
1079
+ const fetchResource = options.fetch ?? fetch;
1080
+ const loaderId = nextAnimationLoaderId++;
1081
+ return {
1082
+ type: HAIYUE_ANIMATION_ASSET_TYPE,
1083
+ extensions: ['.hya'],
1084
+ mimeTypes: [ANIMATION_MIME_TYPE],
1085
+ aliases: ['hya'],
1086
+ async load(url, context) {
1087
+ context.setPhase('loading');
1088
+ const networkKey = `network:hya:${url}`;
1089
+ let buffer = context.cache.network.get(networkKey);
1090
+ let responseUrl = '';
1091
+ if (!buffer) {
1092
+ const response = await fetchResource(url, { signal: context.signal });
1093
+ if (!response.ok)
1094
+ throw new Error(`Failed to load Haiyue animation (${response.status} ${response.statusText}): ${url}`);
1095
+ responseUrl = response.url;
1096
+ buffer = await response.arrayBuffer();
1097
+ if (context.signal.aborted)
1098
+ throw context.signal.reason;
1099
+ context.cache.network.set(networkKey, buffer, buffer.byteLength);
1100
+ }
1101
+ context.reportProgress(buffer.byteLength, buffer.byteLength);
1102
+ context.setPhase('parsing');
1103
+ const parsedKey = `parsed:hya:${loaderId}:${url}`;
1104
+ let animation = context.cache.parsed.get(parsedKey);
1105
+ if (!animation) {
1106
+ animation = resolveAnimationResourceUris(parseAnimation(buffer, options.parse), responseUrl || absoluteAnimationUrl(url));
1107
+ context.cache.parsed.set(parsedKey, animation, buffer.byteLength);
1108
+ }
1109
+ return animation;
1110
+ },
1111
+ };
1112
+ }
1113
+ /** Package-relative resources resolve from the fetched HYA, including redirects. */
1114
+ function resolveAnimationResourceUris(animation, sourceUrl) {
1115
+ if (!sourceUrl || animation.resources.length === 0)
1116
+ return animation;
1117
+ const resources = animation.resources.map(resource => {
1118
+ try {
1119
+ return Object.freeze({ ...resource, uri: new URL(resource.uri, sourceUrl).href });
1120
+ }
1121
+ catch {
1122
+ return resource;
1123
+ }
1124
+ });
1125
+ return Object.freeze({ ...animation, resources: Object.freeze(resources) });
1126
+ }
1127
+ function absoluteAnimationUrl(url) {
1128
+ try {
1129
+ return new URL(url).href;
1130
+ }
1131
+ catch {
1132
+ const base = typeof globalThis.location === 'object' ? globalThis.location.href : '';
1133
+ if (!base)
1134
+ return '';
1135
+ try {
1136
+ return new URL(url, base).href;
1137
+ }
1138
+ catch {
1139
+ return '';
1140
+ }
1141
+ }
1142
+ }
1143
+
1144
+ /** Instance-owned registry; unregister tokens only remove the handler that created them. */
1145
+ class Animation2DExtensionRegistry {
1146
+ _handlers = new Map();
1147
+ register(handler) {
1148
+ if (!handler.id.trim())
1149
+ throw new TypeError('Animation runtime extension id must not be empty.');
1150
+ if (this._handlers.has(handler.id))
1151
+ throw new Error(`Animation runtime extension "${handler.id}" is already registered.`);
1152
+ this._handlers.set(handler.id, handler);
1153
+ let active = true;
1154
+ return () => {
1155
+ if (!active)
1156
+ return;
1157
+ active = false;
1158
+ if (this._handlers.get(handler.id) === handler)
1159
+ this._handlers.delete(handler.id);
1160
+ };
1161
+ }
1162
+ get(id) {
1163
+ return this._handlers.get(id);
1164
+ }
1165
+ has(id) {
1166
+ return this._handlers.has(id);
1167
+ }
1168
+ }
1169
+
1170
+ export { Animation2DComponent, Animation2DExtensionRegistry, Animation2DRenderSystem, Animation2DSystem, HAIYUE_ANIMATION_ASSET_TYPE, createAnimationAssetLoader };
1171
+ //# sourceMappingURL=animation.js.map