@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.
- package/README.md +25 -0
- package/dist/animation/Animation2DComponent.d.ts +18 -0
- package/dist/animation/Animation2DExtensionRegistry.d.ts +7 -0
- package/dist/animation/Animation2DRenderSystem.d.ts +6 -0
- package/dist/animation/Animation2DRuntime.d.ts +6 -2
- package/dist/animation/AnimationTextRasterizer.d.ts +4 -0
- package/dist/animation/AnimationVisual2D.d.ts +58 -2
- package/dist/animation/interaction/InteractionRuntime.d.ts +83 -0
- package/dist/animation/interaction/runtime-types.d.ts +257 -0
- package/dist/animation.d.ts +3 -1
- package/dist/animation.js +551 -59
- package/dist/animation3d.js +1 -1
- package/dist/canvas-text.js +1 -1
- package/dist/chunks/2d-ui-animation-2d.generated-FpziAeGJ.js +4 -0
- package/dist/chunks/2d-ui-indexed-sprite.generated-DmKR0CC3.js +4 -0
- package/dist/chunks/Animation2DStateMachine-BHcA6Cu3.js +18212 -0
- package/dist/chunks/{AnimationStateMachineController-BIcAh-sT.js → AnimationStateMachineController-4sKaqh9E.js} +2 -2
- package/dist/chunks/AnimationVisual2D-8y3qp0_U.js +230 -0
- package/dist/chunks/{RenderSystem2DBase-DDT61dAu.js → RenderSystem2DBase-CF2s00vg.js} +2 -2
- package/dist/controls/VirtualJoystickControls.d.ts +46 -0
- package/dist/controls/contracts.d.ts +91 -0
- package/dist/controls.d.ts +2 -0
- package/dist/controls.js +358 -0
- package/dist/deformable-animation/runtime/DeformableMesh2DRuntimeExtension.d.ts +11 -0
- package/dist/deformable-animation/runtime/DeformableMesh2DSampler.d.ts +11 -0
- package/dist/deformable-animation.d.ts +5 -0
- package/dist/deformable-animation.js +316 -0
- package/dist/experimental-indexed-sprite.d.ts +5 -0
- package/dist/experimental-indexed-sprite.js +482 -0
- package/dist/gltf-worker-runtime.js +9 -9
- package/dist/hya-state-machine.js +3 -2
- package/dist/index.js +1 -1
- package/dist/indexed-sprite/AtlasLayout.d.ts +2 -0
- package/dist/indexed-sprite/IndexedSpriteRenderer.d.ts +23 -0
- package/dist/indexed-sprite/contracts.d.ts +89 -0
- package/dist/internal/2d-ui-shader-artifact.js +232 -23
- package/dist/ray-tracing/acceleration/bvh.d.ts +37 -0
- package/dist/ray-tracing/acceleration/index.d.ts +6 -0
- package/dist/ray-tracing/acceleration/pack.d.ts +152 -0
- package/dist/ray-tracing/acceleration/query.d.ts +4 -0
- package/dist/ray-tracing/acceleration/runtime.d.ts +30 -0
- package/dist/ray-tracing/acceleration/tlas.d.ts +4 -0
- package/dist/ray-tracing/acceleration/types.d.ts +165 -0
- package/dist/ray-tracing/denoise/index.d.ts +3 -0
- package/dist/ray-tracing/denoise/layout.d.ts +9 -0
- package/dist/ray-tracing/denoise/runtime.d.ts +23 -0
- package/dist/ray-tracing/denoise/types.d.ts +28 -0
- package/dist/ray-tracing/hybrid/contract.d.ts +2 -0
- package/dist/ray-tracing/hybrid/index.d.ts +5 -0
- package/dist/ray-tracing/hybrid/layout.d.ts +17 -0
- package/dist/ray-tracing/hybrid/plan.d.ts +2 -0
- package/dist/ray-tracing/hybrid/runtime.d.ts +31 -0
- package/dist/ray-tracing/hybrid/shaders/hybrid-artifact.generated.d.ts +625 -0
- package/dist/ray-tracing/hybrid/types.d.ts +144 -0
- package/dist/ray-tracing/lifecycle/device-recovery.d.ts +31 -0
- package/dist/ray-tracing/lifecycle/index.d.ts +2 -0
- package/dist/ray-tracing/lifecycle/types.d.ts +37 -0
- package/dist/ray-tracing/material/index.d.ts +2 -0
- package/dist/ray-tracing/material/pack.d.ts +5 -0
- package/dist/ray-tracing/material/types.d.ts +63 -0
- package/dist/ray-tracing/reference/index.d.ts +122 -0
- package/dist/ray-tracing/renderer/index.d.ts +7 -0
- package/dist/ray-tracing/renderer/layout.d.ts +20 -0
- package/dist/ray-tracing/renderer/plan.d.ts +2 -0
- package/dist/ray-tracing/renderer/reference.d.ts +17 -0
- package/dist/ray-tracing/renderer/runtime.d.ts +39 -0
- package/dist/ray-tracing/renderer/scene.d.ts +4 -0
- package/dist/ray-tracing/renderer/types.d.ts +115 -0
- package/dist/ray-tracing/sampling/index.d.ts +8 -0
- package/dist/ray-tracing/sampling/layout.d.ts +12 -0
- package/dist/ray-tracing/sampling/plan.d.ts +5 -0
- package/dist/ray-tracing/sampling/revision.d.ts +15 -0
- package/dist/ray-tracing/sampling/runtime.d.ts +44 -0
- package/dist/ray-tracing/sampling/sequence.d.ts +2 -0
- package/dist/ray-tracing/sampling/types.d.ts +96 -0
- package/dist/ray-tracing/scene/index.d.ts +41 -0
- package/dist/ray-tracing/traversal/index.d.ts +4 -0
- package/dist/ray-tracing/traversal/layout.d.ts +16 -0
- package/dist/ray-tracing/traversal/plan.d.ts +2 -0
- package/dist/ray-tracing/traversal/runtime.d.ts +25 -0
- package/dist/ray-tracing/traversal/types.d.ts +79 -0
- package/dist/ray-tracing/worker/client.d.ts +25 -0
- package/dist/ray-tracing/worker/index.d.ts +5 -0
- package/dist/ray-tracing/worker/runtime.d.ts +11 -0
- package/dist/ray-tracing/worker/serialization.d.ts +16 -0
- package/dist/ray-tracing/worker/source.d.ts +1 -0
- package/dist/ray-tracing/worker/types.d.ts +91 -0
- package/dist/ray-tracing.d.ts +17 -0
- package/dist/ray-tracing.js +7666 -0
- package/dist/spine.js +1 -1
- package/dist/tilemap.js +1 -1
- package/package.json +26 -23
- package/dist/animation/Animation2DStateMachineVisualRuntime.d.ts +0 -27
- package/dist/animation/Particle2DDescriptorAdapter.d.ts +0 -5
- package/dist/animation3d/Animation3DResource.d.ts +0 -28
- package/dist/animation3d/hya/HyaAnimation3DStateMachineRuntime.d.ts +0 -65
- package/dist/animation3d/hya/index.d.ts +0 -4
- package/dist/animation3d/runtime/integration/Animation3DStateMachineMixerAdapter.d.ts +0 -81
- package/dist/animation3d/runtime/integration/Animation3DStateMachineMixerIntegration.d.ts +0 -39
- package/dist/animation3d/runtime/integration/index.d.ts +0 -2
- package/dist/animation3d/runtime/mixer/Action.d.ts +0 -150
- package/dist/animation3d/runtime/mixer/Animation3DMixerRuntimeStore.d.ts +0 -6
- package/dist/animation3d/runtime/mixer/Mixer.d.ts +0 -59
- package/dist/animation3d/runtime/mixer/PoseBuffer.d.ts +0 -1
- package/dist/animation3d/runtime/mixer/TrackSampler.d.ts +0 -24
- package/dist/animation3d/runtime/mixer/index.d.ts +0 -4
- package/dist/animation3d/runtime/state-machine/Animation3DBlendTreeWeights.d.ts +0 -1
- package/dist/animation3d/runtime/state-machine/Animation3DStateMachineCompiler.d.ts +0 -1
- package/dist/animation3d/runtime/state-machine/Animation3DStateMachineController.d.ts +0 -1
- package/dist/animation3d/runtime/state-machine/Animation3DStateMachineMixerPort.d.ts +0 -1
- package/dist/animation3d/runtime/state-machine/index.d.ts +0 -6
- package/dist/benchmark.d.ts +0 -30
- package/dist/chunks/2d-ui-animation-2d.generated-D4aHzFU2.js +0 -4
- package/dist/chunks/Animation2DStateMachine-9IonguNa.js +0 -3245
- package/dist/gltf/GltfAccessorReader.d.ts +0 -7
- package/dist/gltf/GltfDracoDecoder.d.ts +0 -4
- package/dist/gltf/GltfLoaderErrors.d.ts +0 -4
- package/dist/gltf/GltfMaterialDescriptor.d.ts +0 -23
- package/dist/gltf/GltfMaterialLoader.d.ts +0 -6
- package/dist/gltf-worker-runtime.d.ts +0 -7
- package/dist/plugins/pluginUtils.d.ts +0 -20
- package/dist/plugins.d.ts +0 -6
- package/dist/shaders/generated/2d-ui-artifact.generated.d.ts +0 -658
- package/dist/spine/Spine2DGpuRenderer.d.ts +0 -65
- package/dist/spine/Spine2DRuntime.d.ts +0 -103
- package/dist/spine/SpineAnimationBoneTimelines.d.ts +0 -8
- package/dist/spine/SpineFloatBuilder.d.ts +0 -15
- package/dist/spine/SpinePathConstraintSolver.d.ts +0 -32
- package/dist/spine/SpineSkeletonRuntime.d.ts +0 -185
- package/dist/spine/SpineSlotAnimation.d.ts +0 -17
- package/dist/spine/SpineTimelineRuntime.d.ts +0 -25
- package/dist/spine/SpineVertexBuilder.d.ts +0 -61
- package/dist/tilemap/Tilemap2DRenderer.d.ts +0 -24
- package/dist/utils/arrayAccess.d.ts +0 -4
- package/dist/utils/camera2d.d.ts +0 -12
- package/src/animation/Animation2DComponent.ts +0 -135
- package/src/animation/Animation2DExtensionRegistry.ts +0 -52
- package/src/animation/Animation2DRenderSystem.ts +0 -1026
- package/src/animation/Animation2DRuntime.ts +0 -1326
- package/src/animation/Animation2DStateMachine.ts +0 -39
- package/src/animation/Animation2DStateMachineComponent.ts +0 -228
- package/src/animation/Animation2DStateMachineRuntime.ts +0 -141
- package/src/animation/Animation2DStateMachineSystem.ts +0 -60
- package/src/animation/Animation2DStateMachineVisualRuntime.ts +0 -282
- package/src/animation/Animation2DSystem.ts +0 -72
- package/src/animation/AnimationAssetLoader.ts +0 -84
- package/src/animation/AnimationAudioClip.ts +0 -140
- package/src/animation/AnimationPathTessellator.ts +0 -691
- package/src/animation/AnimationTextRasterizer.ts +0 -340
- package/src/animation/AnimationVisual2D.ts +0 -153
- package/src/animation/HyaAnimation2DClipAdapter.ts +0 -845
- package/src/animation/Particle2DDescriptorAdapter.ts +0 -35
- package/src/animation/runtime/mixer/Action.ts +0 -876
- package/src/animation/runtime/mixer/Mixer.ts +0 -505
- package/src/animation/runtime/mixer/PoseBuffer.ts +0 -105
- package/src/animation/runtime/mixer/Sampler.ts +0 -252
- package/src/animation/runtime/mixer/Types.ts +0 -138
- package/src/animation/runtime/mixer/index.ts +0 -31
- package/src/animation-state-machine/AnimationStateMachine.ts +0 -146
- package/src/animation-state-machine/AnimationStateMachineChannels.ts +0 -116
- package/src/animation-state-machine/runtime/Animation2DStateMachineMixerAdapter.ts +0 -197
- package/src/animation-state-machine/runtime/AnimationStateMachineBlendTreeWeights.ts +0 -503
- package/src/animation-state-machine/runtime/AnimationStateMachineCompiler.ts +0 -763
- package/src/animation-state-machine/runtime/AnimationStateMachineController.ts +0 -987
- package/src/animation-state-machine/runtime/AnimationStateMachineMixerPort.ts +0 -54
- package/src/animation-state-machine/runtime/index.ts +0 -47
- package/src/animation.ts +0 -33
- package/src/animation3d/Animation3DAction.ts +0 -73
- package/src/animation3d/Animation3DBinding.ts +0 -116
- package/src/animation3d/Animation3DClip.ts +0 -22
- package/src/animation3d/Animation3DError.ts +0 -34
- package/src/animation3d/Animation3DMixer.ts +0 -82
- package/src/animation3d/Animation3DPose.ts +0 -100
- package/src/animation3d/Animation3DPoseBuffer.ts +0 -85
- package/src/animation3d/Animation3DResource.ts +0 -49
- package/src/animation3d/Animation3DStateMachine.ts +0 -126
- package/src/animation3d/Animation3DStateMachineRuntime.ts +0 -191
- package/src/animation3d/Animation3DTrack.ts +0 -29
- package/src/animation3d/hya/HyaAnimation3DRuntime.ts +0 -756
- package/src/animation3d/hya/HyaAnimation3DStateMachineRuntime.ts +0 -618
- package/src/animation3d/hya/HyaAnimation3DTypes.ts +0 -168
- package/src/animation3d/hya/index.ts +0 -10
- package/src/animation3d/index.ts +0 -76
- package/src/animation3d/runtime/integration/Animation3DStateMachineMixerAdapter.ts +0 -325
- package/src/animation3d/runtime/integration/Animation3DStateMachineMixerIntegration.ts +0 -158
- package/src/animation3d/runtime/integration/index.ts +0 -9
- package/src/animation3d/runtime/mixer/Action.ts +0 -833
- package/src/animation3d/runtime/mixer/Animation3DMixerRuntimeStore.ts +0 -26
- package/src/animation3d/runtime/mixer/Mixer.ts +0 -562
- package/src/animation3d/runtime/mixer/PoseBuffer.ts +0 -1
- package/src/animation3d/runtime/mixer/TrackSampler.ts +0 -399
- package/src/animation3d/runtime/mixer/index.ts +0 -8
- package/src/animation3d/runtime/state-machine/Animation3DBlendTreeWeights.ts +0 -4
- package/src/animation3d/runtime/state-machine/Animation3DStateMachineCompiler.ts +0 -1
- package/src/animation3d/runtime/state-machine/Animation3DStateMachineController.ts +0 -1
- package/src/animation3d/runtime/state-machine/Animation3DStateMachineMixerPort.ts +0 -1
- package/src/animation3d/runtime/state-machine/index.ts +0 -45
- package/src/animation3d.ts +0 -5
- package/src/benchmark.ts +0 -168
- package/src/canvas-text/CanvasText2DRenderSystem.ts +0 -292
- package/src/canvas-text/CanvasTextComponent.ts +0 -47
- package/src/canvas-text.ts +0 -4
- package/src/experimental-gltf-worker.ts +0 -18
- package/src/experimental-spine-worker.ts +0 -9
- package/src/gltf/GltfAccessorReader.ts +0 -271
- package/src/gltf/GltfAnimation3DAdapter.ts +0 -680
- package/src/gltf/GltfAnimationRuntime.ts +0 -392
- package/src/gltf/GltfAssetWorkerClient.ts +0 -136
- package/src/gltf/GltfCompatibilityReport.ts +0 -214
- package/src/gltf/GltfConservativeBounds.ts +0 -219
- package/src/gltf/GltfDracoDecoder.ts +0 -251
- package/src/gltf/GltfExtensionAdapter.ts +0 -340
- package/src/gltf/GltfLoaderContract.ts +0 -333
- package/src/gltf/GltfLoaderErrors.ts +0 -59
- package/src/gltf/GltfMaterialDescriptor.ts +0 -134
- package/src/gltf/GltfMaterialEncoder.ts +0 -173
- package/src/gltf/GltfMaterialLoader.ts +0 -293
- package/src/gltf/GltfModelComponent.ts +0 -115
- package/src/gltf/GltfModelSystem.ts +0 -208
- package/src/gltf/GltfPlugin.ts +0 -84
- package/src/gltf/GltfSchema.ts +0 -229
- package/src/gltf/GltfUvSemanticPlanner.ts +0 -166
- package/src/gltf/gltfLoader.ts +0 -910
- package/src/gltf-animation3d.ts +0 -10
- package/src/gltf-worker-runtime.ts +0 -10
- package/src/gltf.ts +0 -56
- package/src/grid/Grid2DComponent.ts +0 -69
- package/src/grid.ts +0 -2
- package/src/hya-state-machine.ts +0 -19
- package/src/index.ts +0 -2
- package/src/plugins/pluginUtils.ts +0 -50
- package/src/plugins.ts +0 -6
- package/src/shaders/generated/2d-ui-animation-2d.generated.wgsl +0 -242
- package/src/shaders/generated/2d-ui-artifact.generated.ts +0 -867
- package/src/shaders/generated/2d-ui-canvas-text-2d.generated.wgsl +0 -38
- package/src/shaders/generated/2d-ui-spine2d.generated.wgsl +0 -41
- package/src/shaders/generated/2d-ui-tilemap2d.generated.wgsl +0 -36
- package/src/spine/Spine2DComponent.ts +0 -168
- package/src/spine/Spine2DGpuRenderer.ts +0 -449
- package/src/spine/Spine2DRenderSystem.ts +0 -199
- package/src/spine/Spine2DRuntime.ts +0 -1529
- package/src/spine/SpineAnimationBoneTimelines.ts +0 -30
- package/src/spine/SpineAssetParser.ts +0 -20
- package/src/spine/SpineAssetWorkerClient.ts +0 -112
- package/src/spine/SpineAssetWorkerContract.ts +0 -4
- package/src/spine/SpineAtlasParser.ts +0 -127
- package/src/spine/SpineFloatBuilder.ts +0 -66
- package/src/spine/SpinePathConstraintSolver.ts +0 -582
- package/src/spine/SpinePlugin.ts +0 -76
- package/src/spine/SpineSkeletonRuntime.ts +0 -521
- package/src/spine/SpineSlotAnimation.ts +0 -195
- package/src/spine/SpineTimelineRuntime.ts +0 -453
- package/src/spine/SpineVertexBuilder.ts +0 -686
- package/src/spine.ts +0 -7
- package/src/tilemap/Tilemap2DComponent.ts +0 -232
- package/src/tilemap/Tilemap2DRenderSystem.ts +0 -58
- package/src/tilemap/Tilemap2DRenderer.ts +0 -289
- package/src/tilemap/TilemapPlugin.ts +0 -75
- package/src/tilemap.ts +0 -6
- package/src/tween/Tween2DComponent.ts +0 -83
- package/src/tween/Tween2DSystem.ts +0 -78
- package/src/tween.ts +0 -4
- package/src/types/earcut.d.ts +0 -7
- package/src/types/wgsl.d.ts +0 -4
- package/src/utils/RenderSystem2DBase.ts +0 -164
- package/src/utils/arrayAccess.ts +0 -17
- package/src/utils/camera2d.ts +0 -68
- package/src/utils/render2dGpu.ts +0 -132
- package/test/fixtures/gltf/animation-characterization.gltf +0 -344
|
@@ -1,1529 +0,0 @@
|
|
|
1
|
-
import { EngineError, EngineErrorCode } from '@haiyue/engine';
|
|
2
|
-
import { ErrorDomain, ErrorRecovery } from '@haiyue/engine/core';
|
|
3
|
-
import { createAbortError, parseAssetWorkerFirst } from '@haiyue/engine/experimental/async';
|
|
4
|
-
import { Spine2DComponent } from './Spine2DComponent';
|
|
5
|
-
import { Spine2DGpuRenderer, type AtlasPageGpu, type SpineBufferDirtyRange } from './Spine2DGpuRenderer';
|
|
6
|
-
import { compileAnimationBoneTimelines, getAnimationBoneTimelines } from './SpineAnimationBoneTimelines';
|
|
7
|
-
import { SpineFloatBuilder } from './SpineFloatBuilder';
|
|
8
|
-
import { parseAtlas, type AtlasRegion } from './SpineAtlasParser';
|
|
9
|
-
import {
|
|
10
|
-
applyPathConstraint,
|
|
11
|
-
createSpinePathConstraintScratch,
|
|
12
|
-
type SpinePathConstraintScratch,
|
|
13
|
-
} from './SpinePathConstraintSolver';
|
|
14
|
-
import {
|
|
15
|
-
appendBoneDebug,
|
|
16
|
-
appendMeshVertices,
|
|
17
|
-
appendMeshWireframe,
|
|
18
|
-
appendRegionVertices,
|
|
19
|
-
appendRegionWireframe,
|
|
20
|
-
computeClipPolygon,
|
|
21
|
-
copyBuilderData,
|
|
22
|
-
createEmptySlotCache,
|
|
23
|
-
getDebugBonesSignature,
|
|
24
|
-
getSlotBuildSignature,
|
|
25
|
-
getSlotOrderHash,
|
|
26
|
-
hasInvalidOffsets,
|
|
27
|
-
hashInt,
|
|
28
|
-
normalizeBlend,
|
|
29
|
-
rebuildCachedDrawBatches,
|
|
30
|
-
rebuildCachedGeometryLayout,
|
|
31
|
-
replaceClippedRange,
|
|
32
|
-
type SpineDrawBatch,
|
|
33
|
-
type SpineSlotGeometryCache,
|
|
34
|
-
} from './SpineVertexBuilder';
|
|
35
|
-
import {
|
|
36
|
-
type BonePose,
|
|
37
|
-
type IkConstraintData,
|
|
38
|
-
type PathConstraintData,
|
|
39
|
-
type SliderAnimationState,
|
|
40
|
-
type SliderConstraintData,
|
|
41
|
-
type SlotData,
|
|
42
|
-
type SpineAnimationData,
|
|
43
|
-
type SpineData,
|
|
44
|
-
type TransformConstraintData,
|
|
45
|
-
} from './SpineSkeletonRuntime';
|
|
46
|
-
import {
|
|
47
|
-
compileSpineTimelines,
|
|
48
|
-
createSpineTimelineSamplerState,
|
|
49
|
-
findCompiledFrameIndex,
|
|
50
|
-
getCompiledAnimationDuration,
|
|
51
|
-
sampleCompiledTimeline as sampleTimeline,
|
|
52
|
-
type SpineTimelineCompileStats,
|
|
53
|
-
type SpineTimelineSamplerState,
|
|
54
|
-
} from './SpineTimelineRuntime';
|
|
55
|
-
import {
|
|
56
|
-
getSpineAttachmentRegionName,
|
|
57
|
-
getSpineSlotAttachmentName,
|
|
58
|
-
getSpineSlotColor,
|
|
59
|
-
} from './SpineSlotAnimation';
|
|
60
|
-
import { SPINE_ASSET_PARSER, type SpineParsedAsset } from './SpineAssetParser';
|
|
61
|
-
import type { SpineAssetWorker } from './SpineAssetWorkerContract';
|
|
62
|
-
import { requiredItemAt, requiredNumberAt } from '../utils/arrayAccess';
|
|
63
|
-
|
|
64
|
-
export interface SpineVertexBuild {
|
|
65
|
-
vertices: SpineFloatBuilder;
|
|
66
|
-
batches: SpineDrawBatch[];
|
|
67
|
-
debugVertices: SpineFloatBuilder;
|
|
68
|
-
vertexDirtyRanges: SpineBufferDirtyRange[];
|
|
69
|
-
debugDirtyRanges: SpineBufferDirtyRange[];
|
|
70
|
-
verticesChanged: boolean;
|
|
71
|
-
debugVerticesChanged: boolean;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export interface SpineRuntimeAllocationStats {
|
|
75
|
-
slotRegionGrowths: number;
|
|
76
|
-
batchPoolMisses: number;
|
|
77
|
-
dirtyRangePoolMisses: number;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
type SpineConstraintEntry =
|
|
81
|
-
| { type: 'ik'; order: number; constraint: IkConstraintData }
|
|
82
|
-
| { type: 'path'; order: number; constraint: PathConstraintData }
|
|
83
|
-
| { type: 'transform'; order: number; constraint: TransformConstraintData };
|
|
84
|
-
|
|
85
|
-
class LocalPoseSnapshot {
|
|
86
|
-
data: Float32Array;
|
|
87
|
-
length = 0;
|
|
88
|
-
|
|
89
|
-
constructor(initialCapacity = 256) {
|
|
90
|
-
this.data = new Float32Array(initialCapacity);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
capture(poses: Map<string, BonePose>): void {
|
|
94
|
-
const required = poses.size * 7;
|
|
95
|
-
if (this.data.length < required) {
|
|
96
|
-
let nextCapacity = Math.max(1, this.data.length);
|
|
97
|
-
while (nextCapacity < required) nextCapacity *= 2;
|
|
98
|
-
this.data = new Float32Array(nextCapacity);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
let offset = 0;
|
|
102
|
-
for (const pose of poses.values()) {
|
|
103
|
-
this.data[offset++] = pose.x;
|
|
104
|
-
this.data[offset++] = pose.y;
|
|
105
|
-
this.data[offset++] = pose.rotation;
|
|
106
|
-
this.data[offset++] = pose.shearX;
|
|
107
|
-
this.data[offset++] = pose.shearY;
|
|
108
|
-
this.data[offset++] = pose.scaleX;
|
|
109
|
-
this.data[offset++] = pose.scaleY;
|
|
110
|
-
}
|
|
111
|
-
this.length = offset;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export interface SpineRuntime extends SpinePathConstraintScratch {
|
|
116
|
-
data: SpineData;
|
|
117
|
-
atlas: Map<string, AtlasRegion>;
|
|
118
|
-
pages: Map<string, AtlasPageGpu>;
|
|
119
|
-
vertexBuffer: GPUBuffer;
|
|
120
|
-
vertexBufferSize: number;
|
|
121
|
-
debugVertexBuffer: GPUBuffer;
|
|
122
|
-
debugVertexBufferSize: number;
|
|
123
|
-
batches: SpineDrawBatch[];
|
|
124
|
-
batchPool: SpineDrawBatch[];
|
|
125
|
-
vertexBuilder: SpineFloatBuilder;
|
|
126
|
-
debugVertexBuilder: SpineFloatBuilder;
|
|
127
|
-
slotVertexBuilder: SpineFloatBuilder;
|
|
128
|
-
slotDebugVertexBuilder: SpineFloatBuilder;
|
|
129
|
-
clipBuilder: SpineFloatBuilder;
|
|
130
|
-
meshPointBuilder: SpineFloatBuilder;
|
|
131
|
-
slotGeometryCache: Map<string, SpineSlotGeometryCache>;
|
|
132
|
-
slotOrderHash: number;
|
|
133
|
-
boneChildren: Map<string, string[]>;
|
|
134
|
-
setupPoses: Map<string, BonePose>;
|
|
135
|
-
previousSetupPoses: Map<string, BonePose>;
|
|
136
|
-
slotIndexByName: Map<string, number>;
|
|
137
|
-
slotCacheKeyByName: Map<string, string>;
|
|
138
|
-
sliderConstraintsByOrder: SliderConstraintData[];
|
|
139
|
-
sortedConstraintsBySkin: Map<string, SpineConstraintEntry[]>;
|
|
140
|
-
transformConstraintBoneSets: WeakMap<TransformConstraintData, Set<string>>;
|
|
141
|
-
transformWorldSpaceBonesScratch: string[];
|
|
142
|
-
drawOrderScratch: number[];
|
|
143
|
-
unchangedSlotScratch: number[];
|
|
144
|
-
orderedSlotScratch: SlotData[];
|
|
145
|
-
liveSlotKeys: Set<string>;
|
|
146
|
-
slotEntries: SpineSlotGeometryCache[];
|
|
147
|
-
changedVertexEntries: SpineSlotGeometryCache[];
|
|
148
|
-
changedDebugEntries: SpineSlotGeometryCache[];
|
|
149
|
-
sliderStateScratch: SliderAnimationState[];
|
|
150
|
-
sliderStatePool: SliderAnimationState[];
|
|
151
|
-
vertexDirtyRanges: SpineBufferDirtyRange[];
|
|
152
|
-
debugDirtyRanges: SpineBufferDirtyRange[];
|
|
153
|
-
mergedDirtyRanges: SpineBufferDirtyRange[];
|
|
154
|
-
vertexDirtyRangePool: SpineBufferDirtyRange[];
|
|
155
|
-
debugDirtyRangePool: SpineBufferDirtyRange[];
|
|
156
|
-
mergedDirtyRangePool: SpineBufferDirtyRange[];
|
|
157
|
-
vertexBuildResult: SpineVertexBuild;
|
|
158
|
-
slotColorScratch: [number, number, number, number];
|
|
159
|
-
sliderColorScratch: [number, number, number, number];
|
|
160
|
-
vertexColorScratch: [number, number, number, number];
|
|
161
|
-
timelineCompileStats: SpineTimelineCompileStats;
|
|
162
|
-
timelineSamplerState: SpineTimelineSamplerState;
|
|
163
|
-
allocationStats: SpineRuntimeAllocationStats;
|
|
164
|
-
ikStateScratch: { mix: number; softness: number; bendPositive: boolean };
|
|
165
|
-
transformStateScratch: Pick<TransformConstraintData, 'mixRotate' | 'mixX' | 'mixY' | 'mixScaleX' | 'mixScaleY' | 'mixShearY'>;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const sliderPoseSnapshots = new WeakMap<Spine2DComponent, LocalPoseSnapshot>();
|
|
169
|
-
const TWO_PI = Math.PI * 2;
|
|
170
|
-
const PATH_CONSTRAINT_DEPS = { normalizeRadians, updateDescendantBoneTrees };
|
|
171
|
-
|
|
172
|
-
function getSliderPoseSnapshot(component: Spine2DComponent): LocalPoseSnapshot {
|
|
173
|
-
let snapshot = sliderPoseSnapshots.get(component);
|
|
174
|
-
if (!snapshot) {
|
|
175
|
-
snapshot = new LocalPoseSnapshot();
|
|
176
|
-
sliderPoseSnapshots.set(component, snapshot);
|
|
177
|
-
}
|
|
178
|
-
return snapshot;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function advanceSpineRuntime(component: Spine2DComponent, delta: number): void {
|
|
182
|
-
component.elapsed += delta * component.timeScale;
|
|
183
|
-
if (component.previousAnimation) component.mixElapsed += delta * component.timeScale;
|
|
184
|
-
if (component.previousAnimation && component.mixElapsed >= component.mixDuration * 1000) component.previousAnimation = '';
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export function buildSpineVertices(component: Spine2DComponent, runtime: SpineRuntime): SpineVertexBuild {
|
|
188
|
-
const poses = computePoses(runtime, component);
|
|
189
|
-
const floats = runtime.vertexBuilder;
|
|
190
|
-
const debugFloats = runtime.debugVertexBuilder;
|
|
191
|
-
const skin = runtime.data.skins[component.skin] ?? runtime.data.skins.default ?? {};
|
|
192
|
-
const time = getCurrentAnimationTime(runtime.data, component);
|
|
193
|
-
const sliderStates = computeSliderAnimationStates(runtime, component, poses);
|
|
194
|
-
const entries = runtime.slotEntries;
|
|
195
|
-
const changedVertexEntries = runtime.changedVertexEntries;
|
|
196
|
-
const changedDebugEntries = runtime.changedDebugEntries;
|
|
197
|
-
const liveKeys = runtime.liveSlotKeys;
|
|
198
|
-
entries.length = 0;
|
|
199
|
-
changedVertexEntries.length = 0;
|
|
200
|
-
changedDebugEntries.length = 0;
|
|
201
|
-
liveKeys.clear();
|
|
202
|
-
let clip: { endSlot?: string; polygon: Array<[number, number]> } | null = null;
|
|
203
|
-
for (const slot of getDrawOrderedSlots(runtime, component, time)) {
|
|
204
|
-
const slotSkin = skin[slot.name] ?? {};
|
|
205
|
-
const attachmentName = getSpineSlotAttachmentName(runtime, component, slot, time);
|
|
206
|
-
if (!attachmentName) {
|
|
207
|
-
if (clip?.endSlot === slot.name) clip = null;
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
const attachment = slotSkin[attachmentName];
|
|
211
|
-
if (!attachment || attachment.type === 'boundingbox') {
|
|
212
|
-
if (clip?.endSlot === slot.name) clip = null;
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
const bone = poses.get(slot.bone);
|
|
216
|
-
if (attachment.type === 'clipping') {
|
|
217
|
-
if (bone) clip = {
|
|
218
|
-
...(attachment.end === undefined ? {} : { endSlot: attachment.end }),
|
|
219
|
-
polygon: computeClipPolygon(bone, attachment, component.scale),
|
|
220
|
-
};
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
const regionName = getSpineAttachmentRegionName(
|
|
224
|
-
runtime,
|
|
225
|
-
component,
|
|
226
|
-
slot,
|
|
227
|
-
attachmentName,
|
|
228
|
-
attachment,
|
|
229
|
-
time,
|
|
230
|
-
sliderStates,
|
|
231
|
-
);
|
|
232
|
-
const region = runtime.atlas.get(regionName) ?? runtime.atlas.get(attachment.path || attachment.name || attachmentName) ?? runtime.atlas.get(attachmentName);
|
|
233
|
-
if (!bone || !region) {
|
|
234
|
-
if (clip?.endSlot === slot.name) clip = null;
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
const slotColor = getSpineSlotColor(runtime, component, slot, time, sliderStates);
|
|
238
|
-
const cacheKey = runtime.slotCacheKeyByName.get(slot.name) ?? slot.name;
|
|
239
|
-
liveKeys.add(cacheKey);
|
|
240
|
-
const signature = getSlotBuildSignature(
|
|
241
|
-
slot, attachmentName, attachment, regionName, region, bone, poses, slotColor, clip, component,
|
|
242
|
-
);
|
|
243
|
-
const debugSignature = component.debugMesh ? hashInt(signature, 1) : 0;
|
|
244
|
-
let cache = runtime.slotGeometryCache.get(cacheKey);
|
|
245
|
-
const verticesChanged = !cache || cache.signature !== signature;
|
|
246
|
-
const debugChanged = !cache || cache.debugSignature !== debugSignature;
|
|
247
|
-
if (!cache) {
|
|
248
|
-
cache = createEmptySlotCache(cacheKey);
|
|
249
|
-
runtime.slotGeometryCache.set(cacheKey, cache);
|
|
250
|
-
}
|
|
251
|
-
if (verticesChanged || debugChanged) {
|
|
252
|
-
runtime.slotVertexBuilder.clear();
|
|
253
|
-
runtime.slotDebugVertexBuilder.clear();
|
|
254
|
-
if (attachment.type === 'mesh') {
|
|
255
|
-
appendMeshVertices(runtime.slotVertexBuilder, component, runtime, runtime.data, poses, bone, slotColor, attachment, region);
|
|
256
|
-
if (component.debugMesh) appendMeshWireframe(runtime.slotDebugVertexBuilder, component, runtime, runtime.data, poses, bone, attachment);
|
|
257
|
-
} else {
|
|
258
|
-
appendRegionVertices(runtime.slotVertexBuilder, component, runtime, bone, slotColor, attachment, region);
|
|
259
|
-
if (component.debugMesh) appendRegionWireframe(runtime.slotDebugVertexBuilder, component, bone, attachment, region);
|
|
260
|
-
}
|
|
261
|
-
if (clip) replaceClippedRange(runtime.slotVertexBuilder, 0, clip.polygon, runtime.clipBuilder);
|
|
262
|
-
const previousVertexCapacity = cache.vertexCapacity;
|
|
263
|
-
const previousDebugCapacity = cache.debugVertexCapacity;
|
|
264
|
-
const previousDebugLength = cache.debugVertexLength;
|
|
265
|
-
cache.signature = signature;
|
|
266
|
-
cache.debugSignature = debugSignature;
|
|
267
|
-
const previousVertices = cache.vertices;
|
|
268
|
-
cache.vertices = copyBuilderData(runtime.slotVertexBuilder, cache.vertices);
|
|
269
|
-
if (cache.vertices !== previousVertices) runtime.allocationStats.slotRegionGrowths++;
|
|
270
|
-
cache.vertexLength = runtime.slotVertexBuilder.length;
|
|
271
|
-
cache.vertexCapacity = cache.vertices.length;
|
|
272
|
-
const previousDebugVertices = cache.debugVertices;
|
|
273
|
-
cache.debugVertices = copyBuilderData(runtime.slotDebugVertexBuilder, cache.debugVertices);
|
|
274
|
-
if (cache.debugVertices !== previousDebugVertices) runtime.allocationStats.slotRegionGrowths++;
|
|
275
|
-
cache.debugVertexLength = runtime.slotDebugVertexBuilder.length;
|
|
276
|
-
cache.debugVertexCapacity = cache.debugVertices.length;
|
|
277
|
-
cache.batch.blend = normalizeBlend(slot.blend);
|
|
278
|
-
cache.batch.page = region.page;
|
|
279
|
-
cache.batch.vertexCount = cache.vertexLength / 8;
|
|
280
|
-
if (cache.vertexCapacity !== previousVertexCapacity) cache.vertexOffset = -1;
|
|
281
|
-
if (cache.debugVertexCapacity !== previousDebugCapacity || cache.debugVertexLength !== previousDebugLength) {
|
|
282
|
-
cache.debugVertexOffset = -1;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
if (verticesChanged) changedVertexEntries.push(cache);
|
|
286
|
-
if (debugChanged) changedDebugEntries.push(cache);
|
|
287
|
-
entries.push(cache);
|
|
288
|
-
if (clip?.endSlot === slot.name) clip = null;
|
|
289
|
-
}
|
|
290
|
-
if (component.debugBones) {
|
|
291
|
-
const cacheKey = '__debug_bones__';
|
|
292
|
-
liveKeys.add(cacheKey);
|
|
293
|
-
const signature = 0;
|
|
294
|
-
const debugSignature = getDebugBonesSignature(component.scale, poses);
|
|
295
|
-
let cache = runtime.slotGeometryCache.get(cacheKey);
|
|
296
|
-
const debugChanged = !cache || cache.debugSignature !== debugSignature;
|
|
297
|
-
if (!cache) {
|
|
298
|
-
cache = createEmptySlotCache(cacheKey);
|
|
299
|
-
runtime.slotGeometryCache.set(cacheKey, cache);
|
|
300
|
-
}
|
|
301
|
-
if (debugChanged) {
|
|
302
|
-
runtime.slotDebugVertexBuilder.clear();
|
|
303
|
-
appendBoneDebug(runtime.slotDebugVertexBuilder, runtime.data, poses, component.scale);
|
|
304
|
-
const previousDebugCapacity = cache.debugVertexCapacity;
|
|
305
|
-
const previousDebugLength = cache.debugVertexLength;
|
|
306
|
-
cache.signature = signature;
|
|
307
|
-
cache.debugSignature = debugSignature;
|
|
308
|
-
cache.vertexLength = 0;
|
|
309
|
-
const previousDebugVertices = cache.debugVertices;
|
|
310
|
-
cache.debugVertices = copyBuilderData(runtime.slotDebugVertexBuilder, cache.debugVertices);
|
|
311
|
-
if (cache.debugVertices !== previousDebugVertices) runtime.allocationStats.slotRegionGrowths++;
|
|
312
|
-
cache.debugVertexLength = runtime.slotDebugVertexBuilder.length;
|
|
313
|
-
cache.debugVertexCapacity = cache.debugVertices.length;
|
|
314
|
-
cache.batch.vertexCount = 0;
|
|
315
|
-
if (cache.debugVertexCapacity !== previousDebugCapacity || cache.debugVertexLength !== previousDebugLength) {
|
|
316
|
-
cache.debugVertexOffset = -1;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
if (debugChanged) changedDebugEntries.push(cache);
|
|
320
|
-
entries.push(cache);
|
|
321
|
-
}
|
|
322
|
-
for (const key of runtime.slotGeometryCache.keys()) {
|
|
323
|
-
if (!liveKeys.has(key)) runtime.slotGeometryCache.delete(key);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const slotOrderHash = getSlotOrderHash(entries);
|
|
327
|
-
const layoutChanged = runtime.slotOrderHash !== slotOrderHash || hasInvalidOffsets(entries);
|
|
328
|
-
const vertexDirtyRanges = runtime.vertexDirtyRanges;
|
|
329
|
-
const debugDirtyRanges = runtime.debugDirtyRanges;
|
|
330
|
-
vertexDirtyRanges.length = 0;
|
|
331
|
-
debugDirtyRanges.length = 0;
|
|
332
|
-
if (layoutChanged) {
|
|
333
|
-
rebuildCachedGeometryLayout(runtime, entries);
|
|
334
|
-
runtime.slotOrderHash = slotOrderHash;
|
|
335
|
-
if (floats.byteLength > 0) pushDirtyRange(runtime, vertexDirtyRanges, runtime.vertexDirtyRangePool, 0, floats.byteLength);
|
|
336
|
-
if (debugFloats.byteLength > 0) pushDirtyRange(runtime, debugDirtyRanges, runtime.debugDirtyRangePool, 0, debugFloats.byteLength);
|
|
337
|
-
} else {
|
|
338
|
-
for (const entry of changedVertexEntries) {
|
|
339
|
-
if (entry.vertexLength === 0) continue;
|
|
340
|
-
for (let index = 0; index < entry.vertexLength; index++) {
|
|
341
|
-
floats.data[entry.vertexOffset + index] = entry.vertices[index] ?? 0;
|
|
342
|
-
}
|
|
343
|
-
pushDirtyRange(runtime, vertexDirtyRanges, runtime.vertexDirtyRangePool, entry.vertexOffset * 4, entry.vertexLength * 4);
|
|
344
|
-
}
|
|
345
|
-
for (const entry of changedDebugEntries) {
|
|
346
|
-
if (entry.debugVertexLength === 0) continue;
|
|
347
|
-
for (let index = 0; index < entry.debugVertexLength; index++) {
|
|
348
|
-
debugFloats.data[entry.debugVertexOffset + index] = entry.debugVertices[index] ?? 0;
|
|
349
|
-
}
|
|
350
|
-
pushDirtyRange(runtime, debugDirtyRanges, runtime.debugDirtyRangePool, entry.debugVertexOffset * 4, entry.debugVertexLength * 4);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
rebuildCachedDrawBatches(runtime, entries);
|
|
354
|
-
const result = runtime.vertexBuildResult;
|
|
355
|
-
result.verticesChanged = vertexDirtyRanges.length > 0;
|
|
356
|
-
result.debugVerticesChanged = debugDirtyRanges.length > 0;
|
|
357
|
-
return result;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function pushDirtyRange(
|
|
361
|
-
runtime: SpineRuntime,
|
|
362
|
-
ranges: SpineBufferDirtyRange[],
|
|
363
|
-
pool: SpineBufferDirtyRange[],
|
|
364
|
-
byteOffset: number,
|
|
365
|
-
byteLength: number,
|
|
366
|
-
): void {
|
|
367
|
-
const index = ranges.length;
|
|
368
|
-
let range = pool[index];
|
|
369
|
-
if (!range) {
|
|
370
|
-
range = { byteOffset, byteLength };
|
|
371
|
-
pool.push(range);
|
|
372
|
-
runtime.allocationStats.dirtyRangePoolMisses++;
|
|
373
|
-
} else {
|
|
374
|
-
range.byteOffset = byteOffset;
|
|
375
|
-
range.byteLength = byteLength;
|
|
376
|
-
}
|
|
377
|
-
ranges.push(range);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
export async function loadSpineRuntime(
|
|
381
|
-
gpuRenderer: Spine2DGpuRenderer,
|
|
382
|
-
component: Spine2DComponent,
|
|
383
|
-
options: { assetWorker?: SpineAssetWorker | null; signal?: AbortSignal } = {},
|
|
384
|
-
): Promise<SpineRuntime> {
|
|
385
|
-
try {
|
|
386
|
-
return await loadSpineRuntimeUnchecked(gpuRenderer, component, options);
|
|
387
|
-
} catch (error) {
|
|
388
|
-
if (error instanceof EngineError) {
|
|
389
|
-
throw new EngineError(error.code, error.message, {
|
|
390
|
-
domain: error.domain,
|
|
391
|
-
recoverable: error.recoverable,
|
|
392
|
-
recovery: error.recovery,
|
|
393
|
-
context: {
|
|
394
|
-
resourceType: 'skeleton/spine',
|
|
395
|
-
jsonUrl: component.jsonUrl,
|
|
396
|
-
atlasUrl: component.atlasUrl,
|
|
397
|
-
...error.context,
|
|
398
|
-
},
|
|
399
|
-
...(error.path === undefined ? {} : { path: error.path }),
|
|
400
|
-
cause: error,
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
throw new EngineError(EngineErrorCode.AssetInvalidData, 'Failed to create the Spine runtime.', {
|
|
404
|
-
domain: ErrorDomain.Component,
|
|
405
|
-
recovery: ErrorRecovery.ReleaseResource,
|
|
406
|
-
context: { resourceType: 'skeleton/spine', jsonUrl: component.jsonUrl, atlasUrl: component.atlasUrl },
|
|
407
|
-
path: 'spine',
|
|
408
|
-
cause: error,
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
async function loadSpineRuntimeUnchecked(
|
|
414
|
-
gpuRenderer: Spine2DGpuRenderer,
|
|
415
|
-
component: Spine2DComponent,
|
|
416
|
-
options: { assetWorker?: SpineAssetWorker | null; signal?: AbortSignal },
|
|
417
|
-
): Promise<SpineRuntime> {
|
|
418
|
-
const parsed = await parseAssetWorkerFirst({
|
|
419
|
-
parser: {
|
|
420
|
-
type: 'skeleton/spine',
|
|
421
|
-
parse: (_input, context) => loadAndParseSpineMain(component, context.signal),
|
|
422
|
-
},
|
|
423
|
-
input: Object.freeze({ jsonUrl: component.jsonUrl, atlasUrl: component.atlasUrl }),
|
|
424
|
-
context: { ...(options.signal ? { signal: options.signal } : {}), source: component.jsonUrl },
|
|
425
|
-
worker: options.assetWorker
|
|
426
|
-
? async (_input, context) => requireSpineParsedAsset(await options.assetWorker!.loadParsedAsset(
|
|
427
|
-
component.jsonUrl,
|
|
428
|
-
component.atlasUrl,
|
|
429
|
-
context.signal ? { signal: context.signal } : {},
|
|
430
|
-
), component.jsonUrl)
|
|
431
|
-
: null,
|
|
432
|
-
});
|
|
433
|
-
throwIfAborted(options.signal);
|
|
434
|
-
const data = parsed.data as SpineData;
|
|
435
|
-
const atlas = new Map(parsed.regions);
|
|
436
|
-
const pages = await gpuRenderer.loadAtlasPages(component, atlas);
|
|
437
|
-
throwIfAborted(options.signal);
|
|
438
|
-
const runtimeBuffers = gpuRenderer.createRuntimeBuffers();
|
|
439
|
-
return createSpineRuntimeState(data, atlas, pages, runtimeBuffers);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
function requireSpineParsedAsset(value: unknown, source: string): SpineParsedAsset {
|
|
443
|
-
if (!value || typeof value !== 'object') throw invalidSpineWorkerPayload(source, value);
|
|
444
|
-
const candidate = value as { data?: unknown; regions?: unknown };
|
|
445
|
-
const data = candidate.data as { bones?: unknown } | null | undefined;
|
|
446
|
-
if (!data || typeof data !== 'object' || !Array.isArray(data.bones) || !Array.isArray(candidate.regions)) {
|
|
447
|
-
throw invalidSpineWorkerPayload(source, value);
|
|
448
|
-
}
|
|
449
|
-
return candidate as SpineParsedAsset;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function invalidSpineWorkerPayload(source: string, cause: unknown): EngineError {
|
|
453
|
-
return new EngineError(EngineErrorCode.WorkerProtocolInvalid, 'Spine worker returned an invalid parsed asset.', {
|
|
454
|
-
domain: ErrorDomain.Worker,
|
|
455
|
-
recovery: ErrorRecovery.TerminateRuntime,
|
|
456
|
-
context: { url: source, resourceType: 'skeleton/spine' },
|
|
457
|
-
path: 'spine.worker.response',
|
|
458
|
-
cause,
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
function createSpineRuntimeState(
|
|
463
|
-
data: SpineData,
|
|
464
|
-
atlas: Map<string, AtlasRegion>,
|
|
465
|
-
pages: Map<string, AtlasPageGpu>,
|
|
466
|
-
runtimeBuffers: Pick<SpineRuntime, 'vertexBuffer' | 'vertexBufferSize' | 'debugVertexBuffer' | 'debugVertexBufferSize'>,
|
|
467
|
-
): SpineRuntime {
|
|
468
|
-
const timelineSamplerState = createSpineTimelineSamplerState();
|
|
469
|
-
const timelineCompileStats = compileSpineTimelines(data.animations, timelineSamplerState);
|
|
470
|
-
compileAnimationBoneTimelines(data.animations);
|
|
471
|
-
const vertexBuilder = new SpineFloatBuilder(1024);
|
|
472
|
-
const debugVertexBuilder = new SpineFloatBuilder(256);
|
|
473
|
-
const batches: SpineDrawBatch[] = [];
|
|
474
|
-
const vertexDirtyRanges: SpineBufferDirtyRange[] = [];
|
|
475
|
-
const debugDirtyRanges: SpineBufferDirtyRange[] = [];
|
|
476
|
-
const rangePoolCapacity = Math.max(1, data.slots.length + 1);
|
|
477
|
-
const vertexBuildResult: SpineVertexBuild = {
|
|
478
|
-
vertices: vertexBuilder,
|
|
479
|
-
batches,
|
|
480
|
-
debugVertices: debugVertexBuilder,
|
|
481
|
-
vertexDirtyRanges,
|
|
482
|
-
debugDirtyRanges,
|
|
483
|
-
verticesChanged: false,
|
|
484
|
-
debugVerticesChanged: false,
|
|
485
|
-
};
|
|
486
|
-
return {
|
|
487
|
-
data,
|
|
488
|
-
atlas,
|
|
489
|
-
pages,
|
|
490
|
-
...runtimeBuffers,
|
|
491
|
-
batches,
|
|
492
|
-
batchPool: createBatchPool(Math.max(1, data.slots.length)),
|
|
493
|
-
vertexBuilder,
|
|
494
|
-
debugVertexBuilder,
|
|
495
|
-
slotVertexBuilder: new SpineFloatBuilder(256),
|
|
496
|
-
slotDebugVertexBuilder: new SpineFloatBuilder(128),
|
|
497
|
-
clipBuilder: new SpineFloatBuilder(256),
|
|
498
|
-
meshPointBuilder: new SpineFloatBuilder(256),
|
|
499
|
-
slotGeometryCache: new Map(),
|
|
500
|
-
slotOrderHash: 0,
|
|
501
|
-
boneChildren: buildBoneChildren(data),
|
|
502
|
-
setupPoses: createSetupPoses(data),
|
|
503
|
-
previousSetupPoses: createSetupPoses(data),
|
|
504
|
-
slotIndexByName: buildSlotIndexByName(data),
|
|
505
|
-
slotCacheKeyByName: buildSlotCacheKeys(data),
|
|
506
|
-
sliderConstraintsByOrder: buildSliderConstraintsByOrder(data),
|
|
507
|
-
sortedConstraintsBySkin: new Map(),
|
|
508
|
-
transformConstraintBoneSets: new WeakMap(),
|
|
509
|
-
transformWorldSpaceBonesScratch: [],
|
|
510
|
-
drawOrderScratch: [],
|
|
511
|
-
unchangedSlotScratch: [],
|
|
512
|
-
orderedSlotScratch: [],
|
|
513
|
-
liveSlotKeys: new Set(),
|
|
514
|
-
slotEntries: [],
|
|
515
|
-
changedVertexEntries: [],
|
|
516
|
-
changedDebugEntries: [],
|
|
517
|
-
sliderStateScratch: [],
|
|
518
|
-
sliderStatePool: [],
|
|
519
|
-
vertexDirtyRanges,
|
|
520
|
-
debugDirtyRanges,
|
|
521
|
-
mergedDirtyRanges: [],
|
|
522
|
-
vertexDirtyRangePool: createDirtyRangePool(rangePoolCapacity),
|
|
523
|
-
debugDirtyRangePool: createDirtyRangePool(rangePoolCapacity),
|
|
524
|
-
mergedDirtyRangePool: createDirtyRangePool(rangePoolCapacity),
|
|
525
|
-
vertexBuildResult,
|
|
526
|
-
slotColorScratch: [1, 1, 1, 1],
|
|
527
|
-
sliderColorScratch: [1, 1, 1, 1],
|
|
528
|
-
vertexColorScratch: [1, 1, 1, 1],
|
|
529
|
-
timelineCompileStats,
|
|
530
|
-
timelineSamplerState,
|
|
531
|
-
allocationStats: { slotRegionGrowths: 0, batchPoolMisses: 0, dirtyRangePoolMisses: 0 },
|
|
532
|
-
ikStateScratch: { mix: 0, softness: 0, bendPositive: true },
|
|
533
|
-
transformStateScratch: { mixRotate: 0, mixX: 0, mixY: 0, mixScaleX: 0, mixScaleY: 0, mixShearY: 0 },
|
|
534
|
-
...createSpinePathConstraintScratch(),
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
function createDirtyRangePool(capacity: number): SpineBufferDirtyRange[] {
|
|
539
|
-
return Array.from({ length: capacity }, () => ({ byteOffset: 0, byteLength: 0 }));
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function createBatchPool(capacity: number): SpineDrawBatch[] {
|
|
543
|
-
return Array.from({ length: capacity }, () => ({
|
|
544
|
-
blend: 'normal' as const,
|
|
545
|
-
page: '',
|
|
546
|
-
firstVertex: 0,
|
|
547
|
-
vertexCount: 0,
|
|
548
|
-
}));
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
/** Monorepo benchmark adapter; not re-exported from the Spine package facade. */
|
|
552
|
-
export function createSpineRuntimeForBenchmark(
|
|
553
|
-
data: SpineData,
|
|
554
|
-
atlas: Map<string, AtlasRegion>,
|
|
555
|
-
pages: Map<string, AtlasPageGpu>,
|
|
556
|
-
): SpineRuntime {
|
|
557
|
-
const placeholderBuffer = {} as GPUBuffer;
|
|
558
|
-
return createSpineRuntimeState(data, atlas, pages, {
|
|
559
|
-
vertexBuffer: placeholderBuffer,
|
|
560
|
-
vertexBufferSize: 0,
|
|
561
|
-
debugVertexBuffer: placeholderBuffer,
|
|
562
|
-
debugVertexBufferSize: 0,
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/** Monorepo benchmark adapter; samples the complete animation/constraint pose path. */
|
|
567
|
-
export function sampleSpinePosesForBenchmark(component: Spine2DComponent, runtime: SpineRuntime): number {
|
|
568
|
-
let checksum = 0;
|
|
569
|
-
for (const pose of computePoses(runtime, component).values()) {
|
|
570
|
-
checksum += pose.worldX + pose.worldY + pose.a + pose.d;
|
|
571
|
-
}
|
|
572
|
-
return checksum;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
async function loadAndParseSpineMain(component: Spine2DComponent, signal?: AbortSignal): Promise<SpineParsedAsset> {
|
|
576
|
-
const [json, atlasText] = await Promise.all([
|
|
577
|
-
fetchJson(component.jsonUrl, signal),
|
|
578
|
-
component.atlasUrl ? fetchText(component.atlasUrl, signal) : Promise.resolve(''),
|
|
579
|
-
]);
|
|
580
|
-
return await SPINE_ASSET_PARSER.parse(
|
|
581
|
-
{ json, atlasText },
|
|
582
|
-
{ source: component.jsonUrl, ...(signal === undefined ? {} : { signal }) },
|
|
583
|
-
);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
async function fetchJson(url: string, signal?: AbortSignal): Promise<unknown> {
|
|
587
|
-
const response = await fetch(url, signal ? { signal } : undefined);
|
|
588
|
-
if (!response.ok) {
|
|
589
|
-
throw new EngineError(EngineErrorCode.AssetLoadFailed, `Failed to load Spine JSON: ${response.status} ${response.statusText}`, {
|
|
590
|
-
domain: ErrorDomain.Component,
|
|
591
|
-
recovery: ErrorRecovery.Retry,
|
|
592
|
-
context: { url, resourceType: 'skeleton/spine-json', status: response.status },
|
|
593
|
-
path: 'spine.json',
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
try {
|
|
597
|
-
return await response.json() as unknown;
|
|
598
|
-
} catch (error) {
|
|
599
|
-
throw new EngineError(EngineErrorCode.AssetInvalidData, 'Spine JSON is not valid JSON.', {
|
|
600
|
-
domain: ErrorDomain.Component,
|
|
601
|
-
recovery: ErrorRecovery.ReleaseResource,
|
|
602
|
-
context: { url, resourceType: 'skeleton/spine-json' },
|
|
603
|
-
path: 'spine.json',
|
|
604
|
-
cause: error,
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
async function fetchText(url: string, signal?: AbortSignal): Promise<string> {
|
|
610
|
-
const response = await fetch(url, signal ? { signal } : undefined);
|
|
611
|
-
if (!response.ok) {
|
|
612
|
-
throw new EngineError(EngineErrorCode.AssetLoadFailed, `Failed to load Spine atlas: ${response.status} ${response.statusText}`, {
|
|
613
|
-
domain: ErrorDomain.Component,
|
|
614
|
-
recovery: ErrorRecovery.Retry,
|
|
615
|
-
context: { url, resourceType: 'skeleton/spine-atlas', status: response.status },
|
|
616
|
-
path: 'spine.atlas',
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
return response.text();
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
function throwIfAborted(signal: AbortSignal | undefined): void {
|
|
623
|
-
if (!signal?.aborted) return;
|
|
624
|
-
throw createAbortError('Spine load aborted.', signal.reason);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
function computePoses(runtime: SpineRuntime, component: Spine2DComponent): Map<string, BonePose> {
|
|
628
|
-
const data = runtime.data;
|
|
629
|
-
const children = runtime.boneChildren;
|
|
630
|
-
const local = resetSetupPoses(runtime.setupPoses);
|
|
631
|
-
if (
|
|
632
|
-
component.previousAnimation
|
|
633
|
-
&& component.mixDuration > 0
|
|
634
|
-
&& component.mixElapsed < component.mixDuration * 1000
|
|
635
|
-
&& data.animations[component.previousAnimation]
|
|
636
|
-
) {
|
|
637
|
-
const previous = resetSetupPoses(runtime.previousSetupPoses);
|
|
638
|
-
applyAnimationAt(runtime, data, component.previousAnimation, component.previousElapsed / 1000, component.loop, previous);
|
|
639
|
-
applyAnimationAt(runtime, data, component.animation, component.elapsed / 1000, component.loop, local);
|
|
640
|
-
const alpha = Math.max(0, Math.min(1, component.mixElapsed / (component.mixDuration * 1000)));
|
|
641
|
-
blendLocalPoses(local, previous, alpha);
|
|
642
|
-
} else {
|
|
643
|
-
applyAnimation(runtime, data, component, local);
|
|
644
|
-
}
|
|
645
|
-
applySliderBoneAnimations(runtime, component, local, true);
|
|
646
|
-
updateWorldPoses(data, local);
|
|
647
|
-
applyConstraints(runtime, component, local, children);
|
|
648
|
-
if (applySliderBoneAnimations(runtime, component, local, false)) updateWorldPoses(data, local);
|
|
649
|
-
return local;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
function buildBoneChildren(data: SpineData): Map<string, string[]> {
|
|
653
|
-
const children = new Map<string, string[]>();
|
|
654
|
-
for (const bone of data.bones) {
|
|
655
|
-
if (!bone.parent) continue;
|
|
656
|
-
const list = children.get(bone.parent) ?? [];
|
|
657
|
-
list.push(bone.name);
|
|
658
|
-
children.set(bone.parent, list);
|
|
659
|
-
}
|
|
660
|
-
return children;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
function buildSlotIndexByName(data: SpineData): Map<string, number> {
|
|
664
|
-
const indices = new Map<string, number>();
|
|
665
|
-
for (let i = 0; i < data.slots.length; i++) {
|
|
666
|
-
indices.set(requiredItemAt(data.slots, i, 'Spine slots').name, i);
|
|
667
|
-
}
|
|
668
|
-
return indices;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
function buildSlotCacheKeys(data: SpineData): Map<string, string> {
|
|
672
|
-
const keys = new Map<string, string>();
|
|
673
|
-
for (const slot of data.slots) keys.set(slot.name, `slot:${slot.name}`);
|
|
674
|
-
return keys;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
function buildSliderConstraintsByOrder(data: SpineData): SliderConstraintData[] {
|
|
678
|
-
return data.sliders.slice().sort((a, b) => a.order - b.order);
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
function createSetupPoses(data: SpineData): Map<string, BonePose> {
|
|
682
|
-
const local = new Map<string, BonePose>();
|
|
683
|
-
for (const bone of data.bones) {
|
|
684
|
-
local.set(bone.name, {
|
|
685
|
-
data: bone,
|
|
686
|
-
a: 1,
|
|
687
|
-
b: 0,
|
|
688
|
-
c: 0,
|
|
689
|
-
d: 1,
|
|
690
|
-
worldX: 0,
|
|
691
|
-
worldY: 0,
|
|
692
|
-
rotation: bone.rotation,
|
|
693
|
-
shearX: bone.shearX,
|
|
694
|
-
shearY: bone.shearY,
|
|
695
|
-
x: bone.x,
|
|
696
|
-
y: bone.y,
|
|
697
|
-
scaleX: bone.scaleX,
|
|
698
|
-
scaleY: bone.scaleY,
|
|
699
|
-
});
|
|
700
|
-
}
|
|
701
|
-
return local;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
function resetSetupPoses(poses: Map<string, BonePose>): Map<string, BonePose> {
|
|
705
|
-
for (const pose of poses.values()) {
|
|
706
|
-
const bone = pose.data;
|
|
707
|
-
pose.a = 1;
|
|
708
|
-
pose.b = 0;
|
|
709
|
-
pose.c = 0;
|
|
710
|
-
pose.d = 1;
|
|
711
|
-
pose.worldX = 0;
|
|
712
|
-
pose.worldY = 0;
|
|
713
|
-
pose.rotation = bone.rotation;
|
|
714
|
-
pose.shearX = bone.shearX;
|
|
715
|
-
pose.shearY = bone.shearY;
|
|
716
|
-
pose.x = bone.x;
|
|
717
|
-
pose.y = bone.y;
|
|
718
|
-
pose.scaleX = bone.scaleX;
|
|
719
|
-
pose.scaleY = bone.scaleY;
|
|
720
|
-
}
|
|
721
|
-
return poses;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
function blendLocalPoses(current: Map<string, BonePose>, previous: Map<string, BonePose>, alpha: number): void {
|
|
725
|
-
for (const [name, pose] of current) {
|
|
726
|
-
const from = previous.get(name);
|
|
727
|
-
if (!from) continue;
|
|
728
|
-
pose.x = lerpNumber(from.x, pose.x, alpha);
|
|
729
|
-
pose.y = lerpNumber(from.y, pose.y, alpha);
|
|
730
|
-
pose.rotation = from.rotation + normalizeAngle(pose.rotation - from.rotation) * alpha;
|
|
731
|
-
pose.shearX = lerpNumber(from.shearX, pose.shearX, alpha);
|
|
732
|
-
pose.shearY = lerpNumber(from.shearY, pose.shearY, alpha);
|
|
733
|
-
pose.scaleX = lerpNumber(from.scaleX, pose.scaleX, alpha);
|
|
734
|
-
pose.scaleY = lerpNumber(from.scaleY, pose.scaleY, alpha);
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
function blendLocalPosesFromSnapshot(current: Map<string, BonePose>, previous: LocalPoseSnapshot, alpha: number): void {
|
|
739
|
-
let offset = 0;
|
|
740
|
-
for (const pose of current.values()) {
|
|
741
|
-
if (offset + 6 >= previous.length) return;
|
|
742
|
-
const fromX = requiredNumberAt(previous.data, offset++, 'previous local pose');
|
|
743
|
-
const fromY = requiredNumberAt(previous.data, offset++, 'previous local pose');
|
|
744
|
-
const fromRotation = requiredNumberAt(previous.data, offset++, 'previous local pose');
|
|
745
|
-
const fromShearX = requiredNumberAt(previous.data, offset++, 'previous local pose');
|
|
746
|
-
const fromShearY = requiredNumberAt(previous.data, offset++, 'previous local pose');
|
|
747
|
-
const fromScaleX = requiredNumberAt(previous.data, offset++, 'previous local pose');
|
|
748
|
-
const fromScaleY = requiredNumberAt(previous.data, offset++, 'previous local pose');
|
|
749
|
-
pose.x = lerpNumber(fromX, pose.x, alpha);
|
|
750
|
-
pose.y = lerpNumber(fromY, pose.y, alpha);
|
|
751
|
-
pose.rotation = fromRotation + normalizeAngle(pose.rotation - fromRotation) * alpha;
|
|
752
|
-
pose.shearX = lerpNumber(fromShearX, pose.shearX, alpha);
|
|
753
|
-
pose.shearY = lerpNumber(fromShearY, pose.shearY, alpha);
|
|
754
|
-
pose.scaleX = lerpNumber(fromScaleX, pose.scaleX, alpha);
|
|
755
|
-
pose.scaleY = lerpNumber(fromScaleY, pose.scaleY, alpha);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
function lerpNumber(a: number, b: number, t: number): number {
|
|
760
|
-
return a + (b - a) * t;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
function getDrawOrderedSlots(runtime: SpineRuntime, component: Spine2DComponent, time: number): SlotData[] {
|
|
764
|
-
const data = runtime.data;
|
|
765
|
-
const frames = data.animations[component.animation]?.drawOrder;
|
|
766
|
-
if (!Array.isArray(frames) || frames.length === 0) return data.slots;
|
|
767
|
-
const frameIndex = findCompiledFrameIndex(frames, time, runtime.timelineSamplerState);
|
|
768
|
-
const frame = frameIndex >= 0 ? frames[frameIndex] : null;
|
|
769
|
-
if (!frame?.offsets?.length) return data.slots;
|
|
770
|
-
|
|
771
|
-
const drawOrder = runtime.drawOrderScratch;
|
|
772
|
-
const unchanged = runtime.unchangedSlotScratch;
|
|
773
|
-
const ordered = runtime.orderedSlotScratch;
|
|
774
|
-
drawOrder.length = data.slots.length;
|
|
775
|
-
unchanged.length = 0;
|
|
776
|
-
ordered.length = 0;
|
|
777
|
-
for (let i = 0; i < drawOrder.length; i++) drawOrder[i] = -1;
|
|
778
|
-
let originalIndex = 0;
|
|
779
|
-
for (const offset of frame.offsets) {
|
|
780
|
-
const slotIndex = runtime.slotIndexByName.get(offset.slot) ?? -1;
|
|
781
|
-
if (slotIndex < 0) continue;
|
|
782
|
-
while (originalIndex < slotIndex) unchanged.push(originalIndex++);
|
|
783
|
-
const drawIndex = originalIndex + (offset.offset ?? 0);
|
|
784
|
-
if (drawIndex >= 0 && drawIndex < drawOrder.length) drawOrder[drawIndex] = originalIndex;
|
|
785
|
-
originalIndex++;
|
|
786
|
-
}
|
|
787
|
-
while (originalIndex < data.slots.length) unchanged.push(originalIndex++);
|
|
788
|
-
let unchangedIndex = unchanged.length - 1;
|
|
789
|
-
for (let i = drawOrder.length - 1; i >= 0; i--) {
|
|
790
|
-
if (drawOrder[i] === -1) drawOrder[i] = unchanged[unchangedIndex--] ?? i;
|
|
791
|
-
}
|
|
792
|
-
for (let i = 0; i < drawOrder.length; i++) {
|
|
793
|
-
const drawIndex = drawOrder[i];
|
|
794
|
-
const slot = drawIndex === undefined ? undefined : data.slots[drawIndex];
|
|
795
|
-
if (slot) ordered.push(slot);
|
|
796
|
-
}
|
|
797
|
-
return ordered;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
function updateWorldPoses(data: SpineData, poses: Map<string, BonePose>): void {
|
|
801
|
-
for (const bone of data.bones) {
|
|
802
|
-
const pose = poses.get(bone.name)!;
|
|
803
|
-
const parent = bone.parent ? poses.get(bone.parent) ?? null : null;
|
|
804
|
-
updateWorldPose(pose, parent);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
function updateWorldPose(pose: BonePose, parent: BonePose | null): void {
|
|
809
|
-
const rx = (pose.rotation + pose.shearX) * Math.PI / 180;
|
|
810
|
-
const ry = (pose.rotation + 90 + pose.shearY) * Math.PI / 180;
|
|
811
|
-
const la = Math.cos(rx) * pose.scaleX;
|
|
812
|
-
const lb = Math.cos(ry) * pose.scaleY;
|
|
813
|
-
const lc = Math.sin(rx) * pose.scaleX;
|
|
814
|
-
const ld = Math.sin(ry) * pose.scaleY;
|
|
815
|
-
if (!parent) {
|
|
816
|
-
pose.worldX = pose.x;
|
|
817
|
-
pose.worldY = pose.y;
|
|
818
|
-
pose.a = la;
|
|
819
|
-
pose.b = lb;
|
|
820
|
-
pose.c = lc;
|
|
821
|
-
pose.d = ld;
|
|
822
|
-
return;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
pose.worldX = parent.a * pose.x + parent.b * pose.y + parent.worldX;
|
|
826
|
-
pose.worldY = parent.c * pose.x + parent.d * pose.y + parent.worldY;
|
|
827
|
-
|
|
828
|
-
switch (pose.data.inherit) {
|
|
829
|
-
case 'onlyTranslation':
|
|
830
|
-
pose.a = la;
|
|
831
|
-
pose.b = lb;
|
|
832
|
-
pose.c = lc;
|
|
833
|
-
pose.d = ld;
|
|
834
|
-
return;
|
|
835
|
-
case 'noRotationOrReflection':
|
|
836
|
-
updateNoRotationOrReflectionWorldPose(pose, parent);
|
|
837
|
-
return;
|
|
838
|
-
case 'noScale':
|
|
839
|
-
case 'noScaleOrReflection':
|
|
840
|
-
updateNoScaleWorldPose(pose, parent);
|
|
841
|
-
return;
|
|
842
|
-
default:
|
|
843
|
-
pose.a = parent.a * la + parent.b * lc;
|
|
844
|
-
pose.b = parent.a * lb + parent.b * ld;
|
|
845
|
-
pose.c = parent.c * la + parent.d * lc;
|
|
846
|
-
pose.d = parent.c * lb + parent.d * ld;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
function updateNoRotationOrReflectionWorldPose(pose: BonePose, parent: BonePose): void {
|
|
851
|
-
let pa = parent.a;
|
|
852
|
-
let pb = parent.b;
|
|
853
|
-
let pc = parent.c;
|
|
854
|
-
let pd = parent.d;
|
|
855
|
-
|
|
856
|
-
let s = pa * pa + pc * pc;
|
|
857
|
-
let rotation = 0;
|
|
858
|
-
if (s > 0.000001) {
|
|
859
|
-
s = Math.abs(pa * pd - pb * pc) / s;
|
|
860
|
-
pb = pc * s;
|
|
861
|
-
pd = pa * s;
|
|
862
|
-
rotation = pose.rotation - Math.atan2(pc, pa) * 180 / Math.PI;
|
|
863
|
-
} else {
|
|
864
|
-
pa = 0;
|
|
865
|
-
pc = 0;
|
|
866
|
-
rotation = pose.rotation - 90 + Math.atan2(pd, pb) * 180 / Math.PI;
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
const rx = (rotation + pose.shearX) * Math.PI / 180;
|
|
870
|
-
const ry = (rotation + 90 + pose.shearY) * Math.PI / 180;
|
|
871
|
-
const la = Math.cos(rx) * pose.scaleX;
|
|
872
|
-
const lb = Math.cos(ry) * pose.scaleY;
|
|
873
|
-
const lc = Math.sin(rx) * pose.scaleX;
|
|
874
|
-
const ld = Math.sin(ry) * pose.scaleY;
|
|
875
|
-
pose.a = pa * la - pb * lc;
|
|
876
|
-
pose.b = pa * lb - pb * ld;
|
|
877
|
-
pose.c = pc * la + pd * lc;
|
|
878
|
-
pose.d = pc * lb + pd * ld;
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
function updateNoScaleWorldPose(pose: BonePose, parent: BonePose): void {
|
|
882
|
-
const rotation = pose.rotation * Math.PI / 180;
|
|
883
|
-
const cos = Math.cos(rotation);
|
|
884
|
-
const sin = Math.sin(rotation);
|
|
885
|
-
let za = parent.a * cos + parent.b * sin;
|
|
886
|
-
let zc = parent.c * cos + parent.d * sin;
|
|
887
|
-
const length = Math.sqrt(za * za + zc * zc);
|
|
888
|
-
if (length > 0.000001) {
|
|
889
|
-
za /= length;
|
|
890
|
-
zc /= length;
|
|
891
|
-
}
|
|
892
|
-
let zb = -zc;
|
|
893
|
-
let zd = za;
|
|
894
|
-
const determinant = parent.a * parent.d - parent.b * parent.c;
|
|
895
|
-
if (pose.data.inherit === 'noScale' && determinant < 0) {
|
|
896
|
-
zb = -zb;
|
|
897
|
-
zd = -zd;
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
const rx = pose.shearX * Math.PI / 180;
|
|
901
|
-
const ry = (90 + pose.shearY) * Math.PI / 180;
|
|
902
|
-
const la = Math.cos(rx) * pose.scaleX;
|
|
903
|
-
const lb = Math.cos(ry) * pose.scaleY;
|
|
904
|
-
const lc = Math.sin(rx) * pose.scaleX;
|
|
905
|
-
const ld = Math.sin(ry) * pose.scaleY;
|
|
906
|
-
pose.a = za * la + zb * lc;
|
|
907
|
-
pose.b = za * lb + zb * ld;
|
|
908
|
-
pose.c = zc * la + zd * lc;
|
|
909
|
-
pose.d = zc * lb + zd * ld;
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
function applyAnimation(runtime: SpineRuntime, data: SpineData, component: Spine2DComponent, poses: Map<string, BonePose>): void {
|
|
913
|
-
applyAnimationAt(runtime, data, component.animation, getCurrentAnimationTime(data, component), component.loop, poses);
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
function applyAnimationAt(runtime: SpineRuntime, data: SpineData, animationName: string, rawTime: number, loop: boolean, poses: Map<string, BonePose>): void {
|
|
917
|
-
const animation = data.animations[animationName];
|
|
918
|
-
if (!animation?.bones) return;
|
|
919
|
-
const duration = getCompiledAnimationDuration(animation);
|
|
920
|
-
const time = loop && duration > 0 ? rawTime % duration : Math.min(rawTime, duration);
|
|
921
|
-
for (const entry of getAnimationBoneTimelines(animation)) {
|
|
922
|
-
const pose = poses.get(entry.boneName);
|
|
923
|
-
if (!pose) continue;
|
|
924
|
-
const timelines = entry.timelines;
|
|
925
|
-
if (timelines.rotate) pose.rotation = pose.data.rotation + sampleTimeline(timelines.rotate, time, 'angle', 0, duration, loop, runtime.timelineSamplerState);
|
|
926
|
-
if (timelines.translate) {
|
|
927
|
-
pose.x = pose.data.x + sampleTimeline(timelines.translate, time, 'x', 0, duration, loop, runtime.timelineSamplerState);
|
|
928
|
-
pose.y = pose.data.y + sampleTimeline(timelines.translate, time, 'y', 0, duration, loop, runtime.timelineSamplerState);
|
|
929
|
-
}
|
|
930
|
-
if (timelines.scale) {
|
|
931
|
-
pose.scaleX = pose.data.scaleX * sampleTimeline(timelines.scale, time, 'x', 1, duration, loop, runtime.timelineSamplerState);
|
|
932
|
-
pose.scaleY = pose.data.scaleY * sampleTimeline(timelines.scale, time, 'y', 1, duration, loop, runtime.timelineSamplerState);
|
|
933
|
-
}
|
|
934
|
-
if (timelines.shear) {
|
|
935
|
-
pose.shearX = pose.data.shearX + sampleTimeline(timelines.shear, time, 'x', 0, duration, loop, runtime.timelineSamplerState);
|
|
936
|
-
pose.shearY = pose.data.shearY + sampleTimeline(timelines.shear, time, 'y', 0, duration, loop, runtime.timelineSamplerState);
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
function computeSliderAnimationStates(
|
|
942
|
-
runtime: SpineRuntime,
|
|
943
|
-
component: Spine2DComponent,
|
|
944
|
-
poses: Map<string, BonePose>,
|
|
945
|
-
localOnly?: boolean,
|
|
946
|
-
): SliderAnimationState[] {
|
|
947
|
-
const data = runtime.data;
|
|
948
|
-
const states = runtime.sliderStateScratch;
|
|
949
|
-
states.length = 0;
|
|
950
|
-
if (!runtime.sliderConstraintsByOrder.length) return states;
|
|
951
|
-
const currentAnimation = data.animations[component.animation];
|
|
952
|
-
const currentDuration = getCompiledAnimationDuration(currentAnimation);
|
|
953
|
-
const currentTime = getCurrentAnimationTime(data, component);
|
|
954
|
-
for (const slider of runtime.sliderConstraintsByOrder) {
|
|
955
|
-
if (localOnly !== undefined && slider.local !== localOnly) continue;
|
|
956
|
-
if (slider.animation === component.animation) continue;
|
|
957
|
-
if (!slider.animation || slider.mix <= 0 || !data.animations[slider.animation]) continue;
|
|
958
|
-
if (!isConstraintActiveForSkin(data, component.skin, 'slider', slider.name, slider.skin)) continue;
|
|
959
|
-
const mix = sampleTimeline(currentAnimation?.slider?.[slider.name]?.mix ?? [], currentTime, 'mix', slider.mix, currentDuration, component.loop, runtime.timelineSamplerState);
|
|
960
|
-
if (mix <= 0) continue;
|
|
961
|
-
const duration = getCompiledAnimationDuration(data.animations[slider.animation]);
|
|
962
|
-
let time = slider.offset;
|
|
963
|
-
if (slider.bone) {
|
|
964
|
-
const bone = poses.get(slider.bone);
|
|
965
|
-
if (!bone) continue;
|
|
966
|
-
time += getSliderBoneProperty(bone, slider.property, slider.local) * slider.scale;
|
|
967
|
-
}
|
|
968
|
-
if (slider.loop && duration > 0) {
|
|
969
|
-
time %= duration;
|
|
970
|
-
if (time < 0) time += duration;
|
|
971
|
-
} else {
|
|
972
|
-
time = Math.max(0, Math.min(time, duration));
|
|
973
|
-
}
|
|
974
|
-
const stateIndex = states.length;
|
|
975
|
-
let state = runtime.sliderStatePool[stateIndex];
|
|
976
|
-
if (!state) {
|
|
977
|
-
state = { animation: slider.animation, time, loop: slider.loop, mix };
|
|
978
|
-
runtime.sliderStatePool.push(state);
|
|
979
|
-
} else {
|
|
980
|
-
state.animation = slider.animation;
|
|
981
|
-
state.time = time;
|
|
982
|
-
state.loop = slider.loop;
|
|
983
|
-
state.mix = mix;
|
|
984
|
-
}
|
|
985
|
-
states.push(state);
|
|
986
|
-
}
|
|
987
|
-
return states;
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
function applySliderBoneAnimations(
|
|
991
|
-
runtime: SpineRuntime,
|
|
992
|
-
component: Spine2DComponent,
|
|
993
|
-
poses: Map<string, BonePose>,
|
|
994
|
-
localOnly: boolean,
|
|
995
|
-
): boolean {
|
|
996
|
-
const data = runtime.data;
|
|
997
|
-
let applied = false;
|
|
998
|
-
let before: LocalPoseSnapshot | null = null;
|
|
999
|
-
for (const slider of computeSliderAnimationStates(runtime, component, poses, localOnly)) {
|
|
1000
|
-
if (!data.animations[slider.animation]?.bones) continue;
|
|
1001
|
-
if (slider.mix < 1) {
|
|
1002
|
-
before ??= getSliderPoseSnapshot(component);
|
|
1003
|
-
before.capture(poses);
|
|
1004
|
-
}
|
|
1005
|
-
applyAnimationAt(runtime, data, slider.animation, slider.time, slider.loop, poses);
|
|
1006
|
-
if (slider.mix < 1 && before) blendLocalPosesFromSnapshot(poses, before, slider.mix);
|
|
1007
|
-
applied = true;
|
|
1008
|
-
}
|
|
1009
|
-
return applied;
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
function getSliderBoneProperty(bone: BonePose, property: string, local: boolean): number {
|
|
1013
|
-
if (local) {
|
|
1014
|
-
if (property === 'x') return bone.x;
|
|
1015
|
-
if (property === 'y') return bone.y;
|
|
1016
|
-
if (property === 'scaleX') return bone.scaleX;
|
|
1017
|
-
if (property === 'scaleY') return bone.scaleY;
|
|
1018
|
-
if (property === 'shearX') return bone.shearX;
|
|
1019
|
-
if (property === 'shearY') return bone.shearY;
|
|
1020
|
-
return bone.rotation;
|
|
1021
|
-
}
|
|
1022
|
-
if (property === 'x') return bone.worldX;
|
|
1023
|
-
if (property === 'y') return bone.worldY;
|
|
1024
|
-
if (property === 'scaleX') return Math.hypot(bone.a, bone.c);
|
|
1025
|
-
if (property === 'scaleY') return Math.hypot(bone.b, bone.d);
|
|
1026
|
-
if (property === 'shearY') return Math.atan2(bone.d, bone.b) * 180 / Math.PI - Math.atan2(bone.c, bone.a) * 180 / Math.PI - 90;
|
|
1027
|
-
return Math.atan2(bone.c, bone.a) * 180 / Math.PI;
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
function applyConstraints(
|
|
1031
|
-
runtime: SpineRuntime,
|
|
1032
|
-
component: Spine2DComponent,
|
|
1033
|
-
poses: Map<string, BonePose>,
|
|
1034
|
-
children: Map<string, string[]>,
|
|
1035
|
-
): void {
|
|
1036
|
-
const data = runtime.data;
|
|
1037
|
-
const animation = data.animations[component.animation];
|
|
1038
|
-
const duration = getCompiledAnimationDuration(animation);
|
|
1039
|
-
const time = getCurrentAnimationTime(data, component);
|
|
1040
|
-
const constraints = getSortedConstraintEntries(runtime, component.skin);
|
|
1041
|
-
for (const constraint of constraints) {
|
|
1042
|
-
if (constraint.type === 'ik') {
|
|
1043
|
-
applyIkConstraint(runtime, animation, constraint.constraint, time, duration, component.loop, poses, children);
|
|
1044
|
-
} else if (constraint.type === 'path') {
|
|
1045
|
-
applyPathConstraint(
|
|
1046
|
-
runtime, component, constraint.constraint, time, duration, component.loop, poses, children,
|
|
1047
|
-
PATH_CONSTRAINT_DEPS,
|
|
1048
|
-
);
|
|
1049
|
-
} else {
|
|
1050
|
-
applyTransformConstraint(runtime, animation, constraint.constraint, time, duration, component.loop, poses, children);
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
function getSortedConstraintEntries(runtime: SpineRuntime, skinName: string): SpineConstraintEntry[] {
|
|
1056
|
-
const cached = runtime.sortedConstraintsBySkin.get(skinName);
|
|
1057
|
-
if (cached) return cached;
|
|
1058
|
-
|
|
1059
|
-
const data = runtime.data;
|
|
1060
|
-
const constraints: SpineConstraintEntry[] = [];
|
|
1061
|
-
for (const constraint of data.ik) {
|
|
1062
|
-
if (isConstraintActiveForSkin(data, skinName, 'ik', constraint.name, constraint.skin)) {
|
|
1063
|
-
constraints.push({ type: 'ik', order: constraint.order, constraint });
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
for (const constraint of data.path) {
|
|
1067
|
-
if (isConstraintActiveForSkin(data, skinName, 'path', constraint.name, constraint.skin)) {
|
|
1068
|
-
constraints.push({ type: 'path', order: constraint.order, constraint });
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
for (const constraint of data.transform) {
|
|
1072
|
-
if (isConstraintActiveForSkin(data, skinName, 'transform', constraint.name, constraint.skin)) {
|
|
1073
|
-
constraints.push({ type: 'transform', order: constraint.order, constraint });
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
constraints.sort((a, b) => a.order - b.order);
|
|
1077
|
-
runtime.sortedConstraintsBySkin.set(skinName, constraints);
|
|
1078
|
-
return constraints;
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
function isConstraintActiveForSkin(
|
|
1082
|
-
data: SpineData,
|
|
1083
|
-
skinName: string,
|
|
1084
|
-
type: 'ik' | 'path' | 'transform' | 'slider',
|
|
1085
|
-
constraintName: string,
|
|
1086
|
-
skinRequired: boolean,
|
|
1087
|
-
): boolean {
|
|
1088
|
-
if (!skinRequired) return true;
|
|
1089
|
-
return data.skinConstraints[skinName]?.[type]?.includes(constraintName) ?? false;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
function applyIkConstraint(
|
|
1093
|
-
runtime: SpineRuntime,
|
|
1094
|
-
animation: SpineAnimationData | undefined,
|
|
1095
|
-
constraint: IkConstraintData,
|
|
1096
|
-
time: number,
|
|
1097
|
-
duration: number,
|
|
1098
|
-
loop: boolean,
|
|
1099
|
-
poses: Map<string, BonePose>,
|
|
1100
|
-
children: Map<string, string[]>,
|
|
1101
|
-
): void {
|
|
1102
|
-
const timeline = animation?.ik?.[constraint.name];
|
|
1103
|
-
const state = sampleIkConstraint(
|
|
1104
|
-
constraint, timeline, time, duration, loop, runtime.ikStateScratch, runtime.timelineSamplerState,
|
|
1105
|
-
);
|
|
1106
|
-
if (state.mix <= 0) return;
|
|
1107
|
-
if (constraint.bones.length === 1) {
|
|
1108
|
-
const rootBone = constraint.bones[0];
|
|
1109
|
-
if (rootBone === undefined) return;
|
|
1110
|
-
applyOneBoneIk(constraint, state, poses);
|
|
1111
|
-
updateBoneTree(rootBone, poses, children);
|
|
1112
|
-
} else if (constraint.bones.length === 2) {
|
|
1113
|
-
const rootBone = constraint.bones[0];
|
|
1114
|
-
if (rootBone === undefined) return;
|
|
1115
|
-
applyTwoBoneIk(constraint, state, poses);
|
|
1116
|
-
updateBoneTree(rootBone, poses, children);
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
function sampleIkConstraint(
|
|
1121
|
-
constraint: IkConstraintData,
|
|
1122
|
-
frames: unknown[] | undefined,
|
|
1123
|
-
time: number,
|
|
1124
|
-
duration: number,
|
|
1125
|
-
loop: boolean,
|
|
1126
|
-
out: { mix: number; softness: number; bendPositive: boolean },
|
|
1127
|
-
samplerState: SpineTimelineSamplerState,
|
|
1128
|
-
): { mix: number; softness: number; bendPositive: boolean } {
|
|
1129
|
-
out.mix = sampleTimeline(frames ?? [], time, 'mix', constraint.mix, duration, loop, samplerState);
|
|
1130
|
-
out.softness = sampleTimeline(frames ?? [], time, 'softness', constraint.softness, duration, loop, samplerState);
|
|
1131
|
-
let bendPositive = constraint.bendPositive;
|
|
1132
|
-
if (!Array.isArray(frames) || frames.length === 0) {
|
|
1133
|
-
out.bendPositive = bendPositive;
|
|
1134
|
-
return out;
|
|
1135
|
-
}
|
|
1136
|
-
const frameIndex = findCompiledFrameIndex(frames, time, samplerState);
|
|
1137
|
-
const frame = frameIndex >= 0 ? frames[frameIndex] : null;
|
|
1138
|
-
if (frame && typeof frame === 'object') {
|
|
1139
|
-
const value = (frame as Record<string, unknown>).bendPositive;
|
|
1140
|
-
if (typeof value === 'boolean') bendPositive = value;
|
|
1141
|
-
}
|
|
1142
|
-
out.bendPositive = bendPositive;
|
|
1143
|
-
return out;
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
function applyTransformConstraint(
|
|
1147
|
-
runtime: SpineRuntime,
|
|
1148
|
-
animation: SpineAnimationData | undefined,
|
|
1149
|
-
constraint: TransformConstraintData,
|
|
1150
|
-
time: number,
|
|
1151
|
-
duration: number,
|
|
1152
|
-
loop: boolean,
|
|
1153
|
-
poses: Map<string, BonePose>,
|
|
1154
|
-
children: Map<string, string[]>,
|
|
1155
|
-
): void {
|
|
1156
|
-
const state = sampleTransformConstraint(
|
|
1157
|
-
constraint,
|
|
1158
|
-
animation?.transform?.[constraint.name],
|
|
1159
|
-
time,
|
|
1160
|
-
duration,
|
|
1161
|
-
loop,
|
|
1162
|
-
runtime.transformStateScratch,
|
|
1163
|
-
runtime.timelineSamplerState,
|
|
1164
|
-
);
|
|
1165
|
-
if (
|
|
1166
|
-
state.mixRotate === 0
|
|
1167
|
-
&& state.mixX === 0
|
|
1168
|
-
&& state.mixY === 0
|
|
1169
|
-
&& state.mixScaleX === 0
|
|
1170
|
-
&& state.mixScaleY === 0
|
|
1171
|
-
&& state.mixShearY === 0
|
|
1172
|
-
) return;
|
|
1173
|
-
const source = poses.get(constraint.target);
|
|
1174
|
-
if (!source) return;
|
|
1175
|
-
let constrainedBones = runtime.transformConstraintBoneSets.get(constraint);
|
|
1176
|
-
if (!constrainedBones) {
|
|
1177
|
-
constrainedBones = new Set(constraint.bones);
|
|
1178
|
-
runtime.transformConstraintBoneSets.set(constraint, constrainedBones);
|
|
1179
|
-
}
|
|
1180
|
-
const worldSpaceBones = runtime.transformWorldSpaceBonesScratch;
|
|
1181
|
-
worldSpaceBones.length = 0;
|
|
1182
|
-
for (const boneName of constraint.bones) {
|
|
1183
|
-
const bone = poses.get(boneName);
|
|
1184
|
-
if (!bone) continue;
|
|
1185
|
-
if (constraint.localTarget || constraint.localSource) {
|
|
1186
|
-
applyLocalTransformConstraint(constraint, state, source, bone);
|
|
1187
|
-
updateBoneTree(boneName, poses, children);
|
|
1188
|
-
} else {
|
|
1189
|
-
applyWorldTransformConstraint(constraint, state, source, bone);
|
|
1190
|
-
worldSpaceBones.push(boneName);
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
for (const boneName of worldSpaceBones) updateDescendantBoneTrees(boneName, poses, children, constrainedBones);
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
function sampleTransformConstraint(
|
|
1197
|
-
constraint: TransformConstraintData,
|
|
1198
|
-
frames: unknown[] | undefined,
|
|
1199
|
-
time: number,
|
|
1200
|
-
duration: number,
|
|
1201
|
-
loop: boolean,
|
|
1202
|
-
out: Pick<TransformConstraintData, 'mixRotate' | 'mixX' | 'mixY' | 'mixScaleX' | 'mixScaleY' | 'mixShearY'>,
|
|
1203
|
-
samplerState: SpineTimelineSamplerState,
|
|
1204
|
-
): Pick<TransformConstraintData, 'mixRotate' | 'mixX' | 'mixY' | 'mixScaleX' | 'mixScaleY' | 'mixShearY'> {
|
|
1205
|
-
out.mixRotate = sampleTimeline(frames ?? [], time, 'mixRotate', constraint.mixRotate, duration, loop, samplerState);
|
|
1206
|
-
out.mixX = sampleTimeline(frames ?? [], time, 'mixX', constraint.mixX, duration, loop, samplerState);
|
|
1207
|
-
out.mixY = sampleTimeline(frames ?? [], time, 'mixY', constraint.mixY, duration, loop, samplerState);
|
|
1208
|
-
out.mixScaleX = sampleTimeline(frames ?? [], time, 'mixScaleX', constraint.mixScaleX, duration, loop, samplerState);
|
|
1209
|
-
out.mixScaleY = sampleTimeline(frames ?? [], time, 'mixScaleY', constraint.mixScaleY, duration, loop, samplerState);
|
|
1210
|
-
out.mixShearY = sampleTimeline(frames ?? [], time, 'mixShearY', constraint.mixShearY, duration, loop, samplerState);
|
|
1211
|
-
return out;
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
function applyWorldTransformConstraint(
|
|
1215
|
-
constraint: TransformConstraintData,
|
|
1216
|
-
state: Pick<TransformConstraintData, 'mixRotate' | 'mixX' | 'mixY' | 'mixScaleX' | 'mixScaleY' | 'mixShearY'>,
|
|
1217
|
-
source: BonePose,
|
|
1218
|
-
bone: BonePose,
|
|
1219
|
-
): void {
|
|
1220
|
-
if (state.mixRotate !== 0) {
|
|
1221
|
-
let value = Math.atan2(source.c, source.a) * 180 / Math.PI
|
|
1222
|
-
+ ((source.a * source.d - source.b * source.c) > 0 ? constraint.rotation : -constraint.rotation);
|
|
1223
|
-
if (value < 0) value += 360;
|
|
1224
|
-
if (!constraint.additive) value -= Math.atan2(bone.c, bone.a) * 180 / Math.PI;
|
|
1225
|
-
value = normalizeAngle(value) * Math.PI / 180 * state.mixRotate;
|
|
1226
|
-
const cos = Math.cos(value);
|
|
1227
|
-
const sin = Math.sin(value);
|
|
1228
|
-
const a = bone.a;
|
|
1229
|
-
const b = bone.b;
|
|
1230
|
-
const c = bone.c;
|
|
1231
|
-
const d = bone.d;
|
|
1232
|
-
bone.a = cos * a - sin * c;
|
|
1233
|
-
bone.b = cos * b - sin * d;
|
|
1234
|
-
bone.c = sin * a + cos * c;
|
|
1235
|
-
bone.d = sin * b + cos * d;
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
if (state.mixX !== 0) {
|
|
1239
|
-
let value = constraint.x * source.a + constraint.y * source.b + source.worldX;
|
|
1240
|
-
if (!constraint.additive) value -= bone.worldX;
|
|
1241
|
-
bone.worldX += value * state.mixX;
|
|
1242
|
-
}
|
|
1243
|
-
if (state.mixY !== 0) {
|
|
1244
|
-
let value = constraint.x * source.c + constraint.y * source.d + source.worldY;
|
|
1245
|
-
if (!constraint.additive) value -= bone.worldY;
|
|
1246
|
-
bone.worldY += value * state.mixY;
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
if (state.mixScaleX !== 0) {
|
|
1250
|
-
const sourceScale = Math.hypot(source.a, source.c) + constraint.scaleX;
|
|
1251
|
-
const boneScale = Math.hypot(bone.a, bone.c);
|
|
1252
|
-
if (constraint.additive) {
|
|
1253
|
-
const scale = 1 + (sourceScale - 1) * state.mixScaleX;
|
|
1254
|
-
bone.a *= scale;
|
|
1255
|
-
bone.c *= scale;
|
|
1256
|
-
} else if (boneScale !== 0) {
|
|
1257
|
-
const scale = 1 + (sourceScale - boneScale) * state.mixScaleX / boneScale;
|
|
1258
|
-
bone.a *= scale;
|
|
1259
|
-
bone.c *= scale;
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
if (state.mixScaleY !== 0) {
|
|
1263
|
-
const sourceScale = Math.hypot(source.b, source.d) + constraint.scaleY;
|
|
1264
|
-
const boneScale = Math.hypot(bone.b, bone.d);
|
|
1265
|
-
if (constraint.additive) {
|
|
1266
|
-
const scale = 1 + (sourceScale - 1) * state.mixScaleY;
|
|
1267
|
-
bone.b *= scale;
|
|
1268
|
-
bone.d *= scale;
|
|
1269
|
-
} else if (boneScale !== 0) {
|
|
1270
|
-
const scale = 1 + (sourceScale - boneScale) * state.mixScaleY / boneScale;
|
|
1271
|
-
bone.b *= scale;
|
|
1272
|
-
bone.d *= scale;
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
if (state.mixShearY !== 0) {
|
|
1277
|
-
const by = Math.atan2(bone.d, bone.b);
|
|
1278
|
-
let value = (Math.atan2(source.d, source.b) - Math.atan2(source.c, source.a)) * 180 / Math.PI - 90 + constraint.shearY;
|
|
1279
|
-
value = (value + 90) * Math.PI / 180;
|
|
1280
|
-
if (constraint.additive) value -= Math.PI / 2;
|
|
1281
|
-
else value = by + normalizeRadians(value - by + Math.atan2(bone.c, bone.a)) * state.mixShearY;
|
|
1282
|
-
const scale = Math.hypot(bone.b, bone.d);
|
|
1283
|
-
bone.b = Math.cos(value) * scale;
|
|
1284
|
-
bone.d = Math.sin(value) * scale;
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
function applyLocalTransformConstraint(
|
|
1289
|
-
constraint: TransformConstraintData,
|
|
1290
|
-
state: Pick<TransformConstraintData, 'mixRotate' | 'mixX' | 'mixY' | 'mixScaleX' | 'mixScaleY' | 'mixShearY'>,
|
|
1291
|
-
source: BonePose,
|
|
1292
|
-
bone: BonePose,
|
|
1293
|
-
): void {
|
|
1294
|
-
if (state.mixRotate !== 0) {
|
|
1295
|
-
const value = source.rotation + constraint.rotation;
|
|
1296
|
-
bone.rotation += (constraint.additive ? value : value - bone.rotation) * state.mixRotate;
|
|
1297
|
-
}
|
|
1298
|
-
if (state.mixX !== 0) {
|
|
1299
|
-
const value = source.x + constraint.x;
|
|
1300
|
-
bone.x += (constraint.additive ? value : value - bone.x) * state.mixX;
|
|
1301
|
-
}
|
|
1302
|
-
if (state.mixY !== 0) {
|
|
1303
|
-
const value = source.y + constraint.y;
|
|
1304
|
-
bone.y += (constraint.additive ? value : value - bone.y) * state.mixY;
|
|
1305
|
-
}
|
|
1306
|
-
if (state.mixScaleX !== 0) {
|
|
1307
|
-
const value = source.scaleX + constraint.scaleX;
|
|
1308
|
-
if (constraint.additive) bone.scaleX *= 1 + (value - 1) * state.mixScaleX;
|
|
1309
|
-
else bone.scaleX += (value - bone.scaleX) * state.mixScaleX;
|
|
1310
|
-
}
|
|
1311
|
-
if (state.mixScaleY !== 0) {
|
|
1312
|
-
const value = source.scaleY + constraint.scaleY;
|
|
1313
|
-
if (constraint.additive) bone.scaleY *= 1 + (value - 1) * state.mixScaleY;
|
|
1314
|
-
else bone.scaleY += (value - bone.scaleY) * state.mixScaleY;
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
function applyOneBoneIk(
|
|
1319
|
-
constraint: IkConstraintData,
|
|
1320
|
-
state: { mix: number; softness: number; bendPositive: boolean },
|
|
1321
|
-
poses: Map<string, BonePose>,
|
|
1322
|
-
): void {
|
|
1323
|
-
const boneName = constraint.bones[0];
|
|
1324
|
-
if (boneName === undefined) return;
|
|
1325
|
-
const bone = poses.get(boneName);
|
|
1326
|
-
const target = poses.get(constraint.target);
|
|
1327
|
-
if (!bone || !target) return;
|
|
1328
|
-
const parent = bone.data.parent ? poses.get(bone.data.parent) : null;
|
|
1329
|
-
const [targetX, targetY] = parent
|
|
1330
|
-
? worldToLocal(parent, target.worldX, target.worldY)
|
|
1331
|
-
: [target.worldX, target.worldY];
|
|
1332
|
-
const rotation = Math.atan2(targetY - bone.y, targetX - bone.x) * 180 / Math.PI;
|
|
1333
|
-
bone.rotation += normalizeAngle(rotation - bone.rotation) * state.mix;
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
function applyTwoBoneIk(
|
|
1337
|
-
constraint: IkConstraintData,
|
|
1338
|
-
state: { mix: number; softness: number; bendPositive: boolean },
|
|
1339
|
-
poses: Map<string, BonePose>,
|
|
1340
|
-
): void {
|
|
1341
|
-
const parentName = constraint.bones[0];
|
|
1342
|
-
const childName = constraint.bones[1];
|
|
1343
|
-
if (parentName === undefined || childName === undefined) return;
|
|
1344
|
-
const parent = poses.get(parentName);
|
|
1345
|
-
const child = poses.get(childName);
|
|
1346
|
-
const target = poses.get(constraint.target);
|
|
1347
|
-
if (!parent || !child || !target) return;
|
|
1348
|
-
if (parent.data.inherit && parent.data.inherit !== 'normal') return;
|
|
1349
|
-
if (child.data.inherit && child.data.inherit !== 'normal') return;
|
|
1350
|
-
const grandParent = parent.data.parent ? poses.get(parent.data.parent) : null;
|
|
1351
|
-
if (!grandParent) return;
|
|
1352
|
-
|
|
1353
|
-
let parentScaleX = parent.scaleX;
|
|
1354
|
-
let parentScaleY = parent.scaleY;
|
|
1355
|
-
let childScaleX = child.scaleX;
|
|
1356
|
-
let parentOffset = 0;
|
|
1357
|
-
let childOffset = 0;
|
|
1358
|
-
let sign = 1;
|
|
1359
|
-
if (parentScaleX < 0) {
|
|
1360
|
-
parentScaleX = -parentScaleX;
|
|
1361
|
-
parentOffset = 180;
|
|
1362
|
-
sign = -1;
|
|
1363
|
-
}
|
|
1364
|
-
if (parentScaleY < 0) {
|
|
1365
|
-
parentScaleY = -parentScaleY;
|
|
1366
|
-
sign = -sign;
|
|
1367
|
-
}
|
|
1368
|
-
if (childScaleX < 0) {
|
|
1369
|
-
childScaleX = -childScaleX;
|
|
1370
|
-
childOffset = 180;
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
let childWorldX = 0;
|
|
1374
|
-
let childWorldY = 0;
|
|
1375
|
-
const uniformScale = Math.abs(parentScaleX - parentScaleY) <= 0.000001;
|
|
1376
|
-
if (!uniformScale) {
|
|
1377
|
-
child.y = 0;
|
|
1378
|
-
childWorldX = parent.a * child.x + parent.worldX;
|
|
1379
|
-
childWorldY = parent.c * child.x + parent.worldY;
|
|
1380
|
-
} else {
|
|
1381
|
-
childWorldX = parent.a * child.x + parent.b * child.y + parent.worldX;
|
|
1382
|
-
childWorldY = parent.c * child.x + parent.d * child.y + parent.worldY;
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
let a = grandParent.a;
|
|
1386
|
-
let b = grandParent.b;
|
|
1387
|
-
let c = grandParent.c;
|
|
1388
|
-
let d = grandParent.d;
|
|
1389
|
-
let det = a * d - b * c;
|
|
1390
|
-
det = Math.abs(det) <= 0.000001 ? 0 : 1 / det;
|
|
1391
|
-
let x = childWorldX - grandParent.worldX;
|
|
1392
|
-
let y = childWorldY - grandParent.worldY;
|
|
1393
|
-
const dx = (x * d - y * b) * det - parent.x;
|
|
1394
|
-
const dy = (y * a - x * c) * det - parent.y;
|
|
1395
|
-
let length1 = Math.hypot(dx, dy);
|
|
1396
|
-
let length2 = child.data.length * childScaleX;
|
|
1397
|
-
if (length1 < 0.000001) {
|
|
1398
|
-
applyOneBoneIk({ ...constraint, bones: [parentName] }, state, poses);
|
|
1399
|
-
child.rotation = 0;
|
|
1400
|
-
return;
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1403
|
-
x = target.worldX - grandParent.worldX;
|
|
1404
|
-
y = target.worldY - grandParent.worldY;
|
|
1405
|
-
let targetX = (x * d - y * b) * det - parent.x;
|
|
1406
|
-
let targetY = (y * a - x * c) * det - parent.y;
|
|
1407
|
-
let distanceSq = targetX * targetX + targetY * targetY;
|
|
1408
|
-
if (state.softness !== 0) {
|
|
1409
|
-
const softness = state.softness * parentScaleX * (childScaleX + 1) * 0.5;
|
|
1410
|
-
const targetDistance = Math.sqrt(distanceSq);
|
|
1411
|
-
const softDistance = targetDistance - length1 - length2 * parentScaleX + softness;
|
|
1412
|
-
if (softDistance > 0 && targetDistance > 0.000001) {
|
|
1413
|
-
let percent = Math.min(1, softDistance / (softness * 2)) - 1;
|
|
1414
|
-
percent = (softDistance - softness * (1 - percent * percent)) / targetDistance;
|
|
1415
|
-
targetX -= percent * targetX;
|
|
1416
|
-
targetY -= percent * targetY;
|
|
1417
|
-
distanceSq = targetX * targetX + targetY * targetY;
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
const bend = state.bendPositive ? 1 : -1;
|
|
1422
|
-
let angle1 = 0;
|
|
1423
|
-
let angle2 = 0;
|
|
1424
|
-
if (uniformScale) {
|
|
1425
|
-
length2 *= parentScaleX;
|
|
1426
|
-
let cos = (distanceSq - length1 * length1 - length2 * length2) / (2 * length1 * length2);
|
|
1427
|
-
if (cos < -1) {
|
|
1428
|
-
cos = -1;
|
|
1429
|
-
angle2 = Math.PI * bend;
|
|
1430
|
-
} else if (cos > 1) {
|
|
1431
|
-
cos = 1;
|
|
1432
|
-
angle2 = 0;
|
|
1433
|
-
} else {
|
|
1434
|
-
angle2 = Math.acos(cos) * bend;
|
|
1435
|
-
}
|
|
1436
|
-
a = length1 + length2 * cos;
|
|
1437
|
-
b = length2 * Math.sin(angle2);
|
|
1438
|
-
angle1 = Math.atan2(targetY * a - targetX * b, targetX * a + targetY * b);
|
|
1439
|
-
} else {
|
|
1440
|
-
a = parentScaleX * length2;
|
|
1441
|
-
b = parentScaleY * length2;
|
|
1442
|
-
const aa = a * a;
|
|
1443
|
-
const bb = b * b;
|
|
1444
|
-
const targetAngle = Math.atan2(targetY, targetX);
|
|
1445
|
-
c = bb * length1 * length1 + aa * distanceSq - aa * bb;
|
|
1446
|
-
const c1 = -2 * bb * length1;
|
|
1447
|
-
const c2 = bb - aa;
|
|
1448
|
-
d = c1 * c1 - 4 * c2 * c;
|
|
1449
|
-
if (d >= 0) {
|
|
1450
|
-
let q = Math.sqrt(d);
|
|
1451
|
-
if (c1 < 0) q = -q;
|
|
1452
|
-
q = -(c1 + q) * 0.5;
|
|
1453
|
-
let r0 = q / c2;
|
|
1454
|
-
const r1 = c / q;
|
|
1455
|
-
const r = Math.abs(r0) < Math.abs(r1) ? r0 : r1;
|
|
1456
|
-
r0 = distanceSq - r * r;
|
|
1457
|
-
if (r0 >= 0) {
|
|
1458
|
-
y = Math.sqrt(r0) * bend;
|
|
1459
|
-
angle1 = targetAngle - Math.atan2(y, r);
|
|
1460
|
-
angle2 = Math.atan2(y / parentScaleY, (r - length1) / parentScaleX);
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
if (!Number.isFinite(angle1) || !Number.isFinite(angle2)) {
|
|
1464
|
-
const minX = length1 - a;
|
|
1465
|
-
const maxX = length1 + a;
|
|
1466
|
-
if (distanceSq <= (minX * minX + maxX * maxX) * 0.5) {
|
|
1467
|
-
angle1 = targetAngle;
|
|
1468
|
-
angle2 = Math.PI * bend;
|
|
1469
|
-
} else {
|
|
1470
|
-
angle1 = targetAngle;
|
|
1471
|
-
angle2 = 0;
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
const childOffsetAngle = Math.atan2(child.y, child.x) * sign;
|
|
1477
|
-
let parentDelta = (angle1 - childOffsetAngle) * 180 / Math.PI + parentOffset - parent.rotation;
|
|
1478
|
-
parent.rotation += normalizeAngle(parentDelta) * state.mix;
|
|
1479
|
-
let childDelta = ((angle2 + childOffsetAngle) * 180 / Math.PI) * sign + childOffset - child.rotation;
|
|
1480
|
-
child.rotation += normalizeAngle(childDelta) * state.mix;
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
function updateBoneTree(name: string, poses: Map<string, BonePose>, children: Map<string, string[]>): void {
|
|
1484
|
-
const pose = poses.get(name);
|
|
1485
|
-
if (!pose) return;
|
|
1486
|
-
const parent = pose.data.parent ? poses.get(pose.data.parent) ?? null : null;
|
|
1487
|
-
updateWorldPose(pose, parent);
|
|
1488
|
-
for (const childName of children.get(name) ?? []) updateBoneTree(childName, poses, children);
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
function updateDescendantBoneTrees(
|
|
1492
|
-
name: string,
|
|
1493
|
-
poses: Map<string, BonePose>,
|
|
1494
|
-
children: Map<string, string[]>,
|
|
1495
|
-
blockedRoots: Set<string> = new Set(),
|
|
1496
|
-
): void {
|
|
1497
|
-
for (const childName of children.get(name) ?? []) {
|
|
1498
|
-
if (blockedRoots.has(childName)) continue;
|
|
1499
|
-
updateBoneTree(childName, poses, children);
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
function worldToLocal(bone: BonePose, worldX: number, worldY: number): [number, number] {
|
|
1504
|
-
const dx = worldX - bone.worldX;
|
|
1505
|
-
const dy = worldY - bone.worldY;
|
|
1506
|
-
const det = bone.a * bone.d - bone.b * bone.c;
|
|
1507
|
-
if (Math.abs(det) < 0.000001) return [dx, dy];
|
|
1508
|
-
return [
|
|
1509
|
-
(dx * bone.d - dy * bone.b) / det,
|
|
1510
|
-
(dy * bone.a - dx * bone.c) / det,
|
|
1511
|
-
];
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
function normalizeAngle(value: number): number {
|
|
1515
|
-
return ((value % 360) + 540) % 360 - 180;
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
function normalizeRadians(value: number): number {
|
|
1519
|
-
let angle = value % TWO_PI;
|
|
1520
|
-
if (angle > Math.PI) angle -= TWO_PI;
|
|
1521
|
-
else if (angle < -Math.PI) angle += TWO_PI;
|
|
1522
|
-
return angle;
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
function getCurrentAnimationTime(data: SpineData, component: Spine2DComponent): number {
|
|
1526
|
-
const animation = data.animations[component.animation];
|
|
1527
|
-
const duration = getCompiledAnimationDuration(animation);
|
|
1528
|
-
return component.loop && duration > 0 ? (component.elapsed / 1000) % duration : Math.min(component.elapsed / 1000, duration);
|
|
1529
|
-
}
|