@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,1708 @@
|
|
|
1
|
+
import { A as Animation3DError, b as animation3DMixerRuntime, d as Animation3DPoseBuffer, e as Animation3DMixer, G as GltfAnimation3DRuntime } from './chunks/GltfAnimation3DAdapter-Cu3rQQsW.js';
|
|
2
|
+
import { A as Animation3DStateMachineController$1, r as runAnimationStateMachineControllerUpdateTransaction, b as compileAnimation3DStateMachineDefinition$1, d as validateAnimation3DStateMachineDefinition$1, c as compileAnimationStateMachineDefinition } from './chunks/AnimationStateMachineController-BIcAh-sT.js';
|
|
3
|
+
import { Entity, Mesh3D, Component, Camera3D } from '@haiyue/engine';
|
|
4
|
+
import { MeshHelper, Transform3D, ParticleEmitter3D } from '@haiyue/engine/components';
|
|
5
|
+
import { createCone3D, createCylinder3D, createPlane3D, createSphere3D, createBox3D } from '@haiyue/engine/geometry';
|
|
6
|
+
import { AmbientLight, DirectionalLight } from '@haiyue/engine/lighting';
|
|
7
|
+
import { PbrMaterial } from '@haiyue/engine/material';
|
|
8
|
+
import { d as disposeGltfModel, a as loadGltfModel } from './chunks/gltfLoader-Dm22jLYB.js';
|
|
9
|
+
import { c as composeTrsMatrix } from './chunks/GltfAnimationRuntime-CRHsLycN.js';
|
|
10
|
+
import '@haiyue/engine/experimental/async';
|
|
11
|
+
import 'wgpu-matrix';
|
|
12
|
+
import '@haiyue/engine/core';
|
|
13
|
+
|
|
14
|
+
const POSE_APPLIER_STATES = new WeakMap();
|
|
15
|
+
/**
|
|
16
|
+
* Applies pose channels through a revision-aware binding resolver cache.
|
|
17
|
+
* Resolver misses fail explicitly and never write to a fallback target.
|
|
18
|
+
*/
|
|
19
|
+
class Animation3DPoseApplier {
|
|
20
|
+
constructor(resolver) {
|
|
21
|
+
POSE_APPLIER_STATES.set(this, {
|
|
22
|
+
resolver,
|
|
23
|
+
resolverRevision: Number.NaN,
|
|
24
|
+
resolvedBindings: new Map(),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
apply(pose) {
|
|
28
|
+
const state = POSE_APPLIER_STATES.get(this);
|
|
29
|
+
const { resolver } = state;
|
|
30
|
+
if (state.resolverRevision !== resolver.revision) {
|
|
31
|
+
state.resolverRevision = resolver.revision;
|
|
32
|
+
state.resolvedBindings.clear();
|
|
33
|
+
}
|
|
34
|
+
for (let index = 0; index < pose.channels.length; index++) {
|
|
35
|
+
const channel = pose.channels[index];
|
|
36
|
+
const bindingId = channel.binding.id;
|
|
37
|
+
let resolved = state.resolvedBindings.get(bindingId);
|
|
38
|
+
if (resolved === undefined && !state.resolvedBindings.has(bindingId)) {
|
|
39
|
+
resolved = resolver.resolve(channel.binding);
|
|
40
|
+
state.resolvedBindings.set(bindingId, resolved);
|
|
41
|
+
}
|
|
42
|
+
if (!resolved) {
|
|
43
|
+
throw new Animation3DError('resolver-miss', `Animation3D binding resolver could not resolve "${bindingId}".`, { resolver: 'binding', bindingId });
|
|
44
|
+
}
|
|
45
|
+
resolved.write(channel.value);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let nextAdapterId = 1;
|
|
51
|
+
class Animation3DStateMachineActionRuntimeHandle {
|
|
52
|
+
duration;
|
|
53
|
+
clipId;
|
|
54
|
+
layerId;
|
|
55
|
+
stateId;
|
|
56
|
+
action;
|
|
57
|
+
targetTime = 0;
|
|
58
|
+
appliedTime = 0;
|
|
59
|
+
playing = false;
|
|
60
|
+
destroyed = false;
|
|
61
|
+
pendingDestroy = false;
|
|
62
|
+
_actionCheckpoint;
|
|
63
|
+
_checkpointTargetTime = 0;
|
|
64
|
+
_checkpointAppliedTime = 0;
|
|
65
|
+
_checkpointPlaying = false;
|
|
66
|
+
constructor(clipId, options, action) {
|
|
67
|
+
this.duration = action.clip.duration;
|
|
68
|
+
this.clipId = clipId;
|
|
69
|
+
this.layerId = options.layerId;
|
|
70
|
+
this.stateId = options.stateId;
|
|
71
|
+
this.action = action;
|
|
72
|
+
this._actionCheckpoint = action.createSynchronizedState();
|
|
73
|
+
}
|
|
74
|
+
captureTransactionState() {
|
|
75
|
+
this._checkpointTargetTime = this.targetTime;
|
|
76
|
+
this._checkpointAppliedTime = this.appliedTime;
|
|
77
|
+
this._checkpointPlaying = this.playing;
|
|
78
|
+
this.action.captureSynchronizedState(this._actionCheckpoint);
|
|
79
|
+
}
|
|
80
|
+
restoreTransactionState() {
|
|
81
|
+
this.targetTime = this._checkpointTargetTime;
|
|
82
|
+
this.appliedTime = this._checkpointAppliedTime;
|
|
83
|
+
this.playing = this._checkpointPlaying;
|
|
84
|
+
this.pendingDestroy = false;
|
|
85
|
+
this.action.restoreSynchronizedState(this._actionCheckpoint);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Adapts the dimension-neutral state-machine port to the real 3D mixer.
|
|
90
|
+
*
|
|
91
|
+
* The controller owns every action playhead. The adapter applies its absolute
|
|
92
|
+
* times to actions while a synchronized mixer frame is open; it never calls
|
|
93
|
+
* mixer.update(), so the mixer clock cannot advance the same actions again.
|
|
94
|
+
*/
|
|
95
|
+
class Animation3DStateMachineMixerAdapter {
|
|
96
|
+
mixer;
|
|
97
|
+
clipResolver;
|
|
98
|
+
_handles = new Set();
|
|
99
|
+
_transactionHandles = [];
|
|
100
|
+
_transactionCreated = [];
|
|
101
|
+
_transactionPendingDestroy = [];
|
|
102
|
+
_actionIdPrefix;
|
|
103
|
+
_nextActionId = 1;
|
|
104
|
+
_transactionNextActionId = 1;
|
|
105
|
+
_frameOut = null;
|
|
106
|
+
_controllerTransactionActive = false;
|
|
107
|
+
_destroyed = false;
|
|
108
|
+
constructor(mixer, clipResolver) {
|
|
109
|
+
this.mixer = mixer;
|
|
110
|
+
this.clipResolver = clipResolver;
|
|
111
|
+
this._actionIdPrefix = `animation3d-state-machine:${nextAdapterId++}`;
|
|
112
|
+
}
|
|
113
|
+
get liveActionCount() {
|
|
114
|
+
return this._handles.size;
|
|
115
|
+
}
|
|
116
|
+
createAction(clipId, options) {
|
|
117
|
+
this._requireActive();
|
|
118
|
+
const clip = this.clipResolver.resolve(clipId);
|
|
119
|
+
if (!clip) {
|
|
120
|
+
throw new Animation3DError('resolver-miss', `Animation3D clip resolver could not resolve "${clipId}".`, { resolver: 'clip', clipId });
|
|
121
|
+
}
|
|
122
|
+
const action = this.mixer.createAction(clip, {
|
|
123
|
+
id: `${this._actionIdPrefix}:${this._nextActionId++}`,
|
|
124
|
+
loop: options.loop,
|
|
125
|
+
repetitions: options.loop === 'once' ? 1 : Infinity,
|
|
126
|
+
clampWhenFinished: true,
|
|
127
|
+
weight: 0,
|
|
128
|
+
blendMode: options.blendMode,
|
|
129
|
+
...(options.mask ? { mask: options.mask } : {}),
|
|
130
|
+
});
|
|
131
|
+
const handle = new Animation3DStateMachineActionRuntimeHandle(clipId, options, action);
|
|
132
|
+
this._handles.add(handle);
|
|
133
|
+
if (this._controllerTransactionActive)
|
|
134
|
+
this._transactionCreated.push(handle);
|
|
135
|
+
return handle;
|
|
136
|
+
}
|
|
137
|
+
play(action) {
|
|
138
|
+
this._requireHandle(action);
|
|
139
|
+
action.action.play();
|
|
140
|
+
action.playing = true;
|
|
141
|
+
}
|
|
142
|
+
stop(action) {
|
|
143
|
+
this._requireHandle(action);
|
|
144
|
+
action.action.stop();
|
|
145
|
+
action.playing = false;
|
|
146
|
+
}
|
|
147
|
+
fade(action, _targetWeight, _durationSeconds) {
|
|
148
|
+
this._requireHandle(action);
|
|
149
|
+
// The controller computes exact source/destination weights for every
|
|
150
|
+
// transition segment. Scheduling an action-local fade here would multiply
|
|
151
|
+
// that weight and introduce a second transition clock.
|
|
152
|
+
}
|
|
153
|
+
setWeight(action, weight) {
|
|
154
|
+
this._requireHandle(action);
|
|
155
|
+
action.action.weight = weight;
|
|
156
|
+
}
|
|
157
|
+
setTime(action, timeSeconds) {
|
|
158
|
+
this._requireHandle(action);
|
|
159
|
+
action.targetTime = timeSeconds;
|
|
160
|
+
if (!action.playing) {
|
|
161
|
+
action.action.seekSynchronizedTime(timeSeconds);
|
|
162
|
+
action.appliedTime = timeSeconds;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (this._frameOut)
|
|
166
|
+
this._applyTime(action, this._frameOut);
|
|
167
|
+
}
|
|
168
|
+
setTimeScale(action, timeScale) {
|
|
169
|
+
this._requireHandle(action);
|
|
170
|
+
action.action.timeScale = timeScale;
|
|
171
|
+
}
|
|
172
|
+
destroyAction(action) {
|
|
173
|
+
if (action.destroyed)
|
|
174
|
+
return;
|
|
175
|
+
if (!this._handles.has(action)) {
|
|
176
|
+
throw new Error('Animation3D state-machine action belongs to another adapter.');
|
|
177
|
+
}
|
|
178
|
+
if (this._controllerTransactionActive) {
|
|
179
|
+
if (action.pendingDestroy)
|
|
180
|
+
return;
|
|
181
|
+
action.pendingDestroy = true;
|
|
182
|
+
this._transactionPendingDestroy.push(action);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
this._destroyHandleNow(action);
|
|
186
|
+
}
|
|
187
|
+
/** @internal Called by the shared controller transaction boundary. */
|
|
188
|
+
beginControllerTransaction() {
|
|
189
|
+
this._requireActive();
|
|
190
|
+
if (this._controllerTransactionActive) {
|
|
191
|
+
throw new Error('Animation3D state-machine adapter transaction is already active.');
|
|
192
|
+
}
|
|
193
|
+
this._controllerTransactionActive = true;
|
|
194
|
+
this._transactionNextActionId = this._nextActionId;
|
|
195
|
+
this._transactionHandles.length = 0;
|
|
196
|
+
this._transactionCreated.length = 0;
|
|
197
|
+
this._transactionPendingDestroy.length = 0;
|
|
198
|
+
for (const handle of this._handles) {
|
|
199
|
+
handle.captureTransactionState();
|
|
200
|
+
this._transactionHandles.push(handle);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/** @internal Makes deferred action destruction observable atomically. */
|
|
204
|
+
commitControllerTransaction() {
|
|
205
|
+
if (!this._controllerTransactionActive) {
|
|
206
|
+
throw new Error('Animation3D state-machine adapter has no active transaction.');
|
|
207
|
+
}
|
|
208
|
+
this._controllerTransactionActive = false;
|
|
209
|
+
for (let index = 0; index < this._transactionPendingDestroy.length; index++) {
|
|
210
|
+
this._destroyHandleNow(this._transactionPendingDestroy[index]);
|
|
211
|
+
}
|
|
212
|
+
this._clearControllerTransaction();
|
|
213
|
+
}
|
|
214
|
+
/** @internal Removes new actions and restores exact event/playhead state. */
|
|
215
|
+
rollbackControllerTransaction() {
|
|
216
|
+
if (!this._controllerTransactionActive)
|
|
217
|
+
return;
|
|
218
|
+
this._controllerTransactionActive = false;
|
|
219
|
+
for (let index = this._transactionCreated.length - 1; index >= 0; index--) {
|
|
220
|
+
this._destroyHandleNow(this._transactionCreated[index]);
|
|
221
|
+
}
|
|
222
|
+
for (let index = 0; index < this._transactionHandles.length; index++) {
|
|
223
|
+
const handle = this._transactionHandles[index];
|
|
224
|
+
if (!handle.destroyed)
|
|
225
|
+
handle.restoreTransactionState();
|
|
226
|
+
}
|
|
227
|
+
this._nextActionId = this._transactionNextActionId;
|
|
228
|
+
this._clearControllerTransaction();
|
|
229
|
+
}
|
|
230
|
+
beginFrame(out) {
|
|
231
|
+
this._requireActive();
|
|
232
|
+
if (this._frameOut) {
|
|
233
|
+
throw new Error('Animation3D state-machine adapter already has an active frame.');
|
|
234
|
+
}
|
|
235
|
+
this._frameOut = out;
|
|
236
|
+
for (const handle of this._handles) {
|
|
237
|
+
if (handle.playing)
|
|
238
|
+
this._applyTime(handle, out);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
endFrame(out) {
|
|
242
|
+
if (this._frameOut !== out) {
|
|
243
|
+
throw new Error('Animation3D state-machine adapter output does not match its active frame.');
|
|
244
|
+
}
|
|
245
|
+
this._frameOut = null;
|
|
246
|
+
}
|
|
247
|
+
cancelFrame(out) {
|
|
248
|
+
if (this._frameOut === out)
|
|
249
|
+
this._frameOut = null;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Releases adapter-created actions only. The mixer, clips, and clip resolver
|
|
253
|
+
* are externally owned and are never destroyed by this adapter.
|
|
254
|
+
*/
|
|
255
|
+
destroy() {
|
|
256
|
+
if (this._destroyed)
|
|
257
|
+
return;
|
|
258
|
+
this.rollbackControllerTransaction();
|
|
259
|
+
this._frameOut = null;
|
|
260
|
+
for (const handle of this._handles)
|
|
261
|
+
this._destroyHandleNow(handle);
|
|
262
|
+
this._destroyed = true;
|
|
263
|
+
}
|
|
264
|
+
_applyTime(handle, out) {
|
|
265
|
+
handle.action.synchronizeTime(handle.targetTime, out);
|
|
266
|
+
handle.appliedTime = handle.targetTime;
|
|
267
|
+
}
|
|
268
|
+
_destroyHandleNow(action) {
|
|
269
|
+
if (action.destroyed)
|
|
270
|
+
return;
|
|
271
|
+
if (action.playing)
|
|
272
|
+
action.action.stop();
|
|
273
|
+
this.mixer.removeAction(action.action);
|
|
274
|
+
action.playing = false;
|
|
275
|
+
action.pendingDestroy = false;
|
|
276
|
+
action.destroyed = true;
|
|
277
|
+
this._handles.delete(action);
|
|
278
|
+
}
|
|
279
|
+
_clearControllerTransaction() {
|
|
280
|
+
for (let index = 0; index < this._transactionPendingDestroy.length; index++) {
|
|
281
|
+
this._transactionPendingDestroy[index].pendingDestroy = false;
|
|
282
|
+
}
|
|
283
|
+
this._transactionHandles.length = 0;
|
|
284
|
+
this._transactionCreated.length = 0;
|
|
285
|
+
this._transactionPendingDestroy.length = 0;
|
|
286
|
+
}
|
|
287
|
+
_requireActive() {
|
|
288
|
+
if (this._destroyed) {
|
|
289
|
+
throw new Error('Animation3D state-machine mixer adapter has been destroyed.');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
_requireHandle(handle) {
|
|
293
|
+
this._requireActive();
|
|
294
|
+
if (handle.destroyed || !this._handles.has(handle)) {
|
|
295
|
+
throw new Error('Animation3D state-machine action is no longer valid.');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Closed-loop runtime for a compiled state machine and the real 3D mixer.
|
|
302
|
+
*
|
|
303
|
+
* The state-machine controller is the unique playback-clock owner. A frame
|
|
304
|
+
* advances the controller once, synchronizes absolute action playheads, then
|
|
305
|
+
* asks the mixer to evaluate without advancing any action time.
|
|
306
|
+
*/
|
|
307
|
+
class Animation3DStateMachineMixerIntegration {
|
|
308
|
+
mixer;
|
|
309
|
+
adapter;
|
|
310
|
+
controller;
|
|
311
|
+
_state = 'active';
|
|
312
|
+
_time = 0;
|
|
313
|
+
_transactionOut = null;
|
|
314
|
+
_beginControllerUpdate = () => {
|
|
315
|
+
this.adapter.beginFrame(this._requireTransactionOut());
|
|
316
|
+
};
|
|
317
|
+
_completeControllerUpdate = () => {
|
|
318
|
+
const out = this._requireTransactionOut();
|
|
319
|
+
this.adapter.endFrame(out);
|
|
320
|
+
return this.mixer.endSynchronizedFrame(out);
|
|
321
|
+
};
|
|
322
|
+
constructor(compiled, mixer, clipResolver, controllerOptions = {}) {
|
|
323
|
+
this.mixer = mixer;
|
|
324
|
+
this.adapter = new Animation3DStateMachineMixerAdapter(mixer, clipResolver);
|
|
325
|
+
try {
|
|
326
|
+
this.controller = new Animation3DStateMachineController$1(compiled, this.adapter, controllerOptions);
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
this.adapter.destroy();
|
|
330
|
+
this._state = 'destroyed';
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
get state() {
|
|
335
|
+
return this._state;
|
|
336
|
+
}
|
|
337
|
+
get time() {
|
|
338
|
+
return this._time;
|
|
339
|
+
}
|
|
340
|
+
update(deltaSeconds, out) {
|
|
341
|
+
this._requireActive();
|
|
342
|
+
if (!Number.isFinite(deltaSeconds) || deltaSeconds < 0) {
|
|
343
|
+
throw new RangeError('Animation3D state-machine deltaSeconds must be finite and non-negative.');
|
|
344
|
+
}
|
|
345
|
+
const nextTime = this._time + deltaSeconds;
|
|
346
|
+
this.mixer.beginSynchronizedFrame(nextTime, out);
|
|
347
|
+
this._transactionOut = out;
|
|
348
|
+
try {
|
|
349
|
+
const pose = runAnimationStateMachineControllerUpdateTransaction(this.controller, deltaSeconds, this._beginControllerUpdate, this._completeControllerUpdate);
|
|
350
|
+
this._time = nextTime;
|
|
351
|
+
return pose;
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
this.adapter.cancelFrame(out);
|
|
355
|
+
this.mixer.cancelSynchronizedFrame(out);
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
358
|
+
finally {
|
|
359
|
+
this._transactionOut = null;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Re-evaluates the current controller-owned time without advancing it.
|
|
364
|
+
*/
|
|
365
|
+
evaluate(out) {
|
|
366
|
+
this._requireActive();
|
|
367
|
+
this.mixer.beginSynchronizedFrame(this._time, out);
|
|
368
|
+
this.adapter.beginControllerTransaction();
|
|
369
|
+
try {
|
|
370
|
+
this.adapter.beginFrame(out);
|
|
371
|
+
this.adapter.endFrame(out);
|
|
372
|
+
const pose = this.mixer.endSynchronizedFrame(out);
|
|
373
|
+
this.adapter.commitControllerTransaction();
|
|
374
|
+
return pose;
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
this.adapter.cancelFrame(out);
|
|
378
|
+
this.mixer.cancelSynchronizedFrame(out);
|
|
379
|
+
this.adapter.rollbackControllerTransaction();
|
|
380
|
+
throw error;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
reset(out) {
|
|
384
|
+
this._requireActive();
|
|
385
|
+
this.controller.reset();
|
|
386
|
+
this._time = 0;
|
|
387
|
+
return this.evaluate(out);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Stops and removes every controller action. External mixer/clip resources
|
|
391
|
+
* remain caller-owned. Repeated calls are safe.
|
|
392
|
+
*/
|
|
393
|
+
destroy() {
|
|
394
|
+
if (this._state === 'destroyed')
|
|
395
|
+
return;
|
|
396
|
+
this.controller.destroy();
|
|
397
|
+
this.adapter.destroy();
|
|
398
|
+
this._state = 'destroyed';
|
|
399
|
+
}
|
|
400
|
+
_requireActive() {
|
|
401
|
+
if (this._state !== 'active') {
|
|
402
|
+
throw new Error('Animation3D state-machine mixer integration has been destroyed.');
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
_requireTransactionOut() {
|
|
406
|
+
if (!this._transactionOut) {
|
|
407
|
+
throw new Error('Animation3D state-machine integration has no active transaction output.');
|
|
408
|
+
}
|
|
409
|
+
return this._transactionOut;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
class Animation3DStateMachineValidationError extends Error {
|
|
414
|
+
issues;
|
|
415
|
+
constructor(issues) {
|
|
416
|
+
super(`Invalid Animation3D state-machine definition (${issues.length} ${issues.length === 1 ? 'issue' : 'issues'}).`);
|
|
417
|
+
this.name = 'Animation3DStateMachineValidationError';
|
|
418
|
+
this.issues = Object.freeze([...issues]);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function validateAnimation3DStateMachineDefinition(definition) {
|
|
422
|
+
return validateAnimation3DStateMachineDefinition$1(definition);
|
|
423
|
+
}
|
|
424
|
+
function compileAnimation3DStateMachineDefinition(definition) {
|
|
425
|
+
const issues = validateAnimation3DStateMachineDefinition(definition);
|
|
426
|
+
if (issues.length > 0) {
|
|
427
|
+
throw new Animation3DStateMachineValidationError(issues);
|
|
428
|
+
}
|
|
429
|
+
return compileAnimation3DStateMachineDefinition$1(definition);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Public closed-loop controller and minimal Mixer integration.
|
|
433
|
+
*
|
|
434
|
+
* The internal mixer port, action handles, controller transactions and
|
|
435
|
+
* compiled nodes never cross this boundary.
|
|
436
|
+
*/
|
|
437
|
+
class Animation3DStateMachineController {
|
|
438
|
+
mixer;
|
|
439
|
+
#integration;
|
|
440
|
+
constructor(compiled, mixer, clipResolver, options = {}) {
|
|
441
|
+
this.mixer = mixer;
|
|
442
|
+
this.#integration = new Animation3DStateMachineMixerIntegration(compiled, animation3DMixerRuntime(mixer), clipResolver, options);
|
|
443
|
+
}
|
|
444
|
+
get status() {
|
|
445
|
+
return this.#integration.state;
|
|
446
|
+
}
|
|
447
|
+
get time() {
|
|
448
|
+
return this.#integration.time;
|
|
449
|
+
}
|
|
450
|
+
get layerSnapshots() {
|
|
451
|
+
return this.#integration.controller.layerSnapshots;
|
|
452
|
+
}
|
|
453
|
+
getLayerSnapshot(layerId) {
|
|
454
|
+
return this.#integration.controller.getLayerSnapshot(layerId);
|
|
455
|
+
}
|
|
456
|
+
setFloat(name, value) {
|
|
457
|
+
this.#integration.controller.setFloat(name, value);
|
|
458
|
+
return this;
|
|
459
|
+
}
|
|
460
|
+
setInteger(name, value) {
|
|
461
|
+
this.#integration.controller.setInteger(name, value);
|
|
462
|
+
return this;
|
|
463
|
+
}
|
|
464
|
+
setBoolean(name, value) {
|
|
465
|
+
this.#integration.controller.setBoolean(name, value);
|
|
466
|
+
return this;
|
|
467
|
+
}
|
|
468
|
+
setTrigger(name) {
|
|
469
|
+
this.#integration.controller.setTrigger(name);
|
|
470
|
+
return this;
|
|
471
|
+
}
|
|
472
|
+
resetTrigger(name) {
|
|
473
|
+
this.#integration.controller.resetTrigger(name);
|
|
474
|
+
return this;
|
|
475
|
+
}
|
|
476
|
+
getParameter(name) {
|
|
477
|
+
return this.#integration.controller.getParameter(name);
|
|
478
|
+
}
|
|
479
|
+
update(deltaSeconds, out) {
|
|
480
|
+
return this.#integration.update(deltaSeconds, out);
|
|
481
|
+
}
|
|
482
|
+
evaluate(out) {
|
|
483
|
+
return this.#integration.evaluate(out);
|
|
484
|
+
}
|
|
485
|
+
reset(out) {
|
|
486
|
+
return this.#integration.reset(out);
|
|
487
|
+
}
|
|
488
|
+
destroy() {
|
|
489
|
+
this.#integration.destroy();
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const TIME_EPSILON = 1e-9;
|
|
494
|
+
class HyaAnimation3DStateMachineActionHandle {
|
|
495
|
+
id;
|
|
496
|
+
clipId;
|
|
497
|
+
duration;
|
|
498
|
+
loop;
|
|
499
|
+
children;
|
|
500
|
+
activeCueKeys = new Set();
|
|
501
|
+
targetTime = 0;
|
|
502
|
+
traversal = 0;
|
|
503
|
+
playing = false;
|
|
504
|
+
destroyed = false;
|
|
505
|
+
pendingDestroy = false;
|
|
506
|
+
constructor(id, clipId, duration, loop, children) {
|
|
507
|
+
this.id = id;
|
|
508
|
+
this.clipId = clipId;
|
|
509
|
+
this.duration = duration;
|
|
510
|
+
this.loop = loop;
|
|
511
|
+
this.children = Object.freeze([...children]);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* One controller-facing port spanning the scene mixer and every loaded model
|
|
516
|
+
* mixer. It is the only state-machine clock; child adapters only synchronize
|
|
517
|
+
* existing mixer actions and participate in the same rollback boundary.
|
|
518
|
+
*/
|
|
519
|
+
class HyaAnimation3DStateMachineMixerPort {
|
|
520
|
+
partitions;
|
|
521
|
+
_particleCues;
|
|
522
|
+
_clips = new Map();
|
|
523
|
+
_particles = new Map();
|
|
524
|
+
_owners = new Map();
|
|
525
|
+
_handles = new Set();
|
|
526
|
+
_transactionCreated = [];
|
|
527
|
+
_transactionPendingDestroy = [];
|
|
528
|
+
_transactionCheckpoints = new Map();
|
|
529
|
+
_transactionCommands = [];
|
|
530
|
+
_transactionOwners = new Map();
|
|
531
|
+
_nextHandleId = 1;
|
|
532
|
+
_transactionNextHandleId = 1;
|
|
533
|
+
_transactionActive = false;
|
|
534
|
+
_destroyed = false;
|
|
535
|
+
constructor(partitions, clips, _particleCues) {
|
|
536
|
+
this.partitions = partitions;
|
|
537
|
+
this._particleCues = _particleCues;
|
|
538
|
+
for (const clip of clips)
|
|
539
|
+
this._clips.set(clip.id, clip);
|
|
540
|
+
for (const cues of _particleCues.values()) {
|
|
541
|
+
for (const cue of cues)
|
|
542
|
+
this._particles.set(cue.key, cue.emitter);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
get liveActionCount() { return this._handles.size; }
|
|
546
|
+
get sideEffectOwnerCount() {
|
|
547
|
+
let count = 0;
|
|
548
|
+
for (const owners of this._owners.values())
|
|
549
|
+
count += owners.length;
|
|
550
|
+
return count;
|
|
551
|
+
}
|
|
552
|
+
createAction(clipId, options) {
|
|
553
|
+
this._requireActive();
|
|
554
|
+
const clip = this._clips.get(clipId);
|
|
555
|
+
if (!clip)
|
|
556
|
+
throw new RangeError(`Unknown HYA Animation3D state-machine clip "${clipId}".`);
|
|
557
|
+
const children = [];
|
|
558
|
+
try {
|
|
559
|
+
for (const partition of this.partitions) {
|
|
560
|
+
if (partition.clips.has(clipId)) {
|
|
561
|
+
children.push(partition.adapter.createAction(clipId, translateActionOptions(options, partition.bindingIds)));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
let childIndex = 0;
|
|
567
|
+
for (const partition of this.partitions) {
|
|
568
|
+
if (!partition.clips.has(clipId) || childIndex >= children.length)
|
|
569
|
+
continue;
|
|
570
|
+
partition.adapter.destroyAction(children[childIndex++]);
|
|
571
|
+
}
|
|
572
|
+
throw error;
|
|
573
|
+
}
|
|
574
|
+
const handle = new HyaAnimation3DStateMachineActionHandle(`hya-animation3d-state-machine:${this._nextHandleId++}`, clipId, clip.duration, options.loop, children);
|
|
575
|
+
this._handles.add(handle);
|
|
576
|
+
if (this._transactionActive)
|
|
577
|
+
this._transactionCreated.push(handle);
|
|
578
|
+
return handle;
|
|
579
|
+
}
|
|
580
|
+
play(handle) {
|
|
581
|
+
this._requireHandle(handle);
|
|
582
|
+
forEachChild(this.partitions, handle, (adapter, child) => adapter.play(child));
|
|
583
|
+
if (handle.playing)
|
|
584
|
+
return;
|
|
585
|
+
handle.playing = true;
|
|
586
|
+
this._syncParticleCues(handle, true);
|
|
587
|
+
}
|
|
588
|
+
stop(handle) {
|
|
589
|
+
this._requireHandle(handle);
|
|
590
|
+
forEachChild(this.partitions, handle, (adapter, child) => adapter.stop(child));
|
|
591
|
+
if (!handle.playing)
|
|
592
|
+
return;
|
|
593
|
+
handle.playing = false;
|
|
594
|
+
this._releaseAllParticleCues(handle);
|
|
595
|
+
}
|
|
596
|
+
fade(handle, targetWeight, durationSeconds) {
|
|
597
|
+
this._requireHandle(handle);
|
|
598
|
+
forEachChild(this.partitions, handle, (adapter, child) => adapter.fade(child, targetWeight, durationSeconds));
|
|
599
|
+
}
|
|
600
|
+
setWeight(handle, weight) {
|
|
601
|
+
this._requireHandle(handle);
|
|
602
|
+
forEachChild(this.partitions, handle, (adapter, child) => adapter.setWeight(child, weight));
|
|
603
|
+
}
|
|
604
|
+
setTime(handle, timeSeconds) {
|
|
605
|
+
this._requireHandle(handle);
|
|
606
|
+
forEachChild(this.partitions, handle, (adapter, child) => adapter.setTime(child, timeSeconds));
|
|
607
|
+
const previousTraversal = handle.traversal;
|
|
608
|
+
const position = playbackPosition(timeSeconds, handle.duration, handle.loop);
|
|
609
|
+
handle.targetTime = timeSeconds;
|
|
610
|
+
handle.traversal = position.traversal;
|
|
611
|
+
if (!handle.playing)
|
|
612
|
+
return;
|
|
613
|
+
this._syncParticleCues(handle, false, position.localTime);
|
|
614
|
+
if (position.traversal !== previousTraversal)
|
|
615
|
+
this._restartDominantParticleCues(handle);
|
|
616
|
+
}
|
|
617
|
+
setTimeScale(handle, timeScale) {
|
|
618
|
+
this._requireHandle(handle);
|
|
619
|
+
forEachChild(this.partitions, handle, (adapter, child) => adapter.setTimeScale(child, timeScale));
|
|
620
|
+
}
|
|
621
|
+
destroyAction(handle) {
|
|
622
|
+
if (handle.destroyed)
|
|
623
|
+
return;
|
|
624
|
+
this._requireHandle(handle);
|
|
625
|
+
if (handle.playing) {
|
|
626
|
+
handle.playing = false;
|
|
627
|
+
this._releaseAllParticleCues(handle);
|
|
628
|
+
}
|
|
629
|
+
forEachChild(this.partitions, handle, (adapter, child) => adapter.destroyAction(child));
|
|
630
|
+
if (this._transactionActive) {
|
|
631
|
+
if (!handle.pendingDestroy) {
|
|
632
|
+
handle.pendingDestroy = true;
|
|
633
|
+
this._transactionPendingDestroy.push(handle);
|
|
634
|
+
}
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
this._destroyGroupNow(handle);
|
|
638
|
+
}
|
|
639
|
+
beginControllerTransaction() {
|
|
640
|
+
this._requireActive();
|
|
641
|
+
if (this._transactionActive)
|
|
642
|
+
throw new Error('HYA Animation3D state-machine transaction is already active.');
|
|
643
|
+
this._transactionActive = true;
|
|
644
|
+
this._transactionCreated.length = 0;
|
|
645
|
+
this._transactionPendingDestroy.length = 0;
|
|
646
|
+
this._transactionCheckpoints.clear();
|
|
647
|
+
this._transactionCommands.length = 0;
|
|
648
|
+
this._transactionOwners = cloneOwners(this._owners);
|
|
649
|
+
this._transactionNextHandleId = this._nextHandleId;
|
|
650
|
+
for (const handle of this._handles) {
|
|
651
|
+
this._transactionCheckpoints.set(handle, {
|
|
652
|
+
targetTime: handle.targetTime,
|
|
653
|
+
traversal: handle.traversal,
|
|
654
|
+
playing: handle.playing,
|
|
655
|
+
activeCueKeys: new Set(handle.activeCueKeys),
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
const begun = [];
|
|
659
|
+
try {
|
|
660
|
+
for (const partition of this.partitions) {
|
|
661
|
+
partition.adapter.beginControllerTransaction();
|
|
662
|
+
begun.push(partition.adapter);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
catch (error) {
|
|
666
|
+
for (let index = begun.length - 1; index >= 0; index--)
|
|
667
|
+
begun[index].rollbackControllerTransaction();
|
|
668
|
+
this._transactionActive = false;
|
|
669
|
+
this._clearTransaction();
|
|
670
|
+
throw error;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
commitControllerTransaction() {
|
|
674
|
+
if (!this._transactionActive)
|
|
675
|
+
throw new Error('HYA Animation3D state-machine has no active transaction.');
|
|
676
|
+
for (const partition of this.partitions)
|
|
677
|
+
partition.adapter.commitControllerTransaction();
|
|
678
|
+
this._transactionActive = false;
|
|
679
|
+
for (const command of this._transactionCommands)
|
|
680
|
+
this._applyParticleCommand(command);
|
|
681
|
+
for (const handle of this._transactionPendingDestroy)
|
|
682
|
+
this._destroyGroupNow(handle);
|
|
683
|
+
this._clearTransaction();
|
|
684
|
+
}
|
|
685
|
+
rollbackControllerTransaction() {
|
|
686
|
+
if (!this._transactionActive)
|
|
687
|
+
return;
|
|
688
|
+
this._transactionActive = false;
|
|
689
|
+
for (let index = this.partitions.length - 1; index >= 0; index--) {
|
|
690
|
+
this.partitions[index].adapter.rollbackControllerTransaction();
|
|
691
|
+
}
|
|
692
|
+
for (const handle of this._transactionCreated) {
|
|
693
|
+
handle.destroyed = true;
|
|
694
|
+
this._handles.delete(handle);
|
|
695
|
+
}
|
|
696
|
+
for (const [handle, checkpoint] of this._transactionCheckpoints) {
|
|
697
|
+
if (handle.destroyed)
|
|
698
|
+
continue;
|
|
699
|
+
handle.targetTime = checkpoint.targetTime;
|
|
700
|
+
handle.traversal = checkpoint.traversal;
|
|
701
|
+
handle.playing = checkpoint.playing;
|
|
702
|
+
handle.pendingDestroy = false;
|
|
703
|
+
handle.activeCueKeys.clear();
|
|
704
|
+
for (const key of checkpoint.activeCueKeys)
|
|
705
|
+
handle.activeCueKeys.add(key);
|
|
706
|
+
}
|
|
707
|
+
this._owners.clear();
|
|
708
|
+
for (const [key, owners] of this._transactionOwners)
|
|
709
|
+
this._owners.set(key, [...owners]);
|
|
710
|
+
this._nextHandleId = this._transactionNextHandleId;
|
|
711
|
+
this._clearTransaction();
|
|
712
|
+
}
|
|
713
|
+
destroy() {
|
|
714
|
+
if (this._destroyed)
|
|
715
|
+
return;
|
|
716
|
+
this.rollbackControllerTransaction();
|
|
717
|
+
for (const handle of [...this._handles]) {
|
|
718
|
+
if (handle.playing)
|
|
719
|
+
this.stop(handle);
|
|
720
|
+
forEachChild(this.partitions, handle, (adapter, child) => adapter.destroyAction(child));
|
|
721
|
+
this._destroyGroupNow(handle);
|
|
722
|
+
}
|
|
723
|
+
for (const partition of this.partitions)
|
|
724
|
+
partition.adapter.destroy();
|
|
725
|
+
for (const emitter of this._particles.values())
|
|
726
|
+
stopParticle(emitter);
|
|
727
|
+
this._owners.clear();
|
|
728
|
+
this._destroyed = true;
|
|
729
|
+
}
|
|
730
|
+
_syncParticleCues(handle, entering, localTime = playbackPosition(handle.targetTime, handle.duration, handle.loop).localTime) {
|
|
731
|
+
for (const cue of this._particleCues.get(handle.clipId) ?? []) {
|
|
732
|
+
const active = particleCueActive(cue, localTime, handle.duration);
|
|
733
|
+
const owned = handle.activeCueKeys.has(cue.key);
|
|
734
|
+
if (active && !owned)
|
|
735
|
+
this._acquireParticleCue(handle, cue);
|
|
736
|
+
else if (!active && owned)
|
|
737
|
+
this._releaseParticleCue(handle, cue.key);
|
|
738
|
+
else if (entering && active && owned)
|
|
739
|
+
this._recordParticleCommand({ key: cue.key, kind: 'start' });
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
_acquireParticleCue(handle, cue) {
|
|
743
|
+
handle.activeCueKeys.add(cue.key);
|
|
744
|
+
const owners = this._owners.get(cue.key) ?? [];
|
|
745
|
+
if (!this._owners.has(cue.key))
|
|
746
|
+
this._owners.set(cue.key, owners);
|
|
747
|
+
const existing = owners.indexOf(handle.id);
|
|
748
|
+
if (existing >= 0)
|
|
749
|
+
owners.splice(existing, 1);
|
|
750
|
+
owners.push(handle.id);
|
|
751
|
+
this._recordParticleCommand({ key: cue.key, kind: 'start' });
|
|
752
|
+
}
|
|
753
|
+
_releaseParticleCue(handle, key) {
|
|
754
|
+
handle.activeCueKeys.delete(key);
|
|
755
|
+
const owners = this._owners.get(key);
|
|
756
|
+
if (!owners)
|
|
757
|
+
return;
|
|
758
|
+
const index = owners.indexOf(handle.id);
|
|
759
|
+
const wasDominant = index === owners.length - 1;
|
|
760
|
+
if (index >= 0)
|
|
761
|
+
owners.splice(index, 1);
|
|
762
|
+
if (owners.length === 0) {
|
|
763
|
+
this._owners.delete(key);
|
|
764
|
+
this._recordParticleCommand({ key, kind: 'stop' });
|
|
765
|
+
}
|
|
766
|
+
else if (wasDominant)
|
|
767
|
+
this._recordParticleCommand({ key, kind: 'start' });
|
|
768
|
+
}
|
|
769
|
+
_releaseAllParticleCues(handle) {
|
|
770
|
+
for (const key of [...handle.activeCueKeys])
|
|
771
|
+
this._releaseParticleCue(handle, key);
|
|
772
|
+
}
|
|
773
|
+
_restartDominantParticleCues(handle) {
|
|
774
|
+
for (const key of handle.activeCueKeys) {
|
|
775
|
+
const owners = this._owners.get(key);
|
|
776
|
+
if (owners?.[owners.length - 1] === handle.id)
|
|
777
|
+
this._recordParticleCommand({ key, kind: 'start' });
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
_recordParticleCommand(command) {
|
|
781
|
+
if (this._transactionActive)
|
|
782
|
+
this._transactionCommands.push(command);
|
|
783
|
+
else
|
|
784
|
+
this._applyParticleCommand(command);
|
|
785
|
+
}
|
|
786
|
+
_applyParticleCommand(command) {
|
|
787
|
+
const emitter = this._particles.get(command.key);
|
|
788
|
+
if (!emitter)
|
|
789
|
+
return;
|
|
790
|
+
if (command.kind === 'stop')
|
|
791
|
+
stopParticle(emitter);
|
|
792
|
+
else {
|
|
793
|
+
emitter.emitting = true;
|
|
794
|
+
emitter.restart(true);
|
|
795
|
+
emitter.playing = true;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
_destroyGroupNow(handle) {
|
|
799
|
+
handle.playing = false;
|
|
800
|
+
handle.pendingDestroy = false;
|
|
801
|
+
handle.destroyed = true;
|
|
802
|
+
handle.activeCueKeys.clear();
|
|
803
|
+
this._handles.delete(handle);
|
|
804
|
+
}
|
|
805
|
+
_clearTransaction() {
|
|
806
|
+
this._transactionCreated.length = 0;
|
|
807
|
+
this._transactionPendingDestroy.length = 0;
|
|
808
|
+
this._transactionCheckpoints.clear();
|
|
809
|
+
this._transactionCommands.length = 0;
|
|
810
|
+
this._transactionOwners.clear();
|
|
811
|
+
}
|
|
812
|
+
_requireHandle(handle) {
|
|
813
|
+
this._requireActive();
|
|
814
|
+
if (handle.destroyed || !this._handles.has(handle)) {
|
|
815
|
+
throw new Error('HYA Animation3D state-machine action is no longer valid.');
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
_requireActive() {
|
|
819
|
+
if (this._destroyed)
|
|
820
|
+
throw new Error('HYA Animation3D state-machine mixer port has been destroyed.');
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
/** Coordinates native scene, glTF model and Particle3D channels with one controller transaction. */
|
|
824
|
+
class HyaAnimation3DStateMachineRuntime {
|
|
825
|
+
controller;
|
|
826
|
+
_partitions;
|
|
827
|
+
_port;
|
|
828
|
+
_time = 0;
|
|
829
|
+
_destroyed = false;
|
|
830
|
+
constructor(options) {
|
|
831
|
+
this._partitions = Object.freeze(options.partitions.map(partition => {
|
|
832
|
+
const resolver = { resolve: (clipId) => partition.clips.get(clipId) ?? null };
|
|
833
|
+
return {
|
|
834
|
+
id: partition.id,
|
|
835
|
+
mixer: partition.mixer,
|
|
836
|
+
adapter: new Animation3DStateMachineMixerAdapter(partition.mixer, resolver),
|
|
837
|
+
clips: partition.clips,
|
|
838
|
+
bindingIds: partition.bindingIds,
|
|
839
|
+
pose: partition.pose ?? new Animation3DPoseBuffer(),
|
|
840
|
+
apply: partition.apply,
|
|
841
|
+
previousTime: 0,
|
|
842
|
+
frameCompleted: false,
|
|
843
|
+
};
|
|
844
|
+
}));
|
|
845
|
+
this._port = new HyaAnimation3DStateMachineMixerPort(this._partitions, options.clips, options.particleCues ?? new Map());
|
|
846
|
+
try {
|
|
847
|
+
this.controller = new Animation3DStateMachineController$1(compileAnimationStateMachineDefinition(options.definition), this._port);
|
|
848
|
+
this.evaluate();
|
|
849
|
+
}
|
|
850
|
+
catch (error) {
|
|
851
|
+
this.controller?.destroy();
|
|
852
|
+
this._port.destroy();
|
|
853
|
+
this._destroyed = true;
|
|
854
|
+
throw error;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
get time() { return this._time; }
|
|
858
|
+
get liveActionCount() { return this._port.liveActionCount; }
|
|
859
|
+
get liveBindingCount() {
|
|
860
|
+
let count = 0;
|
|
861
|
+
for (const partition of this._partitions)
|
|
862
|
+
count += partition.mixer.liveBindingCount;
|
|
863
|
+
return count;
|
|
864
|
+
}
|
|
865
|
+
get sideEffectOwnerCount() { return this._port.sideEffectOwnerCount; }
|
|
866
|
+
update(deltaSeconds) {
|
|
867
|
+
this._requireActive();
|
|
868
|
+
if (!Number.isFinite(deltaSeconds) || deltaSeconds < 0) {
|
|
869
|
+
throw new RangeError('HYA Animation3D state-machine deltaSeconds must be finite and non-negative.');
|
|
870
|
+
}
|
|
871
|
+
this._runFrame(deltaSeconds);
|
|
872
|
+
}
|
|
873
|
+
evaluate() {
|
|
874
|
+
this._requireActive();
|
|
875
|
+
this._runFrame(0);
|
|
876
|
+
}
|
|
877
|
+
reset() {
|
|
878
|
+
this._requireActive();
|
|
879
|
+
this.controller.reset();
|
|
880
|
+
this._time = 0;
|
|
881
|
+
this.evaluate();
|
|
882
|
+
}
|
|
883
|
+
destroy() {
|
|
884
|
+
if (this._destroyed)
|
|
885
|
+
return;
|
|
886
|
+
this.controller.destroy();
|
|
887
|
+
this._port.destroy();
|
|
888
|
+
this._destroyed = true;
|
|
889
|
+
}
|
|
890
|
+
_runFrame(deltaSeconds) {
|
|
891
|
+
const nextTime = this._time + deltaSeconds;
|
|
892
|
+
this._openFrames(nextTime);
|
|
893
|
+
let poses;
|
|
894
|
+
try {
|
|
895
|
+
poses = runAnimationStateMachineControllerUpdateTransaction(this.controller, deltaSeconds, () => {
|
|
896
|
+
for (const partition of this._partitions)
|
|
897
|
+
partition.adapter.beginFrame(partition.pose);
|
|
898
|
+
}, () => {
|
|
899
|
+
for (const partition of this._partitions)
|
|
900
|
+
partition.adapter.endFrame(partition.pose);
|
|
901
|
+
const result = [];
|
|
902
|
+
for (const partition of this._partitions) {
|
|
903
|
+
result.push(partition.mixer.endSynchronizedFrame(partition.pose));
|
|
904
|
+
partition.frameCompleted = true;
|
|
905
|
+
}
|
|
906
|
+
return result;
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
catch (error) {
|
|
910
|
+
this._cancelFrames();
|
|
911
|
+
throw error;
|
|
912
|
+
}
|
|
913
|
+
for (const partition of this._partitions)
|
|
914
|
+
partition.frameCompleted = false;
|
|
915
|
+
this._time = nextTime;
|
|
916
|
+
for (let index = 0; index < this._partitions.length; index++) {
|
|
917
|
+
this._partitions[index].apply(poses[index]);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
_openFrames(time) {
|
|
921
|
+
const opened = [];
|
|
922
|
+
try {
|
|
923
|
+
for (const partition of this._partitions) {
|
|
924
|
+
partition.previousTime = partition.mixer.time;
|
|
925
|
+
partition.frameCompleted = false;
|
|
926
|
+
partition.mixer.beginSynchronizedFrame(time, partition.pose);
|
|
927
|
+
opened.push(partition);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
catch (error) {
|
|
931
|
+
for (let index = opened.length - 1; index >= 0; index--) {
|
|
932
|
+
opened[index].mixer.cancelSynchronizedFrame(opened[index].pose);
|
|
933
|
+
}
|
|
934
|
+
throw error;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
_cancelFrames() {
|
|
938
|
+
for (const partition of this._partitions)
|
|
939
|
+
partition.adapter.cancelFrame(partition.pose);
|
|
940
|
+
for (let index = this._partitions.length - 1; index >= 0; index--) {
|
|
941
|
+
const partition = this._partitions[index];
|
|
942
|
+
if (partition.frameCompleted) {
|
|
943
|
+
partition.mixer.rollbackCompletedSynchronizedFrame(partition.previousTime);
|
|
944
|
+
partition.frameCompleted = false;
|
|
945
|
+
}
|
|
946
|
+
else
|
|
947
|
+
partition.mixer.cancelSynchronizedFrame(partition.pose);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
_requireActive() {
|
|
951
|
+
if (this._destroyed)
|
|
952
|
+
throw new Error('HYA Animation3D state-machine runtime has been destroyed.');
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
function forEachChild(partitions, handle, visit) {
|
|
956
|
+
let childIndex = 0;
|
|
957
|
+
for (const partition of partitions) {
|
|
958
|
+
if (!partition.clips.has(handle.clipId))
|
|
959
|
+
continue;
|
|
960
|
+
visit(partition.adapter, handle.children[childIndex++]);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
function cloneOwners(source) {
|
|
964
|
+
const result = new Map();
|
|
965
|
+
for (const [key, owners] of source)
|
|
966
|
+
result.set(key, [...owners]);
|
|
967
|
+
return result;
|
|
968
|
+
}
|
|
969
|
+
function translateActionOptions(options, bindingIds) {
|
|
970
|
+
if (!options.mask || !bindingIds || bindingIds.size === 0)
|
|
971
|
+
return options;
|
|
972
|
+
const translate = (ids) => {
|
|
973
|
+
if (!ids)
|
|
974
|
+
return undefined;
|
|
975
|
+
return Object.freeze(ids.map(id => bindingIds.get(id) ?? id));
|
|
976
|
+
};
|
|
977
|
+
const include = translate(options.mask.include);
|
|
978
|
+
const exclude = translate(options.mask.exclude);
|
|
979
|
+
return {
|
|
980
|
+
...options,
|
|
981
|
+
mask: Object.freeze({
|
|
982
|
+
...(include === undefined ? {} : { include }),
|
|
983
|
+
...(exclude === undefined ? {} : { exclude }),
|
|
984
|
+
}),
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
function playbackPosition(time, duration, loop) {
|
|
988
|
+
if (duration <= TIME_EPSILON || loop === 'once') {
|
|
989
|
+
return { localTime: Math.min(duration, Math.max(0, time)), traversal: 0 };
|
|
990
|
+
}
|
|
991
|
+
const traversal = Math.floor(time / duration);
|
|
992
|
+
let localTime = time - traversal * duration;
|
|
993
|
+
if (localTime < 0)
|
|
994
|
+
localTime += duration;
|
|
995
|
+
if (loop === 'ping-pong' && Math.abs(traversal % 2) === 1)
|
|
996
|
+
localTime = duration - localTime;
|
|
997
|
+
return { localTime, traversal };
|
|
998
|
+
}
|
|
999
|
+
function particleCueActive(cue, time, duration) {
|
|
1000
|
+
const atTerminalEnd = Math.abs(cue.end - duration) <= TIME_EPSILON
|
|
1001
|
+
&& Math.abs(time - duration) <= TIME_EPSILON;
|
|
1002
|
+
return time >= cue.start - TIME_EPSILON
|
|
1003
|
+
&& (time < cue.end - TIME_EPSILON || atTerminalEnd);
|
|
1004
|
+
}
|
|
1005
|
+
function stopParticle(emitter) {
|
|
1006
|
+
emitter.playing = false;
|
|
1007
|
+
emitter.emitting = false;
|
|
1008
|
+
emitter.clear();
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
class HyaAnimation3DResolvedEndpoint {
|
|
1012
|
+
binding;
|
|
1013
|
+
_read;
|
|
1014
|
+
_write;
|
|
1015
|
+
constructor(binding, _read, _write) {
|
|
1016
|
+
this.binding = binding;
|
|
1017
|
+
this._read = _read;
|
|
1018
|
+
this._write = _write;
|
|
1019
|
+
}
|
|
1020
|
+
read(out) { this._read(out); }
|
|
1021
|
+
write(value) { this._write(value); }
|
|
1022
|
+
}
|
|
1023
|
+
class HyaAnimation3DBindingResolver {
|
|
1024
|
+
_endpoints = new Map();
|
|
1025
|
+
_revision = 0;
|
|
1026
|
+
_destroyed = false;
|
|
1027
|
+
get revision() { return this._revision; }
|
|
1028
|
+
get size() { return this._endpoints.size; }
|
|
1029
|
+
add(endpoint) {
|
|
1030
|
+
if (this._destroyed)
|
|
1031
|
+
throw new Error('HYA Animation3D resolver is destroyed.');
|
|
1032
|
+
this._endpoints.set(endpoint.binding.id, endpoint);
|
|
1033
|
+
this._revision++;
|
|
1034
|
+
}
|
|
1035
|
+
resolve(binding) {
|
|
1036
|
+
if (this._destroyed)
|
|
1037
|
+
return null;
|
|
1038
|
+
const endpoint = this._endpoints.get(binding.id);
|
|
1039
|
+
if (!endpoint || endpoint.binding.path !== binding.path
|
|
1040
|
+
|| endpoint.binding.valueType !== binding.valueType
|
|
1041
|
+
|| endpoint.binding.valueSize !== binding.valueSize)
|
|
1042
|
+
return null;
|
|
1043
|
+
return endpoint;
|
|
1044
|
+
}
|
|
1045
|
+
destroy() {
|
|
1046
|
+
if (this._destroyed)
|
|
1047
|
+
return;
|
|
1048
|
+
this._destroyed = true;
|
|
1049
|
+
this._endpoints.clear();
|
|
1050
|
+
this._revision++;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
class HyaAnimation3DRuntimeOwner extends Component {
|
|
1054
|
+
_runtime = null;
|
|
1055
|
+
constructor() { super('HyaAnimation3DRuntimeOwner'); }
|
|
1056
|
+
attach(runtime) { this._runtime = runtime; }
|
|
1057
|
+
release(runtime) { if (this._runtime === runtime)
|
|
1058
|
+
this._runtime = null; }
|
|
1059
|
+
clone() { return new HyaAnimation3DRuntimeOwner(); }
|
|
1060
|
+
destroy() {
|
|
1061
|
+
if (this.destroyed)
|
|
1062
|
+
return;
|
|
1063
|
+
const runtime = this._runtime;
|
|
1064
|
+
this._runtime = null;
|
|
1065
|
+
runtime?.destroy();
|
|
1066
|
+
super.destroy();
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
class HyaAnimation3DActionGroupImpl {
|
|
1070
|
+
id;
|
|
1071
|
+
clipId;
|
|
1072
|
+
entries;
|
|
1073
|
+
constructor(id, clipId, entries) {
|
|
1074
|
+
this.id = id;
|
|
1075
|
+
this.clipId = clipId;
|
|
1076
|
+
this.entries = entries;
|
|
1077
|
+
}
|
|
1078
|
+
get actionCount() { return this.entries.length; }
|
|
1079
|
+
get playing() {
|
|
1080
|
+
return this.entries.some(entry => entry.owner.state === 'active'
|
|
1081
|
+
&& (entry.action.status === 'running' || entry.action.status === 'scheduled'));
|
|
1082
|
+
}
|
|
1083
|
+
play() { for (const entry of this.entries)
|
|
1084
|
+
if (entry.owner.state === 'active')
|
|
1085
|
+
entry.action.play(); return this; }
|
|
1086
|
+
stop() { for (const entry of this.entries)
|
|
1087
|
+
if (entry.owner.state === 'active')
|
|
1088
|
+
entry.action.stop(); return this; }
|
|
1089
|
+
reset() { for (const entry of this.entries)
|
|
1090
|
+
if (entry.owner.state === 'active')
|
|
1091
|
+
entry.action.reset(); return this; }
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Optional native-3D HYA owner. The caller must pass an already validated
|
|
1095
|
+
* animation-spec payload; no ECS/GPU object is allocated by the parser.
|
|
1096
|
+
*/
|
|
1097
|
+
class HyaAnimation3DRuntime {
|
|
1098
|
+
root = new Entity('HYA Native 3D');
|
|
1099
|
+
mixer;
|
|
1100
|
+
pose = new Animation3DPoseBuffer();
|
|
1101
|
+
_scene;
|
|
1102
|
+
_payload;
|
|
1103
|
+
_clips;
|
|
1104
|
+
_resources;
|
|
1105
|
+
_entities = new Map();
|
|
1106
|
+
_transforms = new Map();
|
|
1107
|
+
_materials = new Map();
|
|
1108
|
+
_cameras = new Map();
|
|
1109
|
+
_models = [];
|
|
1110
|
+
_mainClips = new Map();
|
|
1111
|
+
_modelClips = new Map();
|
|
1112
|
+
_modelBindingIds = new Map();
|
|
1113
|
+
_actionGroups = new Map();
|
|
1114
|
+
_resolver = new HyaAnimation3DBindingResolver();
|
|
1115
|
+
_poseApplier;
|
|
1116
|
+
_owner = new HyaAnimation3DRuntimeOwner();
|
|
1117
|
+
_geometries = new Set();
|
|
1118
|
+
_particles = [];
|
|
1119
|
+
_abortController = new AbortController();
|
|
1120
|
+
_externalSignal;
|
|
1121
|
+
_externalAbortListener;
|
|
1122
|
+
_disposeModel;
|
|
1123
|
+
_state = 'active';
|
|
1124
|
+
_initialized = false;
|
|
1125
|
+
_stateMachine = null;
|
|
1126
|
+
constructor(options) {
|
|
1127
|
+
this._scene = options.scene;
|
|
1128
|
+
this._payload = options.payload;
|
|
1129
|
+
this._clips = Object.freeze(options.payload.clips.map(lowerRuntimeClip));
|
|
1130
|
+
this._resources = new Map(options.resources.map(resource => [resource.id, resource]));
|
|
1131
|
+
this._disposeModel = options.disposeModel ?? disposeGltfModel;
|
|
1132
|
+
this.mixer = new Animation3DMixer(this._resolver);
|
|
1133
|
+
this._poseApplier = new Animation3DPoseApplier(this._resolver);
|
|
1134
|
+
this._owner.attach(this);
|
|
1135
|
+
this.root.addComponent(this._owner);
|
|
1136
|
+
this._externalSignal = options.signal ?? null;
|
|
1137
|
+
this._externalAbortListener = this._externalSignal ? () => this.destroy() : null;
|
|
1138
|
+
this._externalSignal?.addEventListener('abort', this._externalAbortListener, { once: true });
|
|
1139
|
+
}
|
|
1140
|
+
static async create(options) {
|
|
1141
|
+
if (options.signal?.aborted)
|
|
1142
|
+
throw abortError(options.signal.reason);
|
|
1143
|
+
const runtime = new HyaAnimation3DRuntime(options);
|
|
1144
|
+
try {
|
|
1145
|
+
await runtime._initialize(options);
|
|
1146
|
+
return runtime;
|
|
1147
|
+
}
|
|
1148
|
+
catch (error) {
|
|
1149
|
+
runtime.destroy();
|
|
1150
|
+
throw error;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
get state() { return this._state; }
|
|
1154
|
+
get initialized() { return this._initialized; }
|
|
1155
|
+
get clips() { return this._clips; }
|
|
1156
|
+
get authoredCameraIds() { return Object.freeze([...this._cameras.keys()]); }
|
|
1157
|
+
get stateMachine() { return this._stateMachine; }
|
|
1158
|
+
getEntity(nodeId) { return this._entities.get(nodeId) ?? null; }
|
|
1159
|
+
getMaterial(materialId) { return this._materials.get(materialId) ?? null; }
|
|
1160
|
+
getModelAnimationClips(nodeId) {
|
|
1161
|
+
return this._models.find(model => model.nodeId === nodeId)?.runtime.clips ?? Object.freeze([]);
|
|
1162
|
+
}
|
|
1163
|
+
select(nodeIds) {
|
|
1164
|
+
const selected = new Set(nodeIds);
|
|
1165
|
+
for (const [id, entity] of this._entities) {
|
|
1166
|
+
if (selected.has(id))
|
|
1167
|
+
entity.addComponent(new MeshHelper({ mode: 'aabb', color: [0.25, 0.75, 1, 1], lineWidth: 1.5 }));
|
|
1168
|
+
else
|
|
1169
|
+
entity.removeComponent(MeshHelper);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
useAuthoredCamera(nodeId) {
|
|
1173
|
+
this._assertActive();
|
|
1174
|
+
const entity = this._entities.get(nodeId);
|
|
1175
|
+
if (!entity || !this._cameras.has(nodeId))
|
|
1176
|
+
throw new RangeError(`Unknown authored camera node "${nodeId}".`);
|
|
1177
|
+
this._scene.setCamera(entity);
|
|
1178
|
+
}
|
|
1179
|
+
playClip(clipId, options = {}) {
|
|
1180
|
+
this._assertActive();
|
|
1181
|
+
if (this._stateMachine) {
|
|
1182
|
+
throw new Error('Manual HYA Animation3D clip actions are unavailable while the built-in state machine owns the mixers.');
|
|
1183
|
+
}
|
|
1184
|
+
const base = this._clips.find(clip => clip.id === clipId);
|
|
1185
|
+
if (!base)
|
|
1186
|
+
throw new RangeError(`Unknown HYA Animation3D clip "${clipId}".`);
|
|
1187
|
+
const groupId = options.id ?? `${clipId}:action:${this._actionGroups.size}`;
|
|
1188
|
+
if (this._actionGroups.has(groupId))
|
|
1189
|
+
throw new RangeError(`HYA Animation3D action group "${groupId}" already exists.`);
|
|
1190
|
+
const entries = [];
|
|
1191
|
+
const actionOptions = withoutGroupOptions(options);
|
|
1192
|
+
const main = this._mainClips.get(clipId);
|
|
1193
|
+
if (main)
|
|
1194
|
+
entries.push({ owner: this.mixer, action: this.mixer.createAction(main, { ...actionOptions, id: `${groupId}:scene` }) });
|
|
1195
|
+
for (const [model, clip] of this._modelClips.get(clipId) ?? []) {
|
|
1196
|
+
entries.push({ owner: model.runtime.mixer, action: model.runtime.mixer.createAction(clip, { ...actionOptions, id: `${groupId}:${model.componentId}` }) });
|
|
1197
|
+
}
|
|
1198
|
+
const group = new HyaAnimation3DActionGroupImpl(groupId, clipId, entries);
|
|
1199
|
+
if (options.fadeFrom) {
|
|
1200
|
+
const source = options.fadeFrom;
|
|
1201
|
+
const duration = options.fadeDuration ?? 0;
|
|
1202
|
+
for (const entry of entries) {
|
|
1203
|
+
const sourceEntry = source.entries.find(candidate => candidate.owner === entry.owner);
|
|
1204
|
+
if (sourceEntry)
|
|
1205
|
+
entry.action.crossFadeFrom(sourceEntry.action, duration, options.warp ?? false);
|
|
1206
|
+
else
|
|
1207
|
+
entry.action.fadeIn(duration).play();
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
else
|
|
1211
|
+
group.play();
|
|
1212
|
+
this._actionGroups.set(groupId, group);
|
|
1213
|
+
return group;
|
|
1214
|
+
}
|
|
1215
|
+
removeActionGroup(group) {
|
|
1216
|
+
const id = typeof group === 'string' ? group : group.id;
|
|
1217
|
+
const target = this._actionGroups.get(id);
|
|
1218
|
+
if (!target)
|
|
1219
|
+
return false;
|
|
1220
|
+
for (const entry of target.entries) {
|
|
1221
|
+
if (entry.owner.state !== 'active')
|
|
1222
|
+
continue;
|
|
1223
|
+
entry.action.stop();
|
|
1224
|
+
entry.owner.removeAction(entry.action);
|
|
1225
|
+
}
|
|
1226
|
+
this._actionGroups.delete(id);
|
|
1227
|
+
return true;
|
|
1228
|
+
}
|
|
1229
|
+
update(deltaSeconds) {
|
|
1230
|
+
this._assertActive();
|
|
1231
|
+
if (this._stateMachine) {
|
|
1232
|
+
this._stateMachine.update(deltaSeconds);
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
this._poseApplier.apply(this.mixer.update(deltaSeconds, this.pose));
|
|
1236
|
+
for (const model of this._models)
|
|
1237
|
+
model.runtime.update(deltaSeconds);
|
|
1238
|
+
}
|
|
1239
|
+
setTime(timeSeconds) {
|
|
1240
|
+
this._assertActive();
|
|
1241
|
+
if (this._stateMachine) {
|
|
1242
|
+
throw new Error('HYA Animation3D setTime() is unavailable while the built-in state machine owns the playhead.');
|
|
1243
|
+
}
|
|
1244
|
+
this._poseApplier.apply(this.mixer.setTime(timeSeconds, this.pose));
|
|
1245
|
+
for (const model of this._models)
|
|
1246
|
+
model.runtime.setTime(timeSeconds);
|
|
1247
|
+
}
|
|
1248
|
+
diagnostics() {
|
|
1249
|
+
const residual = this._state === 'destroyed'
|
|
1250
|
+
? this._entities.size + this._materials.size + this._models.length + this._actionGroups.size + this._resolver.size
|
|
1251
|
+
: 0;
|
|
1252
|
+
return Object.freeze({
|
|
1253
|
+
state: this._state,
|
|
1254
|
+
entityCount: this._entities.size,
|
|
1255
|
+
materialCount: this._materials.size,
|
|
1256
|
+
modelCount: this._models.length,
|
|
1257
|
+
actionGroupCount: this._actionGroups.size,
|
|
1258
|
+
ownerResidualCount: residual,
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
destroy() {
|
|
1262
|
+
if (this._state === 'destroyed')
|
|
1263
|
+
return;
|
|
1264
|
+
this._state = 'destroyed';
|
|
1265
|
+
if (this._externalSignal && this._externalAbortListener)
|
|
1266
|
+
this._externalSignal.removeEventListener('abort', this._externalAbortListener);
|
|
1267
|
+
this._stateMachine?.destroy();
|
|
1268
|
+
this._stateMachine = null;
|
|
1269
|
+
for (const id of [...this._actionGroups.keys()])
|
|
1270
|
+
this.removeActionGroup(id);
|
|
1271
|
+
this._abortController.abort('hya-animation3d-runtime-destroyed');
|
|
1272
|
+
this.mixer.destroy();
|
|
1273
|
+
this._resolver.destroy();
|
|
1274
|
+
for (const model of this._models.splice(0)) {
|
|
1275
|
+
model.runtime.destroy();
|
|
1276
|
+
if (!model.model.root.destroyed)
|
|
1277
|
+
this._disposeModel(model.model);
|
|
1278
|
+
}
|
|
1279
|
+
this._owner.release(this);
|
|
1280
|
+
if (!this.root.destroyed)
|
|
1281
|
+
this.root.destroy();
|
|
1282
|
+
this._entities.clear();
|
|
1283
|
+
this._transforms.clear();
|
|
1284
|
+
this._materials.clear();
|
|
1285
|
+
this._cameras.clear();
|
|
1286
|
+
this._mainClips.clear();
|
|
1287
|
+
this._modelClips.clear();
|
|
1288
|
+
this._modelBindingIds.clear();
|
|
1289
|
+
this._particles.length = 0;
|
|
1290
|
+
this._geometries.clear();
|
|
1291
|
+
}
|
|
1292
|
+
async _initialize(options) {
|
|
1293
|
+
this._createMaterials();
|
|
1294
|
+
this._createNodes(options);
|
|
1295
|
+
this._attachHierarchy();
|
|
1296
|
+
this._scene.add(this.root);
|
|
1297
|
+
if (options.addPreviewLights !== false)
|
|
1298
|
+
this._addPreviewLights();
|
|
1299
|
+
await this._loadModels(options);
|
|
1300
|
+
this._buildClipPartitions();
|
|
1301
|
+
this._createStateMachineRuntime();
|
|
1302
|
+
const authoredCamera = this._cameras.keys().next().value;
|
|
1303
|
+
if (options.useAuthoredCamera !== false && authoredCamera)
|
|
1304
|
+
this.useAuthoredCamera(authoredCamera);
|
|
1305
|
+
this._initialized = true;
|
|
1306
|
+
}
|
|
1307
|
+
_createMaterials() {
|
|
1308
|
+
for (const descriptor of this._payload.materials) {
|
|
1309
|
+
this._materials.set(descriptor.id, materialFromDescriptor(descriptor));
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
_createNodes(options) {
|
|
1313
|
+
for (const descriptor of this._payload.nodes) {
|
|
1314
|
+
const entity = new Entity(descriptor.name || descriptor.id);
|
|
1315
|
+
const transform = new Transform3D();
|
|
1316
|
+
const state = {
|
|
1317
|
+
transform,
|
|
1318
|
+
translation: new Float32Array(descriptor.transform.translation),
|
|
1319
|
+
rotation: new Float32Array(descriptor.transform.rotation),
|
|
1320
|
+
scale: new Float32Array(descriptor.transform.scale),
|
|
1321
|
+
matrix: new Float32Array(16),
|
|
1322
|
+
};
|
|
1323
|
+
commitTransform(state);
|
|
1324
|
+
entity.addComponent(transform);
|
|
1325
|
+
this._entities.set(descriptor.id, entity);
|
|
1326
|
+
this._transforms.set(descriptor.id, state);
|
|
1327
|
+
for (const component of descriptor.components)
|
|
1328
|
+
this._createComponent(descriptor, entity, component, options);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
_createComponent(node, entity, component, options) {
|
|
1332
|
+
if (component.kind === 'camera3d') {
|
|
1333
|
+
const camera = cameraFromProjection(component.projection, this._payload.viewport.width / this._payload.viewport.height);
|
|
1334
|
+
entity.addComponent(camera);
|
|
1335
|
+
this._cameras.set(node.id, camera);
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
if (component.kind === 'primitive3d') {
|
|
1339
|
+
const geometry = primitiveGeometry(component.primitive);
|
|
1340
|
+
this._geometries.add(geometry);
|
|
1341
|
+
entity.addComponent(new Mesh3D(geometry, required(this._materials, component.materialId, 'material')));
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
if (component.kind === 'particle3d') {
|
|
1345
|
+
const { textureResource, ...descriptor } = component.descriptor;
|
|
1346
|
+
const resource = textureResource ? required(this._resources, textureResource, 'particle texture') : undefined;
|
|
1347
|
+
const textureSource = resource ? options.resolveTexture?.(resource, null) ?? null : null;
|
|
1348
|
+
const emitter = new ParticleEmitter3D({
|
|
1349
|
+
...descriptor,
|
|
1350
|
+
...(textureSource ? { textureSource } : {}),
|
|
1351
|
+
...(this._payload.stateMachine ? { playing: false, emitting: false } : {}),
|
|
1352
|
+
});
|
|
1353
|
+
entity.addComponent(emitter);
|
|
1354
|
+
this._particles.push({
|
|
1355
|
+
key: `${node.id}\u0000${component.id}`,
|
|
1356
|
+
node,
|
|
1357
|
+
componentId: component.id,
|
|
1358
|
+
emitter,
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
_attachHierarchy() {
|
|
1363
|
+
for (const descriptor of this._payload.nodes) {
|
|
1364
|
+
const entity = required(this._entities, descriptor.id, 'node');
|
|
1365
|
+
if (descriptor.parent)
|
|
1366
|
+
required(this._entities, descriptor.parent, 'parent node').addChild(entity);
|
|
1367
|
+
else
|
|
1368
|
+
this.root.addChild(entity);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
async _loadModels(options) {
|
|
1372
|
+
const loader = options.loadModel ?? loadGltfModel;
|
|
1373
|
+
const tasks = [];
|
|
1374
|
+
for (const node of this._payload.nodes) {
|
|
1375
|
+
for (const component of node.components) {
|
|
1376
|
+
if (component.kind !== 'model3d')
|
|
1377
|
+
continue;
|
|
1378
|
+
tasks.push((async () => {
|
|
1379
|
+
const resource = required(this._resources, component.resource, 'model resource');
|
|
1380
|
+
const model = await loader(resource.uri, { signal: this._abortController.signal });
|
|
1381
|
+
if (this._state !== 'active') {
|
|
1382
|
+
if (!model.root.destroyed)
|
|
1383
|
+
this._disposeModel(model);
|
|
1384
|
+
throw abortError('runtime-destroyed-during-model-load');
|
|
1385
|
+
}
|
|
1386
|
+
const entity = required(this._entities, node.id, 'model node');
|
|
1387
|
+
entity.addChild(model.root);
|
|
1388
|
+
applyMaterialOverrides(model.root, component.materialOverrides ?? [], this._materials);
|
|
1389
|
+
const runtime = new GltfAnimation3DRuntime(model, {
|
|
1390
|
+
signal: this._abortController.signal,
|
|
1391
|
+
clipIdPrefix: `hya:${component.id}`,
|
|
1392
|
+
});
|
|
1393
|
+
this._models.push({ nodeId: node.id, componentId: component.id, model, runtime });
|
|
1394
|
+
})());
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
await Promise.all(tasks);
|
|
1398
|
+
}
|
|
1399
|
+
_buildClipPartitions() {
|
|
1400
|
+
for (const clip of this._clips) {
|
|
1401
|
+
const mainTracks = [];
|
|
1402
|
+
const modelTracks = new Map();
|
|
1403
|
+
for (const track of clip.tracks) {
|
|
1404
|
+
const endpoint = this._createSceneEndpoint(track.binding);
|
|
1405
|
+
if (endpoint) {
|
|
1406
|
+
this._resolver.add(endpoint);
|
|
1407
|
+
mainTracks.push(track);
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
const modelMatch = this._matchModelBinding(track);
|
|
1411
|
+
if (modelMatch) {
|
|
1412
|
+
let tracks = modelTracks.get(modelMatch.model);
|
|
1413
|
+
if (!tracks) {
|
|
1414
|
+
tracks = [];
|
|
1415
|
+
modelTracks.set(modelMatch.model, tracks);
|
|
1416
|
+
}
|
|
1417
|
+
tracks.push({ ...track, binding: modelMatch.binding });
|
|
1418
|
+
let bindingIds = this._modelBindingIds.get(modelMatch.model);
|
|
1419
|
+
if (!bindingIds) {
|
|
1420
|
+
bindingIds = new Map();
|
|
1421
|
+
this._modelBindingIds.set(modelMatch.model, bindingIds);
|
|
1422
|
+
}
|
|
1423
|
+
bindingIds.set(track.binding.id, modelMatch.binding.id);
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
throw new RangeError(`HYA Animation3D binding "${track.binding.id}" cannot be resolved.`);
|
|
1427
|
+
}
|
|
1428
|
+
if (mainTracks.length > 0 || this._payload.stateMachine) {
|
|
1429
|
+
this._mainClips.set(clip.id, { ...clip, tracks: Object.freeze(mainTracks) });
|
|
1430
|
+
}
|
|
1431
|
+
const byModel = new Map();
|
|
1432
|
+
for (const [model, tracks] of modelTracks)
|
|
1433
|
+
byModel.set(model, { ...clip, id: `${clip.id}:${model.componentId}`, tracks: Object.freeze(tracks) });
|
|
1434
|
+
this._modelClips.set(clip.id, byModel);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
_createStateMachineRuntime() {
|
|
1438
|
+
const definition = this._payload.stateMachine;
|
|
1439
|
+
if (!definition)
|
|
1440
|
+
return;
|
|
1441
|
+
const partitions = [{
|
|
1442
|
+
id: 'scene',
|
|
1443
|
+
mixer: animation3DMixerRuntime(this.mixer),
|
|
1444
|
+
clips: this._mainClips,
|
|
1445
|
+
pose: this.pose,
|
|
1446
|
+
apply: pose => this._poseApplier.apply(pose),
|
|
1447
|
+
}];
|
|
1448
|
+
for (const model of this._models) {
|
|
1449
|
+
const clips = new Map();
|
|
1450
|
+
for (const [clipId, byModel] of this._modelClips) {
|
|
1451
|
+
const clip = byModel.get(model);
|
|
1452
|
+
if (clip)
|
|
1453
|
+
clips.set(clipId, clip);
|
|
1454
|
+
}
|
|
1455
|
+
if (clips.size === 0)
|
|
1456
|
+
continue;
|
|
1457
|
+
partitions.push({
|
|
1458
|
+
id: `model:${model.nodeId}:${model.componentId}`,
|
|
1459
|
+
mixer: animation3DMixerRuntime(model.runtime.mixer),
|
|
1460
|
+
clips,
|
|
1461
|
+
...(this._modelBindingIds.get(model) ? { bindingIds: this._modelBindingIds.get(model) } : {}),
|
|
1462
|
+
pose: model.runtime.pose,
|
|
1463
|
+
apply: pose => model.runtime.applySynchronizedPose(pose),
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
const particleCues = new Map();
|
|
1467
|
+
for (const clip of this._clips) {
|
|
1468
|
+
const cues = [];
|
|
1469
|
+
for (const particle of this._particles) {
|
|
1470
|
+
const start = Math.min(clip.duration, Math.max(0, particle.node.start ?? 0));
|
|
1471
|
+
const authoredEnd = particle.node.duration === undefined
|
|
1472
|
+
? clip.duration
|
|
1473
|
+
: (particle.node.start ?? 0) + particle.node.duration;
|
|
1474
|
+
const end = Math.min(clip.duration, Math.max(0, authoredEnd));
|
|
1475
|
+
if (end <= start)
|
|
1476
|
+
continue;
|
|
1477
|
+
cues.push(Object.freeze({
|
|
1478
|
+
key: particle.key,
|
|
1479
|
+
emitter: particle.emitter,
|
|
1480
|
+
start,
|
|
1481
|
+
end,
|
|
1482
|
+
}));
|
|
1483
|
+
}
|
|
1484
|
+
particleCues.set(clip.id, Object.freeze(cues));
|
|
1485
|
+
}
|
|
1486
|
+
this._stateMachine = new HyaAnimation3DStateMachineRuntime({
|
|
1487
|
+
definition,
|
|
1488
|
+
clips: this._clips,
|
|
1489
|
+
partitions,
|
|
1490
|
+
particleCues,
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
_createSceneEndpoint(binding) {
|
|
1494
|
+
if (binding.path === 'morph.weights')
|
|
1495
|
+
return null;
|
|
1496
|
+
const nodeId = binding.target.kind === 'node-id' ? binding.target.nodeId
|
|
1497
|
+
: binding.target.kind === 'node-path' && binding.target.segments.length === 1 ? binding.target.segments[0]
|
|
1498
|
+
: null;
|
|
1499
|
+
if (binding.path.startsWith('transform.') && nodeId) {
|
|
1500
|
+
const state = this._transforms.get(nodeId);
|
|
1501
|
+
if (!state)
|
|
1502
|
+
return null;
|
|
1503
|
+
const target = binding.path === 'transform.translation' ? state.translation
|
|
1504
|
+
: binding.path === 'transform.rotation' ? state.rotation : state.scale;
|
|
1505
|
+
return endpoint(binding, new Float32Array(target), value => { copy(value, target, binding.valueSize); commitTransform(state); });
|
|
1506
|
+
}
|
|
1507
|
+
if (binding.path !== 'property')
|
|
1508
|
+
return null;
|
|
1509
|
+
if (binding.component === 'material3d' && binding.target.kind === 'slot') {
|
|
1510
|
+
const material = this._materials.get(binding.target.slot);
|
|
1511
|
+
if (!material)
|
|
1512
|
+
return null;
|
|
1513
|
+
return materialEndpoint(binding, material);
|
|
1514
|
+
}
|
|
1515
|
+
if (binding.component === 'camera3d' && nodeId) {
|
|
1516
|
+
const camera = this._cameras.get(nodeId);
|
|
1517
|
+
if (!camera)
|
|
1518
|
+
return null;
|
|
1519
|
+
return cameraEndpoint(binding, camera, this._payload.viewport.width / this._payload.viewport.height);
|
|
1520
|
+
}
|
|
1521
|
+
return null;
|
|
1522
|
+
}
|
|
1523
|
+
_matchModelBinding(track) {
|
|
1524
|
+
const candidates = [];
|
|
1525
|
+
for (const model of this._models) {
|
|
1526
|
+
for (const sourceClip of model.runtime.clips) {
|
|
1527
|
+
for (const sourceTrack of sourceClip.tracks) {
|
|
1528
|
+
if (sameModelBinding(track.binding, sourceTrack.binding, model.nodeId))
|
|
1529
|
+
candidates.push({ model, binding: sourceTrack.binding });
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
const exact = candidates.find(candidate => candidate.binding.id === track.binding.id);
|
|
1534
|
+
if (exact)
|
|
1535
|
+
return exact;
|
|
1536
|
+
return candidates.length === 1 ? candidates[0] : null;
|
|
1537
|
+
}
|
|
1538
|
+
_addPreviewLights() {
|
|
1539
|
+
const ambient = new Entity('HYA Preview Ambient');
|
|
1540
|
+
ambient.addComponent(new AmbientLight({ color: [0.72, 0.8, 1], intensity: 0.5 }));
|
|
1541
|
+
this.root.addChild(ambient);
|
|
1542
|
+
const sun = new Entity('HYA Preview Sun');
|
|
1543
|
+
sun.addComponent(new DirectionalLight({ color: [1, 0.94, 0.84], intensity: 2, direction: [-0.4, -1, -0.35], castShadow: false }));
|
|
1544
|
+
this.root.addChild(sun);
|
|
1545
|
+
}
|
|
1546
|
+
_assertActive() {
|
|
1547
|
+
if (this._state !== 'active' || this.root.destroyed) {
|
|
1548
|
+
this.destroy();
|
|
1549
|
+
throw new Error('HYA Animation3D runtime is destroyed.');
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
async function createHyaAnimation3DRuntime(options) {
|
|
1554
|
+
return await HyaAnimation3DRuntime.create(options);
|
|
1555
|
+
}
|
|
1556
|
+
function lowerRuntimeClip(value) {
|
|
1557
|
+
return Object.freeze({
|
|
1558
|
+
format: 'haiyue-animation3d-clip@1',
|
|
1559
|
+
id: value.id,
|
|
1560
|
+
name: value.name,
|
|
1561
|
+
duration: value.duration,
|
|
1562
|
+
tracks: Object.freeze(value.tracks.map(track => Object.freeze({
|
|
1563
|
+
id: track.id,
|
|
1564
|
+
binding: track.binding,
|
|
1565
|
+
interpolation: track.interpolation,
|
|
1566
|
+
times: new Float32Array(Array.from(track.times)),
|
|
1567
|
+
values: new Float32Array(Array.from(track.values)),
|
|
1568
|
+
}))),
|
|
1569
|
+
events: Object.freeze([...value.events]),
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
function materialFromDescriptor(value) {
|
|
1573
|
+
return new PbrMaterial({
|
|
1574
|
+
baseColor: value.baseColorFactor,
|
|
1575
|
+
metallic: value.metallicFactor,
|
|
1576
|
+
roughness: Math.max(0.04, value.roughnessFactor),
|
|
1577
|
+
emissiveFactor: value.emissiveFactor,
|
|
1578
|
+
alphaMode: value.alphaMode,
|
|
1579
|
+
alphaCutoff: value.alphaCutoff ?? 0.5,
|
|
1580
|
+
doubleSided: value.doubleSided,
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
function cameraFromProjection(value, aspect) {
|
|
1584
|
+
if (value.kind === 'perspective')
|
|
1585
|
+
return new Camera3D({ type: 'perspective', fov: value.fovYRadians, near: value.near, far: value.far, aspect });
|
|
1586
|
+
const half = value.orthoHeight * 0.5;
|
|
1587
|
+
return new Camera3D({ type: 'orthographic', near: value.near, far: value.far, aspect, left: -half * aspect, right: half * aspect, top: half, bottom: -half });
|
|
1588
|
+
}
|
|
1589
|
+
function primitiveGeometry(value) {
|
|
1590
|
+
switch (value) {
|
|
1591
|
+
case 'box': return createBox3D();
|
|
1592
|
+
case 'sphere': return createSphere3D();
|
|
1593
|
+
case 'plane': return createPlane3D();
|
|
1594
|
+
case 'cylinder': return createCylinder3D();
|
|
1595
|
+
case 'cone': return createCone3D();
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
function commitTransform(state) {
|
|
1599
|
+
composeTrsMatrix(state.translation, state.rotation, state.scale, state.matrix);
|
|
1600
|
+
state.transform.localMatrix = state.matrix;
|
|
1601
|
+
}
|
|
1602
|
+
function endpoint(binding, base, write) {
|
|
1603
|
+
return new HyaAnimation3DResolvedEndpoint(binding, out => copy(base, out, binding.valueSize), write);
|
|
1604
|
+
}
|
|
1605
|
+
function materialEndpoint(binding, material) {
|
|
1606
|
+
const base = new Float32Array(binding.valueSize);
|
|
1607
|
+
if (binding.property === 'baseColorFactor')
|
|
1608
|
+
material.baseColor.writeLinear(base);
|
|
1609
|
+
else {
|
|
1610
|
+
const value = binding.property === 'emissiveFactor' ? material.emissiveFactor
|
|
1611
|
+
: [binding.property === 'metallicFactor' ? material.metallic
|
|
1612
|
+
: binding.property === 'roughnessFactor' ? material.roughness
|
|
1613
|
+
: material.alphaCutoff];
|
|
1614
|
+
copy(value, base, binding.valueSize);
|
|
1615
|
+
}
|
|
1616
|
+
const write = (value) => {
|
|
1617
|
+
if (binding.property === 'baseColorFactor')
|
|
1618
|
+
material.baseColor = tuple4(value);
|
|
1619
|
+
else if (binding.property === 'emissiveFactor')
|
|
1620
|
+
material.emissiveFactor = tuple3(value);
|
|
1621
|
+
else if (binding.property === 'metallicFactor')
|
|
1622
|
+
material.metallic = value[0] ?? 0;
|
|
1623
|
+
else if (binding.property === 'roughnessFactor')
|
|
1624
|
+
material.roughness = Math.max(0.04, value[0] ?? 0.04);
|
|
1625
|
+
else if (binding.property === 'alphaCutoff')
|
|
1626
|
+
material.alphaCutoff = value[0] ?? 0.5;
|
|
1627
|
+
};
|
|
1628
|
+
return endpoint(binding, base, write);
|
|
1629
|
+
}
|
|
1630
|
+
function cameraEndpoint(binding, camera, aspect) {
|
|
1631
|
+
const base = new Float32Array([binding.property === 'fovYRadians' ? camera.fov
|
|
1632
|
+
: binding.property === 'near' ? camera.near
|
|
1633
|
+
: binding.property === 'far' ? camera.far
|
|
1634
|
+
: camera.orthoTop - camera.orthoBottom]);
|
|
1635
|
+
const write = (value) => {
|
|
1636
|
+
const next = value[0] ?? 0;
|
|
1637
|
+
if (binding.property === 'fovYRadians')
|
|
1638
|
+
camera.fov = next;
|
|
1639
|
+
else if (binding.property === 'near')
|
|
1640
|
+
camera.near = next;
|
|
1641
|
+
else if (binding.property === 'far')
|
|
1642
|
+
camera.far = next;
|
|
1643
|
+
else if (binding.property === 'orthoHeight') {
|
|
1644
|
+
const half = next * 0.5;
|
|
1645
|
+
camera.orthoLeft = -half * aspect;
|
|
1646
|
+
camera.orthoRight = half * aspect;
|
|
1647
|
+
camera.orthoTop = half;
|
|
1648
|
+
camera.orthoBottom = -half;
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
return endpoint(binding, base, write);
|
|
1652
|
+
}
|
|
1653
|
+
function sameModelBinding(candidate, endpoint, modelNodeId) {
|
|
1654
|
+
if (candidate.id === endpoint.id)
|
|
1655
|
+
return candidate.path === endpoint.path && candidate.valueSize === endpoint.valueSize;
|
|
1656
|
+
if (candidate.path !== endpoint.path || candidate.valueSize !== endpoint.valueSize)
|
|
1657
|
+
return false;
|
|
1658
|
+
if (candidate.target.kind === 'node-path' && endpoint.target.kind === 'node-path') {
|
|
1659
|
+
const authored = candidate.target.segments[0] === modelNodeId ? candidate.target.segments.slice(1) : candidate.target.segments;
|
|
1660
|
+
const endpointSegments = endpoint.target.segments;
|
|
1661
|
+
return authored.length <= endpointSegments.length
|
|
1662
|
+
&& authored.every((segment, index) => segment === endpointSegments[endpointSegments.length - authored.length + index]);
|
|
1663
|
+
}
|
|
1664
|
+
if (candidate.path === 'morph.weights' && candidate.target.kind === 'slot') {
|
|
1665
|
+
return candidate.target.slot === modelNodeId || candidate.target.slot.startsWith(`${modelNodeId}:`);
|
|
1666
|
+
}
|
|
1667
|
+
return false;
|
|
1668
|
+
}
|
|
1669
|
+
function applyMaterialOverrides(root, overrides, materials) {
|
|
1670
|
+
if (overrides.length === 0)
|
|
1671
|
+
return;
|
|
1672
|
+
const bySlot = new Map(overrides.map(item => [item.slot, required(materials, item.materialId, 'material override')]));
|
|
1673
|
+
const pending = [root];
|
|
1674
|
+
while (pending.length > 0) {
|
|
1675
|
+
const entity = pending.pop();
|
|
1676
|
+
const mesh = entity.getComponent(Mesh3D);
|
|
1677
|
+
const material = bySlot.get(entity.name);
|
|
1678
|
+
if (mesh && material)
|
|
1679
|
+
mesh.material = material;
|
|
1680
|
+
pending.push(...entity.children);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
function copy(source, target, count) {
|
|
1684
|
+
for (let index = 0; index < count; index++)
|
|
1685
|
+
target[index] = source[index] ?? 0;
|
|
1686
|
+
}
|
|
1687
|
+
function tuple3(value) {
|
|
1688
|
+
return [value[0] ?? 0, value[1] ?? 0, value[2] ?? 0];
|
|
1689
|
+
}
|
|
1690
|
+
function tuple4(value) {
|
|
1691
|
+
return [value[0] ?? 0, value[1] ?? 0, value[2] ?? 0, value[3] ?? 0];
|
|
1692
|
+
}
|
|
1693
|
+
function withoutGroupOptions(options) {
|
|
1694
|
+
const { fadeFrom: _fadeFrom, fadeDuration: _fadeDuration, warp: _warp, id: _id, ...result } = options;
|
|
1695
|
+
return result;
|
|
1696
|
+
}
|
|
1697
|
+
function required(map, key, label) {
|
|
1698
|
+
const value = map.get(key);
|
|
1699
|
+
if (!value)
|
|
1700
|
+
throw new RangeError(`Unknown ${label} "${String(key)}".`);
|
|
1701
|
+
return value;
|
|
1702
|
+
}
|
|
1703
|
+
function abortError(reason) {
|
|
1704
|
+
return new DOMException(reason === undefined ? 'The operation was aborted.' : String(reason), 'AbortError');
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
export { Animation3DError, Animation3DMixer, Animation3DPoseApplier, Animation3DPoseBuffer, Animation3DStateMachineController, Animation3DStateMachineValidationError, HyaAnimation3DRuntime, compileAnimation3DStateMachineDefinition, createHyaAnimation3DRuntime, validateAnimation3DStateMachineDefinition };
|
|
1708
|
+
//# sourceMappingURL=animation3d.js.map
|