@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,691 +0,0 @@
1
- import type { AnimationPath2DComponent } from '@haiyue/animation-spec';
2
- import { Geometry2D } from '@haiyue/engine';
3
- import earcut from 'earcut';
4
-
5
- interface Point { x: number; y: number }
6
- interface Contour { points: Point[]; area: number; parent: number; depth: number; role: 'outer' | 'hole' | 'ignore' }
7
- interface StrokeSubpath { points: Point[]; closed: boolean }
8
-
9
- export type ResolvedVectorPathModifier =
10
- | Readonly<{ kind: 'trim-path'; start: number; end: number; offset: number; mode: 'simultaneous' | 'individual' }>
11
- | Readonly<{ kind: 'round-corners'; radius: number }>;
12
-
13
- export interface ResolvedVectorPath {
14
- readonly commands: string;
15
- readonly values: Float32Array;
16
- }
17
-
18
- export interface VectorStrokePathComponent {
19
- readonly [key: string]: unknown;
20
- readonly type: 'org.haiyue.vector-stroke@1';
21
- readonly commands?: string;
22
- readonly values?: readonly number[] | Float32Array;
23
- readonly sourceComponent?: number;
24
- readonly color: readonly [number, number, number, number];
25
- readonly width: number;
26
- readonly lineCap: 'butt' | 'round' | 'square';
27
- readonly lineJoin: 'miter' | 'round' | 'bevel';
28
- readonly miterLimit: number;
29
- readonly dash?: readonly number[] | Float32Array;
30
- readonly dashOffset?: number;
31
- readonly tolerance?: number;
32
- }
33
-
34
- export interface VectorPathMorphComponent {
35
- readonly [key: string]: unknown;
36
- readonly type: 'org.haiyue.vector-path-morph@1';
37
- readonly commands: string;
38
- readonly times: readonly number[] | Float32Array;
39
- readonly values: readonly number[] | Float32Array;
40
- readonly valueSize: number;
41
- readonly interpolation: 'step' | 'linear' | 'cubic-bezier';
42
- readonly easings?: readonly number[] | Float32Array;
43
- readonly fill: readonly [number, number, number, number];
44
- readonly fillRule: 'nonzero' | 'evenodd';
45
- readonly tolerance?: number;
46
- }
47
-
48
- const geometryCache = new WeakMap<object, Geometry2D>();
49
- const strokeGeometryCache = new WeakMap<object, Geometry2D>();
50
- const MAX_FLATTENED_VERTICES = 262_144;
51
-
52
- /** Applies ordered source-neutral modifiers after curve flattening. Empty trim ranges return an empty command stream. */
53
- export function applyVectorPathModifiers(
54
- commands: string,
55
- values: readonly number[] | Float32Array,
56
- tolerance: number,
57
- modifiers: readonly ResolvedVectorPathModifier[],
58
- closeOpen: boolean,
59
- ): ResolvedVectorPath {
60
- if (modifiers.length === 0) return { commands, values: new Float32Array(values) };
61
- let paths = flattenStrokePaths({
62
- type: 'org.haiyue.vector-stroke@1', commands, values,
63
- color: [1, 1, 1, 1], width: 1, lineCap: 'butt', lineJoin: 'miter', miterLimit: 4,
64
- tolerance,
65
- });
66
- for (const modifier of modifiers) {
67
- paths = modifier.kind === 'round-corners'
68
- ? paths.map(path => roundStrokeSubpath(path, modifier.radius, tolerance))
69
- : trimStrokeSubpaths(paths, modifier.start, modifier.end, modifier.offset, modifier.mode);
70
- }
71
- let resolvedCommands = '';
72
- const resolvedValues: number[] = [];
73
- for (const path of paths) {
74
- if (path.points.length < 2 || subpathLength(path) <= 1e-6) continue;
75
- resolvedCommands += `M${'L'.repeat(path.points.length - 1)}${path.closed || closeOpen ? 'Z' : ''}`;
76
- for (const point of path.points) resolvedValues.push(point.x, point.y);
77
- }
78
- return { commands: resolvedCommands, values: new Float32Array(resolvedValues) };
79
- }
80
-
81
- function roundStrokeSubpath(path: StrokeSubpath, radius: number, tolerance: number): StrokeSubpath {
82
- if (radius <= 1e-6 || path.points.length < 3) return path;
83
- const result: Point[] = [];
84
- const last = path.points.length - 1;
85
- for (let index = 0; index <= last; index++) {
86
- const current = path.points[index]!;
87
- if (!path.closed && (index === 0 || index === last)) {
88
- result.push(current);
89
- continue;
90
- }
91
- const previous = path.points[(index - 1 + path.points.length) % path.points.length]!;
92
- const next = path.points[(index + 1) % path.points.length]!;
93
- const previousLength = distance(previous, current);
94
- const nextLength = distance(current, next);
95
- if (previousLength <= 1e-6 || nextLength <= 1e-6) {
96
- result.push(current);
97
- continue;
98
- }
99
- const incomingX = (current.x - previous.x) / previousLength;
100
- const incomingY = (current.y - previous.y) / previousLength;
101
- const outgoingX = (next.x - current.x) / nextLength;
102
- const outgoingY = (next.y - current.y) / nextLength;
103
- if (incomingX * outgoingX + incomingY * outgoingY > 0.995) {
104
- result.push(current);
105
- continue;
106
- }
107
- const inset = Math.min(radius, previousLength * 0.5, nextLength * 0.5);
108
- const entry = interpolatePoint(current, previous, inset / previousLength);
109
- const exit = interpolatePoint(current, next, inset / nextLength);
110
- result.push(entry);
111
- const steps = Math.max(2, Math.min(16, Math.ceil(Math.sqrt(inset / Math.max(tolerance, 0.05)))));
112
- for (let step = 1; step <= steps; step++) {
113
- const t = step / steps;
114
- const inverse = 1 - t;
115
- result.push({
116
- x: inverse * inverse * entry.x + 2 * inverse * t * current.x + t * t * exit.x,
117
- y: inverse * inverse * entry.y + 2 * inverse * t * current.y + t * t * exit.y,
118
- });
119
- }
120
- }
121
- return { points: result, closed: path.closed };
122
- }
123
-
124
- function trimStrokeSubpaths(
125
- paths: readonly StrokeSubpath[],
126
- start: number,
127
- end: number,
128
- offset: number,
129
- mode: 'simultaneous' | 'individual',
130
- ): StrokeSubpath[] {
131
- const span = end - start;
132
- if (Math.abs(span) >= 1 - 1e-6) return paths.slice();
133
- if (Math.abs(span) <= 1e-6) return [];
134
- const normalizedStart = wrapUnit(start + offset);
135
- const normalizedEnd = wrapUnit(end + offset);
136
- const intervals = normalizedEnd > normalizedStart
137
- ? [[normalizedStart, normalizedEnd] as const]
138
- : [[normalizedStart, 1] as const, [0, normalizedEnd] as const];
139
- if (mode === 'individual') return paths.flatMap(path => {
140
- const length = subpathLength(path);
141
- return intervals.flatMap(([from, to]) => extractSubpathRange(path, from * length, to * length));
142
- });
143
- const lengths = paths.map(subpathLength);
144
- const total = lengths.reduce((sum, length) => sum + length, 0);
145
- if (total <= 1e-6) return [];
146
- const result: StrokeSubpath[] = [];
147
- let pathStart = 0;
148
- for (let index = 0; index < paths.length; index++) {
149
- const length = lengths[index]!;
150
- const pathEnd = pathStart + length;
151
- for (const [from, to] of intervals) {
152
- const globalStart = from * total;
153
- const globalEnd = to * total;
154
- const overlapStart = Math.max(pathStart, globalStart);
155
- const overlapEnd = Math.min(pathEnd, globalEnd);
156
- if (overlapEnd > overlapStart + 1e-6) {
157
- result.push(...extractSubpathRange(paths[index]!, overlapStart - pathStart, overlapEnd - pathStart));
158
- }
159
- }
160
- pathStart = pathEnd;
161
- }
162
- return result;
163
- }
164
-
165
- function extractSubpathRange(path: StrokeSubpath, from: number, to: number): StrokeSubpath[] {
166
- const length = subpathLength(path);
167
- if (length <= 1e-6 || to <= from + 1e-6) return [];
168
- if (from <= 1e-6 && to >= length - 1e-6) return [path];
169
- const segmentCount = path.closed ? path.points.length : path.points.length - 1;
170
- const result: Point[] = [pointAtSubpathDistance(path, from)];
171
- let cursor = 0;
172
- for (let segment = 0; segment < segmentCount; segment++) {
173
- const next = (segment + 1) % path.points.length;
174
- cursor += distance(path.points[segment]!, path.points[next]!);
175
- if (cursor > from + 1e-6 && cursor < to - 1e-6) result.push(path.points[next]!);
176
- }
177
- result.push(pointAtSubpathDistance(path, to));
178
- return [{ points: result, closed: false }];
179
- }
180
-
181
- function pointAtSubpathDistance(path: StrokeSubpath, target: number): Point {
182
- const segmentCount = path.closed ? path.points.length : path.points.length - 1;
183
- let cursor = 0;
184
- for (let segment = 0; segment < segmentCount; segment++) {
185
- const next = (segment + 1) % path.points.length;
186
- const segmentLength = distance(path.points[segment]!, path.points[next]!);
187
- if (target <= cursor + segmentLength || segment === segmentCount - 1) {
188
- return interpolatePoint(path.points[segment]!, path.points[next]!, segmentLength <= 1e-8 ? 0 : (target - cursor) / segmentLength);
189
- }
190
- cursor += segmentLength;
191
- }
192
- return path.points.at(-1)!;
193
- }
194
-
195
- function subpathLength(path: StrokeSubpath): number {
196
- const segmentCount = path.closed ? path.points.length : path.points.length - 1;
197
- let result = 0;
198
- for (let index = 0; index < segmentCount; index++) {
199
- result += distance(path.points[index]!, path.points[(index + 1) % path.points.length]!);
200
- }
201
- return result;
202
- }
203
-
204
- function distance(left: Point, right: Point): number {
205
- return Math.hypot(right.x - left.x, right.y - left.y);
206
- }
207
-
208
- function interpolatePoint(from: Point, to: Point, progress: number): Point {
209
- const t = Math.min(1, Math.max(0, progress));
210
- return { x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t };
211
- }
212
-
213
- function wrapUnit(value: number): number {
214
- return ((value % 1) + 1) % 1;
215
- }
216
-
217
- /** Flattens packed quadratic/cubic commands and triangulates contours with hole nesting. */
218
- export function tessellateAnimationPath(component: AnimationPath2DComponent): Geometry2D {
219
- const cached = geometryCache.get(component);
220
- if (cached) return cached;
221
- const contours = flattenPath(component);
222
- if (contours.length === 0) throw new RangeError('Animation path contains no drawable closed contour.');
223
- classifyContours(contours, component.fillRule ?? 'nonzero');
224
-
225
- const positions: number[] = [];
226
- const indices: number[] = [];
227
- for (let contourIndex = 0; contourIndex < contours.length; contourIndex++) {
228
- const outer = contours[contourIndex]!;
229
- if (outer.role !== 'outer') continue;
230
- const flat: number[] = [];
231
- const holes: number[] = [];
232
- appendContour(flat, outer.points);
233
- for (let childIndex = 0; childIndex < contours.length; childIndex++) {
234
- const hole = contours[childIndex]!;
235
- if (hole.role !== 'hole' || nearestOuterAncestor(contours, childIndex) !== contourIndex) continue;
236
- holes.push(flat.length / 2);
237
- appendContour(flat, hole.points);
238
- }
239
- const vertexOffset = positions.length / 2;
240
- positions.push(...flat);
241
- for (const index of earcut(flat, holes, 2)) indices.push(vertexOffset + index);
242
- }
243
- if (indices.length === 0) throw new RangeError('Animation path tessellation produced no triangles.');
244
- const indexArray = positions.length / 2 > 0xffff ? new Uint32Array(indices) : new Uint16Array(indices);
245
- const geometry = new Geometry2D(new Float32Array(positions), indexArray);
246
- geometryCache.set(component, geometry);
247
- return geometry;
248
- }
249
-
250
- /** Tessellates the compact Lottie stroke extension without expanding it in the HYA payload. */
251
- export function tessellateVectorStrokePath(component: VectorStrokePathComponent, allowEmpty = false): Geometry2D {
252
- const cached = strokeGeometryCache.get(component);
253
- if (cached) return cached;
254
- const flattened = flattenStrokePaths(component);
255
- const subpaths = component.dash?.length
256
- ? flattened.flatMap(subpath => dashStrokeSubpath(subpath, component.dash!, component.dashOffset ?? 0))
257
- : flattened;
258
- const contours = subpaths.flatMap(subpath => strokeContours(subpath, component));
259
- const positions: number[] = [];
260
- const indices: number[] = [];
261
- for (const contour of contours) {
262
- if (contour.length < 3) continue;
263
- const flat = contour.flatMap(point => [point.x, point.y]);
264
- const vertexOffset = positions.length / 2;
265
- positions.push(...flat);
266
- for (const index of earcut(flat, undefined, 2)) indices.push(vertexOffset + index);
267
- }
268
- if (indices.length === 0 && !allowEmpty) throw new RangeError('Animation stroke tessellation produced no triangles.');
269
- const indexArray = indices.length === 0
270
- ? new Uint16Array([0, 1, 2])
271
- : positions.length / 2 > 0xffff ? new Uint32Array(indices) : new Uint16Array(indices);
272
- const geometry = new Geometry2D(indices.length === 0 ? new Float32Array(6) : new Float32Array(positions), indexArray);
273
- strokeGeometryCache.set(component, geometry);
274
- return geometry;
275
- }
276
-
277
- function dashStrokeSubpath(
278
- subpath: StrokeSubpath,
279
- sourcePattern: readonly number[] | Float32Array,
280
- sourceOffset: number,
281
- ): StrokeSubpath[] {
282
- const positive = Array.from(sourcePattern, value => Math.max(0, value)).filter(value => value > 1e-6);
283
- if (positive.length === 0) return [subpath];
284
- const pattern = positive.length % 2 === 0 ? positive : [...positive, ...positive];
285
- const total = pattern.reduce((sum, value) => sum + value, 0);
286
- let offset = ((sourceOffset % total) + total) % total;
287
- let patternIndex = 0;
288
- while (offset >= pattern[patternIndex]!) {
289
- offset -= pattern[patternIndex]!;
290
- patternIndex = (patternIndex + 1) % pattern.length;
291
- }
292
- let remaining = pattern[patternIndex]! - offset;
293
- let drawing = patternIndex % 2 === 0;
294
- const result: StrokeSubpath[] = [];
295
- let active: Point[] | null = drawing ? [subpath.points[0]!] : null;
296
- const segmentCount = subpath.closed ? subpath.points.length : subpath.points.length - 1;
297
- for (let segment = 0; segment < segmentCount; segment++) {
298
- const from = subpath.points[segment]!;
299
- const to = subpath.points[(segment + 1) % subpath.points.length]!;
300
- const length = Math.hypot(to.x - from.x, to.y - from.y);
301
- if (length <= 1e-8) continue;
302
- let distance = 0;
303
- while (distance < length - 1e-8) {
304
- const advance = Math.min(remaining, length - distance);
305
- const endDistance = distance + advance;
306
- const point = {
307
- x: from.x + (to.x - from.x) * endDistance / length,
308
- y: from.y + (to.y - from.y) * endDistance / length,
309
- };
310
- if (drawing) {
311
- active ??= [{
312
- x: from.x + (to.x - from.x) * distance / length,
313
- y: from.y + (to.y - from.y) * distance / length,
314
- }];
315
- active.push(point);
316
- }
317
- distance = endDistance;
318
- remaining -= advance;
319
- if (remaining <= 1e-8) {
320
- if (drawing && active && active.length >= 2) result.push({ points: active, closed: false });
321
- patternIndex = (patternIndex + 1) % pattern.length;
322
- drawing = patternIndex % 2 === 0;
323
- remaining = pattern[patternIndex]!;
324
- active = drawing ? [point] : null;
325
- }
326
- }
327
- }
328
- if (drawing && active && active.length >= 2) result.push({ points: active, closed: false });
329
- return result;
330
- }
331
-
332
- /** Samples a topology-stable Lottie path morph into the ordinary path tessellator ABI. */
333
- export function sampleVectorPathMorph(
334
- component: VectorPathMorphComponent,
335
- time: number,
336
- ): AnimationPath2DComponent {
337
- const values = samplePackedPathMorph(component, time);
338
- return {
339
- type: 'path2d',
340
- commands: component.commands,
341
- values,
342
- fill: component.fill,
343
- fillRule: component.fillRule,
344
- ...(component.tolerance === undefined ? {} : { tolerance: component.tolerance }),
345
- };
346
- }
347
-
348
- function samplePackedPathMorph(
349
- component: Readonly<{
350
- times: readonly number[] | Float32Array;
351
- values: readonly number[] | Float32Array;
352
- valueSize: number;
353
- interpolation: 'step' | 'linear' | 'cubic-bezier';
354
- easings?: readonly number[] | Float32Array;
355
- }>,
356
- time: number,
357
- ): Float32Array {
358
- const frameCount = component.times.length;
359
- if (frameCount === 0 || component.valueSize <= 0) throw new RangeError('Lottie path morph contains no keyframes.');
360
- let frame = 0;
361
- while (frame + 1 < frameCount && component.times[frame + 1]! <= time) frame++;
362
- const nextFrame = Math.min(frame + 1, frameCount - 1);
363
- let progress = 0;
364
- if (nextFrame !== frame && component.interpolation !== 'step') {
365
- const start = component.times[frame]!;
366
- const end = component.times[nextFrame]!;
367
- progress = Math.min(1, Math.max(0, (time - start) / Math.max(1e-8, end - start)));
368
- if (component.interpolation === 'cubic-bezier' && component.easings) {
369
- const offset = frame * 4;
370
- progress = cubicBezierYForX(
371
- progress,
372
- component.easings[offset] ?? 0.333,
373
- component.easings[offset + 1] ?? 0.333,
374
- component.easings[offset + 2] ?? 0.667,
375
- component.easings[offset + 3] ?? 0.667,
376
- );
377
- }
378
- }
379
- const fromOffset = frame * component.valueSize;
380
- const toOffset = nextFrame * component.valueSize;
381
- const values = new Float32Array(component.valueSize);
382
- for (let offset = 0; offset < values.length; offset++) {
383
- const from = component.values[fromOffset + offset] ?? 0;
384
- const to = component.values[toOffset + offset] ?? from;
385
- values[offset] = from + (to - from) * progress;
386
- }
387
- return values;
388
- }
389
-
390
- function cubicBezierYForX(x: number, x1: number, y1: number, x2: number, y2: number): number {
391
- let parameter = x;
392
- for (let iteration = 0; iteration < 5; iteration++) {
393
- const estimate = cubicBezierCoordinate(parameter, x1, x2) - x;
394
- const derivative = cubicBezierDerivative(parameter, x1, x2);
395
- if (Math.abs(derivative) < 1e-5) break;
396
- parameter = Math.min(1, Math.max(0, parameter - estimate / derivative));
397
- }
398
- return cubicBezierCoordinate(parameter, y1, y2);
399
- }
400
-
401
- function cubicBezierCoordinate(value: number, first: number, second: number): number {
402
- const inverse = 1 - value;
403
- return 3 * inverse * inverse * value * first + 3 * inverse * value * value * second + value * value * value;
404
- }
405
-
406
- function cubicBezierDerivative(value: number, first: number, second: number): number {
407
- const inverse = 1 - value;
408
- return 3 * inverse * inverse * first + 6 * inverse * value * (second - first) + 3 * value * value * (1 - second);
409
- }
410
-
411
- function flattenStrokePaths(component: VectorStrokePathComponent): StrokeSubpath[] {
412
- const subpaths: StrokeSubpath[] = [];
413
- const values = component.values;
414
- if (typeof component.commands !== 'string' || values === undefined) {
415
- throw new TypeError('Resolved Lottie stroke extension requires commands and values.');
416
- }
417
- const toleranceSquared = (component.tolerance ?? 0.35) ** 2;
418
- let valueOffset = 0;
419
- let current: Point[] | null = null;
420
- let cursor: Point = { x: 0, y: 0 };
421
- let start: Point = cursor;
422
- const budget = { count: 0 };
423
- const finish = (closed: boolean): void => {
424
- if (!current) return;
425
- const compact: Point[] = [];
426
- for (const point of current) if (!compact.length || !samePoint(compact.at(-1)!, point)) compact.push(point);
427
- if (closed && compact.length > 1 && samePoint(compact[0]!, compact.at(-1)!)) compact.pop();
428
- if (compact.length >= 2) subpaths.push({ points: compact, closed });
429
- current = null;
430
- };
431
- for (const command of component.commands) {
432
- if (command === 'M') {
433
- finish(false);
434
- cursor = readPoint(values, valueOffset); valueOffset += 2;
435
- start = cursor;
436
- current = [];
437
- appendPoint(current, cursor, budget);
438
- } else if (command === 'L') {
439
- cursor = readPoint(values, valueOffset); valueOffset += 2;
440
- if (current) appendPoint(current, cursor, budget);
441
- } else if (command === 'Q') {
442
- const control = readPoint(values, valueOffset);
443
- const end = readPoint(values, valueOffset + 2);
444
- valueOffset += 4;
445
- if (current) flattenQuadratic(cursor, control, end, toleranceSquared, current, budget, 0);
446
- cursor = end;
447
- } else if (command === 'C') {
448
- const control1 = readPoint(values, valueOffset);
449
- const control2 = readPoint(values, valueOffset + 2);
450
- const end = readPoint(values, valueOffset + 4);
451
- valueOffset += 6;
452
- if (current) flattenCubic(cursor, control1, control2, end, toleranceSquared, current, budget, 0);
453
- cursor = end;
454
- } else if (command === 'Z') {
455
- cursor = start;
456
- finish(true);
457
- }
458
- }
459
- finish(false);
460
- return subpaths;
461
- }
462
-
463
- function strokeContours(subpath: StrokeSubpath, style: VectorStrokePathComponent): Point[][] {
464
- const points = subpath.points;
465
- const closed = subpath.closed;
466
- const halfWidth = style.width / 2;
467
- const contours: Point[][] = [];
468
- const segmentCount = closed ? points.length : points.length - 1;
469
- for (let segment = 0; segment < segmentCount; segment++) {
470
- const from = points[segment]!;
471
- const to = points[(segment + 1) % points.length]!;
472
- const direction = normalizePoint({ x: to.x - from.x, y: to.y - from.y });
473
- if (!direction) continue;
474
- const normal = { x: -direction.y, y: direction.x };
475
- const extendStart = !closed && style.lineCap === 'square' && segment === 0 ? halfWidth : 0;
476
- const extendEnd = !closed && style.lineCap === 'square' && segment === segmentCount - 1 ? halfWidth : 0;
477
- const start = { x: from.x - direction.x * extendStart, y: from.y - direction.y * extendStart };
478
- const end = { x: to.x + direction.x * extendEnd, y: to.y + direction.y * extendEnd };
479
- contours.push([
480
- { x: start.x + normal.x * halfWidth, y: start.y + normal.y * halfWidth },
481
- { x: end.x + normal.x * halfWidth, y: end.y + normal.y * halfWidth },
482
- { x: end.x - normal.x * halfWidth, y: end.y - normal.y * halfWidth },
483
- { x: start.x - normal.x * halfWidth, y: start.y - normal.y * halfWidth },
484
- ]);
485
- }
486
- const joinStart = closed ? 0 : 1;
487
- const joinEnd = closed ? points.length : points.length - 1;
488
- for (let index = joinStart; index < joinEnd; index++) {
489
- const previous = points[(index - 1 + points.length) % points.length]!;
490
- const vertex = points[index]!;
491
- const next = points[(index + 1) % points.length]!;
492
- const incoming = normalizePoint({ x: vertex.x - previous.x, y: vertex.y - previous.y });
493
- const outgoing = normalizePoint({ x: next.x - vertex.x, y: next.y - vertex.y });
494
- if (!incoming || !outgoing) continue;
495
- if (style.lineJoin === 'round') {
496
- contours.push(strokeCircle(vertex, halfWidth));
497
- continue;
498
- }
499
- const cross = incoming.x * outgoing.y - incoming.y * outgoing.x;
500
- if (Math.abs(cross) < 1e-6) continue;
501
- const side = cross > 0 ? 1 : -1;
502
- const incomingNormal = { x: -incoming.y * side, y: incoming.x * side };
503
- const outgoingNormal = { x: -outgoing.y * side, y: outgoing.x * side };
504
- const outerIncoming = { x: vertex.x + incomingNormal.x * halfWidth, y: vertex.y + incomingNormal.y * halfWidth };
505
- const outerOutgoing = { x: vertex.x + outgoingNormal.x * halfWidth, y: vertex.y + outgoingNormal.y * halfWidth };
506
- if (style.lineJoin === 'miter') {
507
- const bisector = normalizePoint({ x: incomingNormal.x + outgoingNormal.x, y: incomingNormal.y + outgoingNormal.y });
508
- const denominator = bisector ? bisector.x * outgoingNormal.x + bisector.y * outgoingNormal.y : 0;
509
- const miterLength = Math.abs(denominator) > 1e-6 ? halfWidth / denominator : Infinity;
510
- if (bisector && miterLength > 0 && miterLength <= halfWidth * style.miterLimit) {
511
- contours.push([outerIncoming, { x: vertex.x + bisector.x * miterLength, y: vertex.y + bisector.y * miterLength }, outerOutgoing]);
512
- continue;
513
- }
514
- }
515
- contours.push([outerIncoming, vertex, outerOutgoing]);
516
- }
517
- if (!closed && style.lineCap === 'round' && points.length > 1) {
518
- contours.push(strokeCircle(points[0]!, halfWidth), strokeCircle(points.at(-1)!, halfWidth));
519
- }
520
- return contours;
521
- }
522
-
523
- function strokeCircle(center: Point, radius: number): Point[] {
524
- return Array.from({ length: 16 }, (_, index) => {
525
- const angle = index * Math.PI * 2 / 16;
526
- return { x: center.x + Math.cos(angle) * radius, y: center.y + Math.sin(angle) * radius };
527
- });
528
- }
529
-
530
- function normalizePoint(point: Point): Point | null {
531
- const length = Math.hypot(point.x, point.y);
532
- return length > 1e-8 ? { x: point.x / length, y: point.y / length } : null;
533
- }
534
-
535
- function flattenPath(component: AnimationPath2DComponent): Contour[] {
536
- const contours: Contour[] = [];
537
- const values = component.values;
538
- const toleranceSquared = (component.tolerance ?? 0.35) ** 2;
539
- let valueOffset = 0;
540
- let current: Point[] | null = null;
541
- let cursor: Point = { x: 0, y: 0 };
542
- let start: Point = cursor;
543
- const budget = { count: 0 };
544
- for (const command of component.commands) {
545
- if (command === 'M') {
546
- if (current && current.length >= 3) closeContour(contours, current);
547
- cursor = readPoint(values, valueOffset); valueOffset += 2;
548
- start = cursor;
549
- current = [];
550
- appendPoint(current, cursor, budget);
551
- } else if (command === 'L') {
552
- cursor = readPoint(values, valueOffset); valueOffset += 2;
553
- if (current) appendPoint(current, cursor, budget);
554
- } else if (command === 'Q') {
555
- const control = readPoint(values, valueOffset);
556
- const end = readPoint(values, valueOffset + 2);
557
- valueOffset += 4;
558
- if (current) flattenQuadratic(cursor, control, end, toleranceSquared, current, budget, 0);
559
- cursor = end;
560
- } else if (command === 'C') {
561
- const control1 = readPoint(values, valueOffset);
562
- const control2 = readPoint(values, valueOffset + 2);
563
- const end = readPoint(values, valueOffset + 4);
564
- valueOffset += 6;
565
- if (current) flattenCubic(cursor, control1, control2, end, toleranceSquared, current, budget, 0);
566
- cursor = end;
567
- } else if (command === 'Z' && current) {
568
- if (!samePoint(cursor, start)) appendPoint(current, start, budget);
569
- closeContour(contours, current);
570
- current = null;
571
- cursor = start;
572
- }
573
- }
574
- return contours;
575
- }
576
-
577
- function readPoint(values: ArrayLike<number>, offset: number): Point {
578
- return { x: values[offset] ?? 0, y: -(values[offset + 1] ?? 0) };
579
- }
580
-
581
- function flattenQuadratic(a: Point, b: Point, c: Point, toleranceSquared: number, out: Point[], budget: { count: number }, depth: number): void {
582
- if (depth >= 12 || pointLineDistanceSquared(b, a, c) <= toleranceSquared) { appendPoint(out, c, budget); return; }
583
- const ab = midpoint(a, b), bc = midpoint(b, c), middle = midpoint(ab, bc);
584
- flattenQuadratic(a, ab, middle, toleranceSquared, out, budget, depth + 1);
585
- flattenQuadratic(middle, bc, c, toleranceSquared, out, budget, depth + 1);
586
- }
587
-
588
- function flattenCubic(a: Point, b: Point, c: Point, d: Point, toleranceSquared: number, out: Point[], budget: { count: number }, depth: number): void {
589
- if (depth >= 12 || Math.max(pointLineDistanceSquared(b, a, d), pointLineDistanceSquared(c, a, d)) <= toleranceSquared) {
590
- appendPoint(out, d, budget); return;
591
- }
592
- const ab = midpoint(a, b), bc = midpoint(b, c), cd = midpoint(c, d);
593
- const abc = midpoint(ab, bc), bcd = midpoint(bc, cd), middle = midpoint(abc, bcd);
594
- flattenCubic(a, ab, abc, middle, toleranceSquared, out, budget, depth + 1);
595
- flattenCubic(middle, bcd, cd, d, toleranceSquared, out, budget, depth + 1);
596
- }
597
-
598
- function appendPoint(out: Point[], point: Point, budget: { count: number }): void {
599
- if (++budget.count > MAX_FLATTENED_VERTICES) {
600
- throw new RangeError(`Animation path exceeds the ${MAX_FLATTENED_VERTICES} flattened-vertex runtime budget.`);
601
- }
602
- out.push(point);
603
- }
604
-
605
- function closeContour(out: Contour[], points: Point[]): void {
606
- const compact: Point[] = [];
607
- for (const point of points) if (!compact.length || !samePoint(compact[compact.length - 1]!, point)) compact.push(point);
608
- if (compact.length > 1 && samePoint(compact[0]!, compact[compact.length - 1]!)) compact.pop();
609
- if (compact.length < 3) return;
610
- out.push({ points: compact, area: signedArea(compact), parent: -1, depth: 0, role: 'ignore' });
611
- }
612
-
613
- function classifyContours(contours: Contour[], fillRule: AnimationPath2DComponent['fillRule']): void {
614
- for (let i = 0; i < contours.length; i++) {
615
- const contour = contours[i]!;
616
- let parent = -1;
617
- let parentArea = Infinity;
618
- const point = contour.points[0]!;
619
- for (let candidateIndex = 0; candidateIndex < contours.length; candidateIndex++) {
620
- if (candidateIndex === i) continue;
621
- const candidate = contours[candidateIndex]!;
622
- const area = Math.abs(candidate.area);
623
- if (area <= Math.abs(contour.area) || area >= parentArea || !pointInPolygon(point, candidate.points)) continue;
624
- parent = candidateIndex;
625
- parentArea = area;
626
- }
627
- contour.parent = parent;
628
- }
629
- for (const contour of contours) {
630
- let depth = 0;
631
- let parent = contour.parent;
632
- while (parent >= 0) { depth++; parent = contours[parent]!.parent; }
633
- contour.depth = depth;
634
- if (fillRule === 'evenodd') {
635
- contour.role = (depth & 1) === 0 ? 'outer' : 'hole';
636
- continue;
637
- }
638
- let windingBefore = 0;
639
- parent = contour.parent;
640
- while (parent >= 0) {
641
- windingBefore += Math.sign(contours[parent]!.area);
642
- parent = contours[parent]!.parent;
643
- }
644
- const windingAfter = windingBefore + Math.sign(contour.area);
645
- contour.role = windingBefore === 0 && windingAfter !== 0
646
- ? 'outer'
647
- : windingBefore !== 0 && windingAfter === 0 ? 'hole' : 'ignore';
648
- }
649
- }
650
-
651
- function nearestOuterAncestor(contours: readonly Contour[], contourIndex: number): number {
652
- let parent = contours[contourIndex]!.parent;
653
- while (parent >= 0) {
654
- if (contours[parent]!.role === 'outer') return parent;
655
- parent = contours[parent]!.parent;
656
- }
657
- return -1;
658
- }
659
-
660
- function appendContour(out: number[], points: readonly Point[]): void {
661
- for (const point of points) out.push(point.x, point.y);
662
- }
663
-
664
- function signedArea(points: readonly Point[]): number {
665
- let area = 0;
666
- for (let i = 0; i < points.length; i++) {
667
- const a = points[i]!, b = points[(i + 1) % points.length]!;
668
- area += a.x * b.y - b.x * a.y;
669
- }
670
- return area * 0.5;
671
- }
672
-
673
- function pointInPolygon(point: Point, polygon: readonly Point[]): boolean {
674
- let inside = false;
675
- for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
676
- const a = polygon[i]!, b = polygon[j]!;
677
- if ((a.y > point.y) !== (b.y > point.y) && point.x < (b.x - a.x) * (point.y - a.y) / (b.y - a.y) + a.x) inside = !inside;
678
- }
679
- return inside;
680
- }
681
-
682
- function midpoint(a: Point, b: Point): Point { return { x: (a.x + b.x) * 0.5, y: (a.y + b.y) * 0.5 }; }
683
- function samePoint(a: Point, b: Point): boolean { return Math.abs(a.x - b.x) < 1e-6 && Math.abs(a.y - b.y) < 1e-6; }
684
- function pointLineDistanceSquared(point: Point, a: Point, b: Point): number {
685
- const dx = b.x - a.x, dy = b.y - a.y;
686
- const lengthSquared = dx * dx + dy * dy;
687
- if (lengthSquared <= 1e-12) return (point.x - a.x) ** 2 + (point.y - a.y) ** 2;
688
- const t = ((point.x - a.x) * dx + (point.y - a.y) * dy) / lengthSquared;
689
- const x = a.x - point.x + dx * t, y = a.y - point.y + dy * t;
690
- return x * x + y * y;
691
- }