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