@aura3d/assets 1.0.6
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 +17 -0
- package/dist/AdvancedAssetCorpus.d.ts +124 -0
- package/dist/AdvancedAssetCorpus.d.ts.map +1 -0
- package/dist/AdvancedAssetCorpus.js +266 -0
- package/dist/AdvancedAssetCorpus.js.map +1 -0
- package/dist/AssetBundleCacheFixtures.d.ts +62 -0
- package/dist/AssetBundleCacheFixtures.d.ts.map +1 -0
- package/dist/AssetBundleCacheFixtures.js +222 -0
- package/dist/AssetBundleCacheFixtures.js.map +1 -0
- package/dist/AssetCache.d.ts +21 -0
- package/dist/AssetCache.d.ts.map +1 -0
- package/dist/AssetCache.js +54 -0
- package/dist/AssetCache.js.map +1 -0
- package/dist/AssetCompatibility.d.ts +54 -0
- package/dist/AssetCompatibility.d.ts.map +1 -0
- package/dist/AssetCompatibility.js +122 -0
- package/dist/AssetCompatibility.js.map +1 -0
- package/dist/AssetCorpus.d.ts +88 -0
- package/dist/AssetCorpus.d.ts.map +1 -0
- package/dist/AssetCorpus.js +215 -0
- package/dist/AssetCorpus.js.map +1 -0
- package/dist/AssetDependencyGraph.d.ts +12 -0
- package/dist/AssetDependencyGraph.d.ts.map +1 -0
- package/dist/AssetDependencyGraph.js +66 -0
- package/dist/AssetDependencyGraph.js.map +1 -0
- package/dist/AssetHandle.d.ts +26 -0
- package/dist/AssetHandle.d.ts.map +1 -0
- package/dist/AssetHandle.js +53 -0
- package/dist/AssetHandle.js.map +1 -0
- package/dist/AssetImportPreflight.d.ts +43 -0
- package/dist/AssetImportPreflight.d.ts.map +1 -0
- package/dist/AssetImportPreflight.js +117 -0
- package/dist/AssetImportPreflight.js.map +1 -0
- package/dist/AssetInspection.d.ts +183 -0
- package/dist/AssetInspection.d.ts.map +1 -0
- package/dist/AssetInspection.js +397 -0
- package/dist/AssetInspection.js.map +1 -0
- package/dist/AssetLoader.d.ts +26 -0
- package/dist/AssetLoader.d.ts.map +1 -0
- package/dist/AssetLoader.js +12 -0
- package/dist/AssetLoader.js.map +1 -0
- package/dist/AssetManager.d.ts +37 -0
- package/dist/AssetManager.d.ts.map +1 -0
- package/dist/AssetManager.js +161 -0
- package/dist/AssetManager.js.map +1 -0
- package/dist/AssetRegistry.d.ts +10 -0
- package/dist/AssetRegistry.d.ts.map +1 -0
- package/dist/AssetRegistry.js +34 -0
- package/dist/AssetRegistry.js.map +1 -0
- package/dist/AssetRenderDefaults.d.ts +13 -0
- package/dist/AssetRenderDefaults.d.ts.map +1 -0
- package/dist/AssetRenderDefaults.js +23 -0
- package/dist/AssetRenderDefaults.js.map +1 -0
- package/dist/AudioLoader.d.ts +17 -0
- package/dist/AudioLoader.d.ts.map +1 -0
- package/dist/AudioLoader.js +23 -0
- package/dist/AudioLoader.js.map +1 -0
- package/dist/BlenderExportValidation.d.ts +64 -0
- package/dist/BlenderExportValidation.d.ts.map +1 -0
- package/dist/BlenderExportValidation.js +143 -0
- package/dist/BlenderExportValidation.js.map +1 -0
- package/dist/CarConceptMaterialStability.d.ts +28 -0
- package/dist/CarConceptMaterialStability.d.ts.map +1 -0
- package/dist/CarConceptMaterialStability.js +1308 -0
- package/dist/CarConceptMaterialStability.js.map +1 -0
- package/dist/EXRLoader.d.ts +2 -0
- package/dist/EXRLoader.d.ts.map +1 -0
- package/dist/EXRLoader.js +2 -0
- package/dist/EXRLoader.js.map +1 -0
- package/dist/ExternalParityGLTFCorpus.d.ts +39 -0
- package/dist/ExternalParityGLTFCorpus.d.ts.map +1 -0
- package/dist/ExternalParityGLTFCorpus.js +32 -0
- package/dist/ExternalParityGLTFCorpus.js.map +1 -0
- package/dist/GLTFAnimationRuntime.d.ts +243 -0
- package/dist/GLTFAnimationRuntime.d.ts.map +1 -0
- package/dist/GLTFAnimationRuntime.js +954 -0
- package/dist/GLTFAnimationRuntime.js.map +1 -0
- package/dist/GLTFCompressionDecoders.d.ts +42 -0
- package/dist/GLTFCompressionDecoders.d.ts.map +1 -0
- package/dist/GLTFCompressionDecoders.js +99 -0
- package/dist/GLTFCompressionDecoders.js.map +1 -0
- package/dist/GLTFExtensionSupport.d.ts +33 -0
- package/dist/GLTFExtensionSupport.d.ts.map +1 -0
- package/dist/GLTFExtensionSupport.js +85 -0
- package/dist/GLTFExtensionSupport.js.map +1 -0
- package/dist/GLTFLoader.d.ts +361 -0
- package/dist/GLTFLoader.d.ts.map +1 -0
- package/dist/GLTFLoader.js +2735 -0
- package/dist/GLTFLoader.js.map +1 -0
- package/dist/GLTFRenderResources.d.ts +162 -0
- package/dist/GLTFRenderResources.d.ts.map +1 -0
- package/dist/GLTFRenderResources.js +1684 -0
- package/dist/GLTFRenderResources.js.map +1 -0
- package/dist/HDRLoader.d.ts +2 -0
- package/dist/HDRLoader.d.ts.map +1 -0
- package/dist/HDRLoader.js +2 -0
- package/dist/HDRLoader.js.map +1 -0
- package/dist/ImageLoader.d.ts +15 -0
- package/dist/ImageLoader.d.ts.map +1 -0
- package/dist/ImageLoader.js +36 -0
- package/dist/ImageLoader.js.map +1 -0
- package/dist/ImportPipeline.d.ts +39 -0
- package/dist/ImportPipeline.d.ts.map +1 -0
- package/dist/ImportPipeline.js +105 -0
- package/dist/ImportPipeline.js.map +1 -0
- package/dist/KTX2BasisTextureTranscoder.d.ts +9 -0
- package/dist/KTX2BasisTextureTranscoder.d.ts.map +1 -0
- package/dist/KTX2BasisTextureTranscoder.js +178 -0
- package/dist/KTX2BasisTextureTranscoder.js.map +1 -0
- package/dist/LoadContext.d.ts +20 -0
- package/dist/LoadContext.d.ts.map +1 -0
- package/dist/LoadContext.js +42 -0
- package/dist/LoadContext.js.map +1 -0
- package/dist/MaterialLoader.d.ts +17 -0
- package/dist/MaterialLoader.d.ts.map +1 -0
- package/dist/MaterialLoader.js +138 -0
- package/dist/MaterialLoader.js.map +1 -0
- package/dist/MeshOptimization.d.ts +20 -0
- package/dist/MeshOptimization.d.ts.map +1 -0
- package/dist/MeshOptimization.js +65 -0
- package/dist/MeshOptimization.js.map +1 -0
- package/dist/OBJLoader.d.ts +13 -0
- package/dist/OBJLoader.d.ts.map +1 -0
- package/dist/OBJLoader.js +492 -0
- package/dist/OBJLoader.js.map +1 -0
- package/dist/SceneAnalysisFixtures.d.ts +100 -0
- package/dist/SceneAnalysisFixtures.d.ts.map +1 -0
- package/dist/SceneAnalysisFixtures.js +331 -0
- package/dist/SceneAnalysisFixtures.js.map +1 -0
- package/dist/SceneLoader.d.ts +27 -0
- package/dist/SceneLoader.d.ts.map +1 -0
- package/dist/SceneLoader.js +40 -0
- package/dist/SceneLoader.js.map +1 -0
- package/dist/ShaderLoader.d.ts +12 -0
- package/dist/ShaderLoader.d.ts.map +1 -0
- package/dist/ShaderLoader.js +18 -0
- package/dist/ShaderLoader.js.map +1 -0
- package/dist/TextureLoader.d.ts +16 -0
- package/dist/TextureLoader.d.ts.map +1 -0
- package/dist/TextureLoader.js +17 -0
- package/dist/TextureLoader.js.map +1 -0
- package/dist/TexturePipeline.d.ts +24 -0
- package/dist/TexturePipeline.d.ts.map +1 -0
- package/dist/TexturePipeline.js +64 -0
- package/dist/TexturePipeline.js.map +1 -0
- package/dist/TextureStreaming.d.ts +10 -0
- package/dist/TextureStreaming.d.ts.map +1 -0
- package/dist/TextureStreaming.js +11 -0
- package/dist/TextureStreaming.js.map +1 -0
- package/dist/WorkerAssetJobs.d.ts +18 -0
- package/dist/WorkerAssetJobs.d.ts.map +1 -0
- package/dist/WorkerAssetJobs.js +37 -0
- package/dist/WorkerAssetJobs.js.map +1 -0
- package/dist/advanced-gallery/index.d.ts +3 -0
- package/dist/advanced-gallery/index.d.ts.map +1 -0
- package/dist/advanced-gallery/index.js +2 -0
- package/dist/advanced-gallery/index.js.map +1 -0
- package/dist/asset-corpus/GLTFSceneLoader.d.ts +3 -0
- package/dist/asset-corpus/GLTFSceneLoader.d.ts.map +1 -0
- package/dist/asset-corpus/GLTFSceneLoader.js +2 -0
- package/dist/asset-corpus/GLTFSceneLoader.js.map +1 -0
- package/dist/asset-corpus/ProductionAssetCorpus.d.ts +89 -0
- package/dist/asset-corpus/ProductionAssetCorpus.d.ts.map +1 -0
- package/dist/asset-corpus/ProductionAssetCorpus.js +116 -0
- package/dist/asset-corpus/ProductionAssetCorpus.js.map +1 -0
- package/dist/asset-corpus/ProductionAssetPipeline.d.ts +3 -0
- package/dist/asset-corpus/ProductionAssetPipeline.d.ts.map +1 -0
- package/dist/asset-corpus/ProductionAssetPipeline.js +3 -0
- package/dist/asset-corpus/ProductionAssetPipeline.js.map +1 -0
- package/dist/asset-corpus/ProductionGLTFRenderPipeline.d.ts +65 -0
- package/dist/asset-corpus/ProductionGLTFRenderPipeline.d.ts.map +1 -0
- package/dist/asset-corpus/ProductionGLTFRenderPipeline.js +110 -0
- package/dist/asset-corpus/ProductionGLTFRenderPipeline.js.map +1 -0
- package/dist/asset-corpus/ProductionHDRTextureLoader.d.ts +8 -0
- package/dist/asset-corpus/ProductionHDRTextureLoader.d.ts.map +1 -0
- package/dist/asset-corpus/ProductionHDRTextureLoader.js +2 -0
- package/dist/asset-corpus/ProductionHDRTextureLoader.js.map +1 -0
- package/dist/asset-corpus/ProductionKTX2TextureLoader.d.ts +7 -0
- package/dist/asset-corpus/ProductionKTX2TextureLoader.d.ts.map +1 -0
- package/dist/asset-corpus/ProductionKTX2TextureLoader.js +2 -0
- package/dist/asset-corpus/ProductionKTX2TextureLoader.js.map +1 -0
- package/dist/asset-corpus/ProductionTextureLoader.d.ts +7 -0
- package/dist/asset-corpus/ProductionTextureLoader.d.ts.map +1 -0
- package/dist/asset-corpus/ProductionTextureLoader.js +2 -0
- package/dist/asset-corpus/ProductionTextureLoader.js.map +1 -0
- package/dist/asset-corpus/index.d.ts +10 -0
- package/dist/asset-corpus/index.d.ts.map +1 -0
- package/dist/asset-corpus/index.js +8 -0
- package/dist/asset-corpus/index.js.map +1 -0
- package/dist/browser-index.d.ts +48 -0
- package/dist/browser-index.d.ts.map +1 -0
- package/dist/browser-index.js +26 -0
- package/dist/browser-index.js.map +1 -0
- package/dist/createRenderableScene.d.ts +23 -0
- package/dist/createRenderableScene.d.ts.map +1 -0
- package/dist/createRenderableScene.js +94 -0
- package/dist/createRenderableScene.js.map +1 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/loadRenderableAsset.d.ts +16 -0
- package/dist/loadRenderableAsset.d.ts.map +1 -0
- package/dist/loadRenderableAsset.js +50 -0
- package/dist/loadRenderableAsset.js.map +1 -0
- package/dist/loaders/CubeTextureLoader.d.ts +4 -0
- package/dist/loaders/CubeTextureLoader.d.ts.map +1 -0
- package/dist/loaders/CubeTextureLoader.js +9 -0
- package/dist/loaders/CubeTextureLoader.js.map +1 -0
- package/dist/loaders/EXRLoader.d.ts +4 -0
- package/dist/loaders/EXRLoader.d.ts.map +1 -0
- package/dist/loaders/EXRLoader.js +10 -0
- package/dist/loaders/EXRLoader.js.map +1 -0
- package/dist/loaders/HDRLoader.d.ts +4 -0
- package/dist/loaders/HDRLoader.d.ts.map +1 -0
- package/dist/loaders/HDRLoader.js +7 -0
- package/dist/loaders/HDRLoader.js.map +1 -0
- package/dist/loaders/KTX2Loader.d.ts +4 -0
- package/dist/loaders/KTX2Loader.d.ts.map +1 -0
- package/dist/loaders/KTX2Loader.js +7 -0
- package/dist/loaders/KTX2Loader.js.map +1 -0
- package/dist/loaders/LoaderDiagnostics.d.ts +18 -0
- package/dist/loaders/LoaderDiagnostics.d.ts.map +1 -0
- package/dist/loaders/LoaderDiagnostics.js +18 -0
- package/dist/loaders/LoaderDiagnostics.js.map +1 -0
- package/dist/loaders/MTLLoader.d.ts +10 -0
- package/dist/loaders/MTLLoader.d.ts.map +1 -0
- package/dist/loaders/MTLLoader.js +14 -0
- package/dist/loaders/MTLLoader.js.map +1 -0
- package/dist/loaders/OBJLoader.d.ts +12 -0
- package/dist/loaders/OBJLoader.d.ts.map +1 -0
- package/dist/loaders/OBJLoader.js +16 -0
- package/dist/loaders/OBJLoader.js.map +1 -0
- package/dist/loaders/TextureLoader.d.ts +6 -0
- package/dist/loaders/TextureLoader.d.ts.map +1 -0
- package/dist/loaders/TextureLoader.js +11 -0
- package/dist/loaders/TextureLoader.js.map +1 -0
- package/dist/loaders/ThreeCompatGLTFLoader.d.ts +10 -0
- package/dist/loaders/ThreeCompatGLTFLoader.d.ts.map +1 -0
- package/dist/loaders/ThreeCompatGLTFLoader.js +13 -0
- package/dist/loaders/ThreeCompatGLTFLoader.js.map +1 -0
- package/dist/loaders/index.d.ts +15 -0
- package/dist/loaders/index.d.ts.map +1 -0
- package/dist/loaders/index.js +10 -0
- package/dist/loaders/index.js.map +1 -0
- package/dist/threejs-compatibility/ThreeCompatAssetProvenance.d.ts +34 -0
- package/dist/threejs-compatibility/ThreeCompatAssetProvenance.d.ts.map +1 -0
- package/dist/threejs-compatibility/ThreeCompatAssetProvenance.js +16 -0
- package/dist/threejs-compatibility/ThreeCompatAssetProvenance.js.map +1 -0
- package/dist/threejs-compatibility/ThreeCompatAssetRegistry.d.ts +29 -0
- package/dist/threejs-compatibility/ThreeCompatAssetRegistry.d.ts.map +1 -0
- package/dist/threejs-compatibility/ThreeCompatAssetRegistry.js +33 -0
- package/dist/threejs-compatibility/ThreeCompatAssetRegistry.js.map +1 -0
- package/dist/threejs-example-parity/index.d.ts +20 -0
- package/dist/threejs-example-parity/index.d.ts.map +1 -0
- package/dist/threejs-example-parity/index.js +218 -0
- package/dist/threejs-example-parity/index.js.map +1 -0
- package/package.json +23 -0
- package/src/AdvancedAssetCorpus.ts +454 -0
- package/src/AssetBundleCacheFixtures.ts +290 -0
- package/src/AssetCache.ts +76 -0
- package/src/AssetCompatibility.ts +224 -0
- package/src/AssetCorpus.ts +329 -0
- package/src/AssetDependencyGraph.ts +83 -0
- package/src/AssetHandle.ts +74 -0
- package/src/AssetImportPreflight.ts +179 -0
- package/src/AssetInspection.ts +706 -0
- package/src/AssetLoader.ts +36 -0
- package/src/AssetManager.ts +211 -0
- package/src/AssetRegistry.ts +44 -0
- package/src/AssetRenderDefaults.ts +36 -0
- package/src/AudioLoader.ts +41 -0
- package/src/BlenderExportValidation.ts +227 -0
- package/src/CarConceptMaterialStability.ts +1405 -0
- package/src/EXRLoader.ts +1 -0
- package/src/ExternalParityGLTFCorpus.ts +65 -0
- package/src/GLTFAnimationRuntime.ts +1221 -0
- package/src/GLTFCompressionDecoders.ts +190 -0
- package/src/GLTFExtensionSupport.ts +138 -0
- package/src/GLTFLoader.ts +4001 -0
- package/src/GLTFRenderResources.ts +2105 -0
- package/src/HDRLoader.ts +1 -0
- package/src/ImageLoader.ts +52 -0
- package/src/ImportPipeline.ts +148 -0
- package/src/KTX2BasisTextureTranscoder.ts +224 -0
- package/src/LoadContext.ts +60 -0
- package/src/MaterialLoader.ts +166 -0
- package/src/MeshOptimization.ts +93 -0
- package/src/OBJLoader.ts +551 -0
- package/src/SceneAnalysisFixtures.ts +438 -0
- package/src/SceneLoader.ts +62 -0
- package/src/ShaderLoader.ts +30 -0
- package/src/TextureLoader.ts +30 -0
- package/src/TexturePipeline.ts +96 -0
- package/src/TextureStreaming.ts +20 -0
- package/src/WorkerAssetJobs.ts +56 -0
- package/src/advanced-gallery/index.ts +18 -0
- package/src/asset-corpus/GLTFSceneLoader.ts +9 -0
- package/src/asset-corpus/ProductionAssetCorpus.ts +230 -0
- package/src/asset-corpus/ProductionAssetPipeline.ts +5 -0
- package/src/asset-corpus/ProductionGLTFRenderPipeline.ts +182 -0
- package/src/asset-corpus/ProductionHDRTextureLoader.ts +2 -0
- package/src/asset-corpus/ProductionKTX2TextureLoader.ts +2 -0
- package/src/asset-corpus/ProductionTextureLoader.ts +2 -0
- package/src/asset-corpus/index.ts +29 -0
- package/src/browser-index.ts +224 -0
- package/src/createRenderableScene.ts +128 -0
- package/src/index.ts +317 -0
- package/src/loadRenderableAsset.ts +67 -0
- package/src/loaders/CubeTextureLoader.ts +8 -0
- package/src/loaders/EXRLoader.ts +10 -0
- package/src/loaders/HDRLoader.ts +7 -0
- package/src/loaders/KTX2Loader.ts +7 -0
- package/src/loaders/LoaderDiagnostics.ts +36 -0
- package/src/loaders/MTLLoader.ts +20 -0
- package/src/loaders/OBJLoader.ts +24 -0
- package/src/loaders/TextureLoader.ts +14 -0
- package/src/loaders/ThreeCompatGLTFLoader.ts +19 -0
- package/src/loaders/index.ts +14 -0
- package/src/threejs-compatibility/ThreeCompatAssetProvenance.ts +51 -0
- package/src/threejs-compatibility/ThreeCompatAssetRegistry.ts +61 -0
- package/src/threejs-example-parity/index.ts +262 -0
- package/tests/assets.test.ts +311 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,1221 @@
|
|
|
1
|
+
import { AnimationAction, AnimationClip, AnimationMixer, normalizeQuat, slerpQuat, solveTwoBoneIk, type AnimationEvent, type AnimationMixerOptions, type AnimationValue, type LoopMode, type TrackValueType, type TwoBoneIkResult } from "@aura3d/animation";
|
|
2
|
+
import { invertMat4, multiplyMat4, Renderable, Scene, transformPoint, type Mat4, type SceneNode, type Vec3 } from "@aura3d/scene";
|
|
3
|
+
import type { GLTFAsset, GLTFMeshAsset, GLTFSkinAsset } from "./GLTFLoader";
|
|
4
|
+
|
|
5
|
+
export interface GLTFSceneAnimationRuntimeOptions {
|
|
6
|
+
readonly scene: Scene;
|
|
7
|
+
readonly clips: readonly AnimationClip[];
|
|
8
|
+
readonly asset?: Pick<GLTFAsset, "meshes" | "skins">;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface GLTFSceneAnimationClipSample {
|
|
12
|
+
readonly clipName: string;
|
|
13
|
+
readonly time: number;
|
|
14
|
+
readonly weight?: number;
|
|
15
|
+
readonly additive?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface GLTFSceneAnimationApplyResult {
|
|
19
|
+
readonly clipName: string;
|
|
20
|
+
readonly time: number;
|
|
21
|
+
readonly blendedClipCount?: number;
|
|
22
|
+
readonly tracksApplied: number;
|
|
23
|
+
readonly transformTracksApplied: number;
|
|
24
|
+
readonly morphWeightTracksApplied: number;
|
|
25
|
+
readonly skinningPalettesUpdated: number;
|
|
26
|
+
readonly missingTargets: readonly string[];
|
|
27
|
+
readonly unsupportedTracks: readonly string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface GLTFSceneAnimationRuntimeSnapshot {
|
|
31
|
+
readonly clipCount: number;
|
|
32
|
+
readonly nodeTargetCount: number;
|
|
33
|
+
readonly morphTargetNodeCount: number;
|
|
34
|
+
readonly skinningBindingCount: number;
|
|
35
|
+
readonly clips: readonly string[];
|
|
36
|
+
readonly lastApply?: GLTFSceneAnimationApplyResult;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface GLTFSceneAnimationClipBindingDiagnostics {
|
|
40
|
+
readonly clipName: string;
|
|
41
|
+
readonly trackCount: number;
|
|
42
|
+
readonly supportedTrackCount: number;
|
|
43
|
+
readonly boundTrackCount: number;
|
|
44
|
+
readonly transformTrackCount: number;
|
|
45
|
+
readonly morphWeightTrackCount: number;
|
|
46
|
+
readonly missingTargetCount: number;
|
|
47
|
+
readonly unsupportedTrackCount: number;
|
|
48
|
+
readonly skinningBindingCount: number;
|
|
49
|
+
readonly boundNodeNames: readonly string[];
|
|
50
|
+
readonly missingTargets: readonly string[];
|
|
51
|
+
readonly unsupportedTracks: readonly string[];
|
|
52
|
+
readonly animatesSkeleton: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface GLTFSceneAnimationMixerOptions extends GLTFSceneAnimationRuntimeOptions {
|
|
56
|
+
readonly autoPlay?: string | false;
|
|
57
|
+
readonly mixer?: AnimationMixerOptions;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface GLTFSceneAnimationMixerUpdateResult {
|
|
61
|
+
readonly events: readonly AnimationEvent[];
|
|
62
|
+
readonly applyResult: GLTFSceneAnimationApplyResult;
|
|
63
|
+
readonly activeActions: readonly GLTFSceneAnimationActionSnapshot[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface GLTFSceneAnimationMixerSnapshot extends GLTFSceneAnimationRuntimeSnapshot {
|
|
67
|
+
readonly mixerActionCount: number;
|
|
68
|
+
readonly pendingValueCount: number;
|
|
69
|
+
readonly elapsedTime: number;
|
|
70
|
+
readonly timeScale: number;
|
|
71
|
+
readonly activeClipNames: readonly string[];
|
|
72
|
+
readonly actions: readonly GLTFSceneAnimationActionSnapshot[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface GLTFSceneAnimationPlayOptions {
|
|
76
|
+
readonly weight?: number;
|
|
77
|
+
readonly timeScale?: number;
|
|
78
|
+
readonly loopMode?: LoopMode;
|
|
79
|
+
readonly reset?: boolean;
|
|
80
|
+
readonly fadeDuration?: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface GLTFSceneAnimationActionSnapshot {
|
|
84
|
+
readonly clipName: string;
|
|
85
|
+
readonly duration: number;
|
|
86
|
+
readonly time: number;
|
|
87
|
+
readonly weight: number;
|
|
88
|
+
readonly timeScale: number;
|
|
89
|
+
readonly playing: boolean;
|
|
90
|
+
readonly paused: boolean;
|
|
91
|
+
readonly loopMode: LoopMode;
|
|
92
|
+
readonly active: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface GLTFImportedSkeletonIKOptions {
|
|
96
|
+
readonly skinName?: string;
|
|
97
|
+
readonly jointNames?: readonly [string, string, string];
|
|
98
|
+
readonly target: readonly [number, number, number];
|
|
99
|
+
readonly pole?: readonly [number, number, number];
|
|
100
|
+
readonly weight?: number;
|
|
101
|
+
readonly allowStretch?: boolean;
|
|
102
|
+
readonly apply?: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface GLTFImportedSkeletonIKResult {
|
|
106
|
+
readonly skinName: string;
|
|
107
|
+
readonly jointNames: readonly [string, string, string];
|
|
108
|
+
readonly solution: TwoBoneIkResult;
|
|
109
|
+
readonly applied: boolean;
|
|
110
|
+
readonly skinningPalettesUpdated: number;
|
|
111
|
+
readonly missingTargets: readonly string[];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface GLTFImportedSkeletonIKControllerOptions extends Omit<GLTFImportedSkeletonIKOptions, "target"> {
|
|
115
|
+
readonly target: readonly [number, number, number];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface GLTFImportedSkeletonIKControllerSnapshot {
|
|
119
|
+
readonly target: readonly [number, number, number];
|
|
120
|
+
readonly pole?: readonly [number, number, number];
|
|
121
|
+
readonly weight?: number;
|
|
122
|
+
readonly allowStretch?: boolean;
|
|
123
|
+
readonly apply: boolean;
|
|
124
|
+
readonly skinName?: string;
|
|
125
|
+
readonly jointNames?: readonly [string, string, string];
|
|
126
|
+
readonly lastResult?: GLTFImportedSkeletonIKResult;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface GLTFSceneAnimationCloneSample {
|
|
130
|
+
readonly cloneId: string;
|
|
131
|
+
readonly clipName: string;
|
|
132
|
+
readonly time: number;
|
|
133
|
+
readonly weight?: number;
|
|
134
|
+
readonly additive?: boolean;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface GLTFSceneAnimationCloneSampleResult {
|
|
138
|
+
readonly cloneId: string;
|
|
139
|
+
readonly clipName: string;
|
|
140
|
+
readonly time: number;
|
|
141
|
+
readonly applyResult: GLTFSceneAnimationApplyResult;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface GLTFSceneAnimationCloneSamplerSnapshot {
|
|
145
|
+
readonly cloneCount: number;
|
|
146
|
+
readonly lastSampleCount: number;
|
|
147
|
+
readonly lastSkinningPalettesUpdated: number;
|
|
148
|
+
readonly lastResults: readonly GLTFSceneAnimationCloneSampleResult[];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface GLTFSceneMorphTargetControllerOptions {
|
|
152
|
+
readonly target: string;
|
|
153
|
+
readonly labels?: readonly string[];
|
|
154
|
+
readonly initialWeights?: readonly number[];
|
|
155
|
+
readonly clamp?: boolean;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface GLTFSceneMorphTargetControllerSnapshot {
|
|
159
|
+
readonly target: string;
|
|
160
|
+
readonly labels: readonly string[];
|
|
161
|
+
readonly weights: readonly number[];
|
|
162
|
+
readonly lastApply?: GLTFSceneAnimationApplyResult;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export class GLTFSceneMorphTargetController {
|
|
166
|
+
private readonly labels: readonly string[];
|
|
167
|
+
private readonly labelToIndex = new Map<string, number>();
|
|
168
|
+
private readonly clampWeights: boolean;
|
|
169
|
+
private weights: number[];
|
|
170
|
+
private lastApply: GLTFSceneAnimationApplyResult | undefined;
|
|
171
|
+
|
|
172
|
+
constructor(
|
|
173
|
+
private readonly runtime: GLTFSceneAnimationRuntime,
|
|
174
|
+
private readonly options: GLTFSceneMorphTargetControllerOptions
|
|
175
|
+
) {
|
|
176
|
+
if (options.target.trim().length === 0) {
|
|
177
|
+
throw new Error("glTF morph target controller target cannot be empty.");
|
|
178
|
+
}
|
|
179
|
+
this.labels = [...(options.labels ?? [])];
|
|
180
|
+
this.labels.forEach((label, index) => {
|
|
181
|
+
if (label.trim().length === 0) {
|
|
182
|
+
throw new Error("glTF morph target controller labels cannot be empty.");
|
|
183
|
+
}
|
|
184
|
+
this.labelToIndex.set(label, index);
|
|
185
|
+
});
|
|
186
|
+
this.clampWeights = options.clamp ?? true;
|
|
187
|
+
this.weights = [...(options.initialWeights ?? new Array(Math.max(1, this.labels.length)).fill(0))].map((weight) => this.normalizeWeight(weight));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
setWeight(indexOrLabel: number | string, weight: number): this {
|
|
191
|
+
const index = this.resolveIndex(indexOrLabel);
|
|
192
|
+
this.ensureWeightIndex(index);
|
|
193
|
+
this.weights[index] = this.normalizeWeight(weight);
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
setWeights(weights: readonly number[]): this {
|
|
198
|
+
if (weights.length === 0) {
|
|
199
|
+
throw new Error("glTF morph target controller requires at least one morph weight.");
|
|
200
|
+
}
|
|
201
|
+
this.weights = weights.map((weight) => this.normalizeWeight(weight));
|
|
202
|
+
return this;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
getWeights(): readonly number[] {
|
|
206
|
+
return [...this.weights];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
apply(time: number, label = `morph:${this.options.target}`): GLTFSceneAnimationApplyResult {
|
|
210
|
+
this.lastApply = this.runtime.applyAnimationValues(label, time, new Map([[this.options.target, [...this.weights]]]));
|
|
211
|
+
return this.lastApply;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
snapshot(): GLTFSceneMorphTargetControllerSnapshot {
|
|
215
|
+
return {
|
|
216
|
+
target: this.options.target,
|
|
217
|
+
labels: [...this.labels],
|
|
218
|
+
weights: [...this.weights],
|
|
219
|
+
...(this.lastApply === undefined ? {} : { lastApply: this.lastApply })
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private resolveIndex(indexOrLabel: number | string): number {
|
|
224
|
+
if (typeof indexOrLabel === "number") {
|
|
225
|
+
if (!Number.isInteger(indexOrLabel) || indexOrLabel < 0) {
|
|
226
|
+
throw new Error("glTF morph target controller index must be a non-negative integer.");
|
|
227
|
+
}
|
|
228
|
+
return indexOrLabel;
|
|
229
|
+
}
|
|
230
|
+
const index = this.labelToIndex.get(indexOrLabel);
|
|
231
|
+
if (index === undefined) {
|
|
232
|
+
throw new Error(`glTF morph target label "${indexOrLabel}" was not found.`);
|
|
233
|
+
}
|
|
234
|
+
return index;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private ensureWeightIndex(index: number): void {
|
|
238
|
+
while (this.weights.length <= index) {
|
|
239
|
+
this.weights.push(0);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private normalizeWeight(weight: number): number {
|
|
244
|
+
if (!Number.isFinite(weight)) {
|
|
245
|
+
throw new Error("glTF morph target controller weights must be finite.");
|
|
246
|
+
}
|
|
247
|
+
if (!this.clampWeights) return weight;
|
|
248
|
+
return Math.min(1, Math.max(0, weight));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export class GLTFSceneAnimationCloneSampler {
|
|
253
|
+
private lastResults: GLTFSceneAnimationCloneSampleResult[] = [];
|
|
254
|
+
|
|
255
|
+
constructor(private readonly runtime: GLTFSceneAnimationRuntime) {}
|
|
256
|
+
|
|
257
|
+
sampleClones(
|
|
258
|
+
samples: readonly GLTFSceneAnimationCloneSample[],
|
|
259
|
+
onSample?: (result: GLTFSceneAnimationCloneSampleResult) => void
|
|
260
|
+
): readonly GLTFSceneAnimationCloneSampleResult[] {
|
|
261
|
+
const results: GLTFSceneAnimationCloneSampleResult[] = [];
|
|
262
|
+
for (const sample of samples) {
|
|
263
|
+
const applyResult = sample.weight === undefined && sample.additive !== true
|
|
264
|
+
? this.runtime.applyClipByName(sample.clipName, sample.time)
|
|
265
|
+
: this.runtime.applyClips([{
|
|
266
|
+
clipName: sample.clipName,
|
|
267
|
+
time: sample.time,
|
|
268
|
+
...(sample.weight === undefined ? {} : { weight: sample.weight }),
|
|
269
|
+
...(sample.additive === undefined ? {} : { additive: sample.additive })
|
|
270
|
+
}]);
|
|
271
|
+
const result: GLTFSceneAnimationCloneSampleResult = {
|
|
272
|
+
cloneId: sample.cloneId,
|
|
273
|
+
clipName: sample.clipName,
|
|
274
|
+
time: applyResult.time,
|
|
275
|
+
applyResult
|
|
276
|
+
};
|
|
277
|
+
results.push(result);
|
|
278
|
+
onSample?.(result);
|
|
279
|
+
}
|
|
280
|
+
this.lastResults = results;
|
|
281
|
+
return results;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
snapshot(): GLTFSceneAnimationCloneSamplerSnapshot {
|
|
285
|
+
return {
|
|
286
|
+
cloneCount: new Set(this.lastResults.map((result) => result.cloneId)).size,
|
|
287
|
+
lastSampleCount: this.lastResults.length,
|
|
288
|
+
lastSkinningPalettesUpdated: this.lastResults.reduce((sum, result) => sum + result.applyResult.skinningPalettesUpdated, 0),
|
|
289
|
+
lastResults: this.lastResults.map((result) => ({ ...result }))
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export class GLTFImportedSkeletonIKController {
|
|
295
|
+
private target: readonly [number, number, number];
|
|
296
|
+
private pole: readonly [number, number, number] | undefined;
|
|
297
|
+
private weight: number | undefined;
|
|
298
|
+
private allowStretch: boolean | undefined;
|
|
299
|
+
private apply: boolean;
|
|
300
|
+
private lastResult: GLTFImportedSkeletonIKResult | undefined;
|
|
301
|
+
|
|
302
|
+
constructor(
|
|
303
|
+
private readonly runtime: GLTFSceneAnimationRuntime,
|
|
304
|
+
private readonly options: Omit<GLTFImportedSkeletonIKControllerOptions, "target" | "pole" | "weight" | "allowStretch" | "apply">
|
|
305
|
+
& Pick<GLTFImportedSkeletonIKControllerOptions, "skinName" | "jointNames">,
|
|
306
|
+
initial: Pick<GLTFImportedSkeletonIKControllerOptions, "target" | "pole" | "weight" | "allowStretch" | "apply">
|
|
307
|
+
) {
|
|
308
|
+
this.target = initial.target;
|
|
309
|
+
this.pole = initial.pole;
|
|
310
|
+
this.weight = initial.weight;
|
|
311
|
+
this.allowStretch = initial.allowStretch;
|
|
312
|
+
this.apply = initial.apply ?? true;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
setTarget(target: readonly [number, number, number]): this {
|
|
316
|
+
this.target = target;
|
|
317
|
+
return this;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
setPole(pole: readonly [number, number, number] | undefined): this {
|
|
321
|
+
this.pole = pole;
|
|
322
|
+
return this;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
setWeight(weight: number | undefined): this {
|
|
326
|
+
if (weight !== undefined && (!Number.isFinite(weight) || weight < 0 || weight > 1)) {
|
|
327
|
+
throw new Error("glTF imported skeleton IK controller weight must be between 0 and 1.");
|
|
328
|
+
}
|
|
329
|
+
this.weight = weight;
|
|
330
|
+
return this;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
setAllowStretch(allowStretch: boolean | undefined): this {
|
|
334
|
+
this.allowStretch = allowStretch;
|
|
335
|
+
return this;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
setApply(apply: boolean): this {
|
|
339
|
+
this.apply = apply;
|
|
340
|
+
return this;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
solve(patch: Partial<Pick<GLTFImportedSkeletonIKControllerOptions, "target" | "pole" | "weight" | "allowStretch" | "apply">> = {}): GLTFImportedSkeletonIKResult {
|
|
344
|
+
if (patch.target !== undefined) this.setTarget(patch.target);
|
|
345
|
+
if ("pole" in patch) this.setPole(patch.pole);
|
|
346
|
+
if ("weight" in patch) this.setWeight(patch.weight);
|
|
347
|
+
if ("allowStretch" in patch) this.setAllowStretch(patch.allowStretch);
|
|
348
|
+
if (patch.apply !== undefined) this.setApply(patch.apply);
|
|
349
|
+
this.lastResult = this.runtime.solveImportedSkeletonTwoBoneIK({
|
|
350
|
+
...this.options,
|
|
351
|
+
target: this.target,
|
|
352
|
+
...(this.pole === undefined ? {} : { pole: this.pole }),
|
|
353
|
+
...(this.weight === undefined ? {} : { weight: this.weight }),
|
|
354
|
+
...(this.allowStretch === undefined ? {} : { allowStretch: this.allowStretch }),
|
|
355
|
+
apply: this.apply
|
|
356
|
+
});
|
|
357
|
+
return this.lastResult;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
snapshot(): GLTFImportedSkeletonIKControllerSnapshot {
|
|
361
|
+
return {
|
|
362
|
+
target: this.target,
|
|
363
|
+
...(this.pole === undefined ? {} : { pole: this.pole }),
|
|
364
|
+
...(this.weight === undefined ? {} : { weight: this.weight }),
|
|
365
|
+
...(this.allowStretch === undefined ? {} : { allowStretch: this.allowStretch }),
|
|
366
|
+
apply: this.apply,
|
|
367
|
+
...(this.options.skinName === undefined ? {} : { skinName: this.options.skinName }),
|
|
368
|
+
...(this.options.jointNames === undefined ? {} : { jointNames: this.options.jointNames }),
|
|
369
|
+
...(this.lastResult === undefined ? {} : { lastResult: this.lastResult })
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
interface RuntimeSkinningBinding {
|
|
375
|
+
readonly node: SceneNode;
|
|
376
|
+
readonly renderable: Renderable;
|
|
377
|
+
readonly mesh: GLTFMeshAsset;
|
|
378
|
+
readonly skin: GLTFSkinAsset;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
type WeightedAccumulator = { value: AnimationValue; weight: number; type: TrackValueType };
|
|
382
|
+
type TargetAccumulator = { type: TrackValueType; base?: WeightedAccumulator; additive?: AnimationValue };
|
|
383
|
+
|
|
384
|
+
export class GLTFSceneAnimationRuntime {
|
|
385
|
+
private readonly clipsByName = new Map<string, AnimationClip>();
|
|
386
|
+
private readonly nodesByName = new Map<string, SceneNode[]>();
|
|
387
|
+
private readonly morphRenderablesByNodeName = new Map<string, Renderable[]>();
|
|
388
|
+
private readonly skinningBindings: RuntimeSkinningBinding[] = [];
|
|
389
|
+
private lastApply?: GLTFSceneAnimationApplyResult;
|
|
390
|
+
|
|
391
|
+
constructor(private readonly options: GLTFSceneAnimationRuntimeOptions) {
|
|
392
|
+
for (const clip of options.clips) {
|
|
393
|
+
this.clipsByName.set(clip.name, clip);
|
|
394
|
+
}
|
|
395
|
+
this.reindexScene();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
applyClipByName(name: string, time: number): GLTFSceneAnimationApplyResult {
|
|
399
|
+
const clip = this.clipsByName.get(name);
|
|
400
|
+
if (!clip) {
|
|
401
|
+
throw new Error(`glTF animation clip "${name}" was not found.`);
|
|
402
|
+
}
|
|
403
|
+
return this.applyClip(clip, time);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
applyClip(clip: AnimationClip, time: number): GLTFSceneAnimationApplyResult {
|
|
407
|
+
if (!Number.isFinite(time) || time < 0) {
|
|
408
|
+
throw new Error("glTF animation runtime time must be finite and non-negative.");
|
|
409
|
+
}
|
|
410
|
+
const wrappedTime = clip.duration > 0 && time > clip.duration ? time % clip.duration : Math.min(time, clip.duration);
|
|
411
|
+
this.lastApply = this.applySampledTargets(clip.name, wrappedTime, sampleClipTracks(clip, wrappedTime, 1, false));
|
|
412
|
+
return this.lastApply;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
applyClips(samples: readonly GLTFSceneAnimationClipSample[]): GLTFSceneAnimationApplyResult {
|
|
416
|
+
if (samples.length === 0) {
|
|
417
|
+
throw new Error("glTF animation runtime blend requires at least one clip sample.");
|
|
418
|
+
}
|
|
419
|
+
const accumulators = new Map<string, TargetAccumulator>();
|
|
420
|
+
const unsupportedTracks: string[] = [];
|
|
421
|
+
let maxTime = 0;
|
|
422
|
+
const names: string[] = [];
|
|
423
|
+
|
|
424
|
+
for (const sample of samples) {
|
|
425
|
+
const clip = this.clipsByName.get(sample.clipName);
|
|
426
|
+
if (!clip) {
|
|
427
|
+
throw new Error(`glTF animation clip "${sample.clipName}" was not found.`);
|
|
428
|
+
}
|
|
429
|
+
if (!Number.isFinite(sample.time) || sample.time < 0) {
|
|
430
|
+
throw new Error("glTF animation runtime blend time must be finite and non-negative.");
|
|
431
|
+
}
|
|
432
|
+
const weight = sample.weight ?? 1;
|
|
433
|
+
if (!Number.isFinite(weight) || weight < 0) {
|
|
434
|
+
throw new Error("glTF animation runtime blend weight must be finite and non-negative.");
|
|
435
|
+
}
|
|
436
|
+
if (weight === 0) continue;
|
|
437
|
+
const wrappedTime = clip.duration > 0 && sample.time > clip.duration ? sample.time % clip.duration : Math.min(sample.time, clip.duration);
|
|
438
|
+
maxTime = Math.max(maxTime, wrappedTime);
|
|
439
|
+
names.push(`${clip.name}@${Number(wrappedTime.toFixed(4))}x${Number(weight.toFixed(4))}${sample.additive ? "+add" : ""}`);
|
|
440
|
+
for (const track of clip.tracks) {
|
|
441
|
+
const target = parseAnimationTarget(track.target);
|
|
442
|
+
if (!target) {
|
|
443
|
+
unsupportedTracks.push(track.target);
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
blendInto(accumulators, track.target, track.valueType, track.sample(wrappedTime), weight, sample.additive === true);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const sampledTargets = new Map<string, AnimationValue>();
|
|
451
|
+
for (const [target, accumulator] of accumulators) {
|
|
452
|
+
sampledTargets.set(target, finalizeTargetBlend(accumulator));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
this.lastApply = this.applySampledTargets(
|
|
456
|
+
`blend:${names.join(",")}`,
|
|
457
|
+
maxTime,
|
|
458
|
+
{ sampledTargets, unsupportedTracks },
|
|
459
|
+
samples.length
|
|
460
|
+
);
|
|
461
|
+
return this.lastApply;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
applyAnimationValues(
|
|
465
|
+
label: string,
|
|
466
|
+
time: number,
|
|
467
|
+
values: ReadonlyMap<string, AnimationValue>,
|
|
468
|
+
unsupportedTracks: readonly string[] = []
|
|
469
|
+
): GLTFSceneAnimationApplyResult {
|
|
470
|
+
if (label.trim().length === 0) {
|
|
471
|
+
throw new Error("glTF animation runtime value application label cannot be empty.");
|
|
472
|
+
}
|
|
473
|
+
if (!Number.isFinite(time) || time < 0) {
|
|
474
|
+
throw new Error("glTF animation runtime value application time must be finite and non-negative.");
|
|
475
|
+
}
|
|
476
|
+
this.lastApply = this.applySampledTargets(label, time, {
|
|
477
|
+
sampledTargets: values,
|
|
478
|
+
unsupportedTracks
|
|
479
|
+
});
|
|
480
|
+
return this.lastApply;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
solveImportedSkeletonTwoBoneIK(options: GLTFImportedSkeletonIKOptions): GLTFImportedSkeletonIKResult {
|
|
484
|
+
if (!this.options.asset || this.options.asset.skins.length === 0) {
|
|
485
|
+
throw new Error("glTF imported skeleton IK requires an asset with at least one skin.");
|
|
486
|
+
}
|
|
487
|
+
const skin = resolveIKSkin(this.options.asset.skins, options.skinName);
|
|
488
|
+
const jointNames = options.jointNames ?? firstTwoBoneJointChain(skin);
|
|
489
|
+
const [rootName, midName, endName] = jointNames;
|
|
490
|
+
this.options.scene.updateWorldTransforms();
|
|
491
|
+
const rootNode = this.nodesByName.get(rootName)?.[0];
|
|
492
|
+
const midNode = this.nodesByName.get(midName)?.[0];
|
|
493
|
+
const endNode = this.nodesByName.get(endName)?.[0];
|
|
494
|
+
const missingTargets = [
|
|
495
|
+
...(!rootNode ? [`${skin.name}.${rootName}`] : []),
|
|
496
|
+
...(!midNode ? [`${skin.name}.${midName}`] : []),
|
|
497
|
+
...(!endNode ? [`${skin.name}.${endName}`] : [])
|
|
498
|
+
];
|
|
499
|
+
if (!rootNode || !midNode || !endNode) {
|
|
500
|
+
return {
|
|
501
|
+
skinName: skin.name,
|
|
502
|
+
jointNames,
|
|
503
|
+
solution: solveTwoBoneIk({
|
|
504
|
+
root: [0, 0, 0],
|
|
505
|
+
mid: [0, 1, 0],
|
|
506
|
+
end: [0, 2, 0],
|
|
507
|
+
target: [0, 2, 0.01],
|
|
508
|
+
allowStretch: true
|
|
509
|
+
}),
|
|
510
|
+
applied: false,
|
|
511
|
+
skinningPalettesUpdated: 0,
|
|
512
|
+
missingTargets
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const solution = solveTwoBoneIk({
|
|
517
|
+
root: worldPosition(rootNode),
|
|
518
|
+
mid: worldPosition(midNode),
|
|
519
|
+
end: worldPosition(endNode),
|
|
520
|
+
target: options.target,
|
|
521
|
+
...(options.pole ? { pole: options.pole } : {}),
|
|
522
|
+
...(options.weight !== undefined ? { weight: options.weight } : {}),
|
|
523
|
+
...(options.allowStretch !== undefined ? { allowStretch: options.allowStretch } : {})
|
|
524
|
+
});
|
|
525
|
+
if (options.apply === false) {
|
|
526
|
+
return {
|
|
527
|
+
skinName: skin.name,
|
|
528
|
+
jointNames,
|
|
529
|
+
solution,
|
|
530
|
+
applied: false,
|
|
531
|
+
skinningPalettesUpdated: 0,
|
|
532
|
+
missingTargets: []
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
setWorldPosition(midNode, solution.mid);
|
|
537
|
+
this.options.scene.updateWorldTransforms();
|
|
538
|
+
setWorldPosition(endNode, solution.end);
|
|
539
|
+
this.options.scene.updateWorldTransforms();
|
|
540
|
+
const skinning = this.refreshSkinningPalettes();
|
|
541
|
+
return {
|
|
542
|
+
skinName: skin.name,
|
|
543
|
+
jointNames,
|
|
544
|
+
solution,
|
|
545
|
+
applied: true,
|
|
546
|
+
skinningPalettesUpdated: skinning.updated,
|
|
547
|
+
missingTargets: skinning.missingTargets
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
createTwoBoneIKController(options: GLTFImportedSkeletonIKControllerOptions): GLTFImportedSkeletonIKController {
|
|
552
|
+
return new GLTFImportedSkeletonIKController(this, {
|
|
553
|
+
...(options.skinName === undefined ? {} : { skinName: options.skinName }),
|
|
554
|
+
...(options.jointNames === undefined ? {} : { jointNames: options.jointNames })
|
|
555
|
+
}, {
|
|
556
|
+
target: options.target,
|
|
557
|
+
...(options.pole === undefined ? {} : { pole: options.pole }),
|
|
558
|
+
...(options.weight === undefined ? {} : { weight: options.weight }),
|
|
559
|
+
...(options.allowStretch === undefined ? {} : { allowStretch: options.allowStretch }),
|
|
560
|
+
...(options.apply === undefined ? {} : { apply: options.apply })
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
createCloneSampler(): GLTFSceneAnimationCloneSampler {
|
|
565
|
+
return new GLTFSceneAnimationCloneSampler(this);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
createMorphTargetController(options: GLTFSceneMorphTargetControllerOptions): GLTFSceneMorphTargetController {
|
|
569
|
+
return new GLTFSceneMorphTargetController(this, options);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
snapshot(): GLTFSceneAnimationRuntimeSnapshot {
|
|
573
|
+
return {
|
|
574
|
+
clipCount: this.clipsByName.size,
|
|
575
|
+
nodeTargetCount: this.nodesByName.size,
|
|
576
|
+
morphTargetNodeCount: this.morphRenderablesByNodeName.size,
|
|
577
|
+
skinningBindingCount: this.skinningBindings.length,
|
|
578
|
+
clips: [...this.clipsByName.keys()],
|
|
579
|
+
...(this.lastApply ? { lastApply: this.lastApply } : {})
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
inspectClipBindings(name?: string): readonly GLTFSceneAnimationClipBindingDiagnostics[] {
|
|
584
|
+
if (name !== undefined) {
|
|
585
|
+
const clip = this.clipsByName.get(name);
|
|
586
|
+
if (!clip) {
|
|
587
|
+
throw new Error(`glTF animation clip "${name}" was not found.`);
|
|
588
|
+
}
|
|
589
|
+
return [this.inspectClipBinding(clip)];
|
|
590
|
+
}
|
|
591
|
+
return [...this.clipsByName.values()].map((clip) => this.inspectClipBinding(clip));
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
reindexScene(): void {
|
|
595
|
+
this.nodesByName.clear();
|
|
596
|
+
this.morphRenderablesByNodeName.clear();
|
|
597
|
+
this.skinningBindings.length = 0;
|
|
598
|
+
this.options.scene.traverse((node) => {
|
|
599
|
+
const nodes = this.nodesByName.get(node.name) ?? [];
|
|
600
|
+
nodes.push(node);
|
|
601
|
+
this.nodesByName.set(node.name, nodes);
|
|
602
|
+
});
|
|
603
|
+
for (const { node, renderable } of this.options.scene.collectRenderables()) {
|
|
604
|
+
if (renderable.morphWeights.length === 0) continue;
|
|
605
|
+
for (const nodeName of new Set([node.name, node.parent?.name].filter((name): name is string => typeof name === "string" && name.length > 0))) {
|
|
606
|
+
const renderables = this.morphRenderablesByNodeName.get(nodeName) ?? [];
|
|
607
|
+
renderables.push(renderable);
|
|
608
|
+
this.morphRenderablesByNodeName.set(nodeName, renderables);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (this.options.asset) {
|
|
612
|
+
const meshesByName = new Map(this.options.asset.meshes.map((mesh) => [mesh.name, mesh]));
|
|
613
|
+
for (const { node, renderable } of this.options.scene.collectRenderables()) {
|
|
614
|
+
if (!renderable.skinning) continue;
|
|
615
|
+
const mesh = meshesByName.get(renderable.geometry);
|
|
616
|
+
const skin = mesh?.skinIndex === undefined ? undefined : this.options.asset.skins[mesh.skinIndex];
|
|
617
|
+
if (!mesh || !skin || skin.joints.length > 96) continue;
|
|
618
|
+
this.skinningBindings.push({ node, renderable, mesh, skin });
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
private refreshSkinningPalettes(): { readonly updated: number; readonly missingTargets: readonly string[] } {
|
|
624
|
+
if (this.skinningBindings.length === 0) {
|
|
625
|
+
return { updated: 0, missingTargets: [] };
|
|
626
|
+
}
|
|
627
|
+
let updated = 0;
|
|
628
|
+
const missingTargets: string[] = [];
|
|
629
|
+
for (const binding of this.skinningBindings) {
|
|
630
|
+
const matrices = new Float32Array(binding.skin.joints.length * 16);
|
|
631
|
+
const inverseMeshWorld = invertMat4(binding.node.transform.worldMatrix);
|
|
632
|
+
let complete = true;
|
|
633
|
+
for (let index = 0; index < binding.skin.jointNames.length; index += 1) {
|
|
634
|
+
const jointName = binding.skin.jointNames[index]!;
|
|
635
|
+
const jointNode = this.nodesByName.get(jointName)?.[0];
|
|
636
|
+
const inverseBind = binding.skin.inverseBindMatrices[index];
|
|
637
|
+
if (!jointNode || !inverseBind) {
|
|
638
|
+
missingTargets.push(`${binding.skin.name}.${jointName}`);
|
|
639
|
+
complete = false;
|
|
640
|
+
break;
|
|
641
|
+
}
|
|
642
|
+
const jointMatrix = multiplyMat4(multiplyMat4(inverseMeshWorld, jointNode.transform.worldMatrix), inverseBind as Mat4);
|
|
643
|
+
matrices.set(jointMatrix, index * 16);
|
|
644
|
+
}
|
|
645
|
+
if (!complete) continue;
|
|
646
|
+
binding.renderable.skinning = {
|
|
647
|
+
jointCount: binding.skin.joints.length,
|
|
648
|
+
matrices
|
|
649
|
+
};
|
|
650
|
+
updated += 1;
|
|
651
|
+
}
|
|
652
|
+
return { updated, missingTargets };
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
private inspectClipBinding(clip: AnimationClip): GLTFSceneAnimationClipBindingDiagnostics {
|
|
656
|
+
let supportedTrackCount = 0;
|
|
657
|
+
let boundTrackCount = 0;
|
|
658
|
+
let transformTrackCount = 0;
|
|
659
|
+
let morphWeightTrackCount = 0;
|
|
660
|
+
const boundNodeNames = new Set<string>();
|
|
661
|
+
const missingTargets = new Set<string>();
|
|
662
|
+
const unsupportedTracks = new Set<string>();
|
|
663
|
+
|
|
664
|
+
for (const track of clip.tracks) {
|
|
665
|
+
const target = parseAnimationTarget(track.target);
|
|
666
|
+
if (!target) {
|
|
667
|
+
unsupportedTracks.add(track.target);
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
supportedTrackCount += 1;
|
|
671
|
+
if (target.path === "weights") {
|
|
672
|
+
morphWeightTrackCount += 1;
|
|
673
|
+
const renderables = this.morphRenderablesByNodeName.get(target.nodeName) ?? [];
|
|
674
|
+
if (renderables.length === 0) {
|
|
675
|
+
missingTargets.add(track.target);
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
boundTrackCount += 1;
|
|
679
|
+
boundNodeNames.add(target.nodeName);
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
transformTrackCount += 1;
|
|
684
|
+
const nodes = this.nodesByName.get(target.nodeName) ?? [];
|
|
685
|
+
if (nodes.length === 0) {
|
|
686
|
+
missingTargets.add(track.target);
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
boundTrackCount += 1;
|
|
690
|
+
boundNodeNames.add(target.nodeName);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return {
|
|
694
|
+
clipName: clip.name,
|
|
695
|
+
trackCount: clip.tracks.length,
|
|
696
|
+
supportedTrackCount,
|
|
697
|
+
boundTrackCount,
|
|
698
|
+
transformTrackCount,
|
|
699
|
+
morphWeightTrackCount,
|
|
700
|
+
missingTargetCount: missingTargets.size,
|
|
701
|
+
unsupportedTrackCount: unsupportedTracks.size,
|
|
702
|
+
skinningBindingCount: this.skinningBindings.length,
|
|
703
|
+
boundNodeNames: [...boundNodeNames].sort(),
|
|
704
|
+
missingTargets: [...missingTargets].sort(),
|
|
705
|
+
unsupportedTracks: [...unsupportedTracks].sort(),
|
|
706
|
+
animatesSkeleton: this.skinningBindings.length > 0 && boundTrackCount > 0 && transformTrackCount > 0
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
private applySampledTargets(
|
|
711
|
+
clipName: string,
|
|
712
|
+
time: number,
|
|
713
|
+
sampled: { readonly sampledTargets: ReadonlyMap<string, AnimationValue>; readonly unsupportedTracks: readonly string[] },
|
|
714
|
+
blendedClipCount?: number
|
|
715
|
+
): GLTFSceneAnimationApplyResult {
|
|
716
|
+
let transformTracksApplied = 0;
|
|
717
|
+
let morphWeightTracksApplied = 0;
|
|
718
|
+
const missingTargets: string[] = [];
|
|
719
|
+
|
|
720
|
+
for (const [trackTarget, value] of sampled.sampledTargets) {
|
|
721
|
+
const target = parseAnimationTarget(trackTarget);
|
|
722
|
+
if (!target) {
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
if (target.path === "weights") {
|
|
726
|
+
const renderables = this.morphRenderablesByNodeName.get(target.nodeName) ?? [];
|
|
727
|
+
if (renderables.length === 0) {
|
|
728
|
+
missingTargets.push(trackTarget);
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
const weights = asNumberArray(value);
|
|
732
|
+
for (const renderable of renderables) {
|
|
733
|
+
renderable.morphWeights = weights;
|
|
734
|
+
}
|
|
735
|
+
morphWeightTracksApplied += 1;
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const nodes = this.nodesByName.get(target.nodeName) ?? [];
|
|
740
|
+
if (nodes.length === 0) {
|
|
741
|
+
missingTargets.push(trackTarget);
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
for (const node of nodes) {
|
|
745
|
+
applyTransformValue(node, target.path, value, trackTarget);
|
|
746
|
+
}
|
|
747
|
+
transformTracksApplied += 1;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
this.options.scene.updateWorldTransforms();
|
|
751
|
+
const skinning = this.refreshSkinningPalettes();
|
|
752
|
+
return {
|
|
753
|
+
clipName,
|
|
754
|
+
time,
|
|
755
|
+
...(blendedClipCount === undefined ? {} : { blendedClipCount }),
|
|
756
|
+
tracksApplied: transformTracksApplied + morphWeightTracksApplied,
|
|
757
|
+
transformTracksApplied,
|
|
758
|
+
morphWeightTracksApplied,
|
|
759
|
+
skinningPalettesUpdated: skinning.updated,
|
|
760
|
+
missingTargets: [...missingTargets, ...skinning.missingTargets],
|
|
761
|
+
unsupportedTracks: sampled.unsupportedTracks
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
export function createGLTFSceneAnimationRuntime(options: GLTFSceneAnimationRuntimeOptions): GLTFSceneAnimationRuntime {
|
|
767
|
+
return new GLTFSceneAnimationRuntime(options);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
export class GLTFSceneAnimationMixerBinding {
|
|
771
|
+
readonly runtime: GLTFSceneAnimationRuntime;
|
|
772
|
+
readonly mixer: AnimationMixer;
|
|
773
|
+
readonly actions: ReadonlyMap<string, AnimationAction>;
|
|
774
|
+
private readonly pendingValues = new Map<string, AnimationValue>();
|
|
775
|
+
private elapsedTime = 0;
|
|
776
|
+
private lastApply?: GLTFSceneAnimationApplyResult;
|
|
777
|
+
|
|
778
|
+
constructor(options: GLTFSceneAnimationMixerOptions) {
|
|
779
|
+
this.runtime = new GLTFSceneAnimationRuntime(options);
|
|
780
|
+
this.mixer = new AnimationMixer({
|
|
781
|
+
setAnimationValue: (target, value) => {
|
|
782
|
+
this.pendingValues.set(target, cloneAnimationValue(value));
|
|
783
|
+
}
|
|
784
|
+
}, options.mixer ?? {});
|
|
785
|
+
const actions = new Map<string, AnimationAction>();
|
|
786
|
+
for (const clip of options.clips) {
|
|
787
|
+
const action = new AnimationAction(clip).setWeight(0);
|
|
788
|
+
this.mixer.addAction(action);
|
|
789
|
+
actions.set(clip.name, action);
|
|
790
|
+
}
|
|
791
|
+
const autoPlay = options.autoPlay === false ? undefined : options.autoPlay ?? options.clips[0]?.name;
|
|
792
|
+
if (autoPlay) {
|
|
793
|
+
const action = actions.get(autoPlay);
|
|
794
|
+
if (!action) {
|
|
795
|
+
throw new Error(`glTF animation mixer autoPlay clip "${autoPlay}" was not found.`);
|
|
796
|
+
}
|
|
797
|
+
action.setWeight(1).play();
|
|
798
|
+
}
|
|
799
|
+
this.actions = actions;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
listClips(): readonly string[] {
|
|
803
|
+
return [...this.actions.keys()];
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
play(name: string, weight = 1): AnimationAction {
|
|
807
|
+
return this.playClip(name, { weight });
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
playClip(name: string, options: GLTFSceneAnimationPlayOptions = {}): AnimationAction {
|
|
811
|
+
const action = this.requireAction(name);
|
|
812
|
+
this.configureAction(action, options);
|
|
813
|
+
action.play();
|
|
814
|
+
return action;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
playExclusive(name: string, options: GLTFSceneAnimationPlayOptions = {}): AnimationAction {
|
|
818
|
+
const action = this.requireAction(name);
|
|
819
|
+
const fadeDuration = options.fadeDuration ?? 0;
|
|
820
|
+
for (const [clipName, candidate] of this.actions) {
|
|
821
|
+
if (clipName === name) continue;
|
|
822
|
+
if (fadeDuration > 0) {
|
|
823
|
+
candidate.fadeTo(0, fadeDuration);
|
|
824
|
+
} else {
|
|
825
|
+
candidate.setWeight(0);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
this.configureAction(action, { ...options, weight: options.weight ?? 1 });
|
|
829
|
+
action.play();
|
|
830
|
+
return action;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
pause(name?: string): void {
|
|
834
|
+
for (const action of this.resolveActions(name)) {
|
|
835
|
+
action.pause();
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
resume(name?: string): void {
|
|
840
|
+
for (const action of this.resolveActions(name)) {
|
|
841
|
+
if (action.weight > 0) action.play();
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
stop(name?: string): void {
|
|
846
|
+
for (const action of this.resolveActions(name)) {
|
|
847
|
+
action.stop().setWeight(0);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
seek(name: string, time: number): AnimationAction {
|
|
852
|
+
const action = this.requireAction(name);
|
|
853
|
+
action.seek(time);
|
|
854
|
+
this.elapsedTime = action.time;
|
|
855
|
+
return action;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
setTimeScale(timeScale: number): void {
|
|
859
|
+
if (!Number.isFinite(timeScale) || timeScale < 0) {
|
|
860
|
+
throw new Error("glTF animation mixer timeScale must be finite and non-negative.");
|
|
861
|
+
}
|
|
862
|
+
this.mixer.timeScale = timeScale;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
setActionTimeScale(name: string, timeScale: number): AnimationAction {
|
|
866
|
+
return this.requireAction(name).setTimeScale(timeScale);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
setActionWeight(name: string, weight: number): AnimationAction {
|
|
870
|
+
return this.requireAction(name).setWeight(weight);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
setActionLoop(name: string, loopMode: LoopMode): AnimationAction {
|
|
874
|
+
return this.requireAction(name).setLoop(loopMode);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
crossFade(fromName: string, toName: string, duration: number): void {
|
|
878
|
+
const from = this.requireAction(fromName);
|
|
879
|
+
const to = this.requireAction(toName);
|
|
880
|
+
if (!to.playing) {
|
|
881
|
+
to.setWeight(0).play();
|
|
882
|
+
}
|
|
883
|
+
this.mixer.crossFade(from, to, duration);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
applyClipSamples(samples: readonly GLTFSceneAnimationClipSample[]): GLTFSceneAnimationMixerUpdateResult {
|
|
887
|
+
this.pendingValues.clear();
|
|
888
|
+
this.lastApply = this.runtime.applyClips(samples);
|
|
889
|
+
this.elapsedTime = Math.max(this.elapsedTime, this.lastApply.time);
|
|
890
|
+
return { events: [], applyResult: this.lastApply, activeActions: this.actionSnapshots().filter((action) => action.active) };
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
update(deltaSeconds: number): GLTFSceneAnimationMixerUpdateResult {
|
|
894
|
+
if (!Number.isFinite(deltaSeconds) || deltaSeconds < 0) {
|
|
895
|
+
throw new Error("glTF animation mixer delta must be finite and non-negative.");
|
|
896
|
+
}
|
|
897
|
+
this.pendingValues.clear();
|
|
898
|
+
const events = this.mixer.update(deltaSeconds);
|
|
899
|
+
this.elapsedTime += deltaSeconds;
|
|
900
|
+
this.lastApply = this.runtime.applyAnimationValues(
|
|
901
|
+
"gltf-animation-mixer",
|
|
902
|
+
this.elapsedTime,
|
|
903
|
+
this.pendingValues
|
|
904
|
+
);
|
|
905
|
+
return { events, applyResult: this.lastApply, activeActions: this.actionSnapshots().filter((action) => action.active) };
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
getAction(name: string): AnimationAction | undefined {
|
|
909
|
+
return this.actions.get(name);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
snapshot(): GLTFSceneAnimationMixerSnapshot {
|
|
913
|
+
return {
|
|
914
|
+
...this.runtime.snapshot(),
|
|
915
|
+
mixerActionCount: this.actions.size,
|
|
916
|
+
pendingValueCount: this.pendingValues.size,
|
|
917
|
+
elapsedTime: this.elapsedTime,
|
|
918
|
+
timeScale: this.mixer.timeScale,
|
|
919
|
+
activeClipNames: this.actionSnapshots().filter((action) => action.active).map((action) => action.clipName),
|
|
920
|
+
actions: this.actionSnapshots(),
|
|
921
|
+
...(this.lastApply ? { lastApply: this.lastApply } : {})
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
private configureAction(action: AnimationAction, options: GLTFSceneAnimationPlayOptions): void {
|
|
926
|
+
if (options.reset === true) {
|
|
927
|
+
action.reset();
|
|
928
|
+
}
|
|
929
|
+
if (options.weight !== undefined) {
|
|
930
|
+
if (options.fadeDuration !== undefined && options.fadeDuration > 0) {
|
|
931
|
+
action.fadeTo(options.weight, options.fadeDuration);
|
|
932
|
+
} else {
|
|
933
|
+
action.setWeight(options.weight);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
if (options.timeScale !== undefined) {
|
|
937
|
+
action.setTimeScale(options.timeScale);
|
|
938
|
+
}
|
|
939
|
+
if (options.loopMode !== undefined) {
|
|
940
|
+
action.setLoop(options.loopMode);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
private resolveActions(name: string | undefined): readonly AnimationAction[] {
|
|
945
|
+
return name === undefined ? [...this.actions.values()] : [this.requireAction(name)];
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
private actionSnapshots(): readonly GLTFSceneAnimationActionSnapshot[] {
|
|
949
|
+
return [...this.actions.values()].map((action) => {
|
|
950
|
+
const snapshot = action.snapshot();
|
|
951
|
+
return {
|
|
952
|
+
...snapshot,
|
|
953
|
+
active: snapshot.playing && !snapshot.paused && snapshot.weight > 0
|
|
954
|
+
};
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
private requireAction(name: string): AnimationAction {
|
|
959
|
+
const action = this.actions.get(name);
|
|
960
|
+
if (!action) {
|
|
961
|
+
throw new Error(`glTF animation action "${name}" was not found.`);
|
|
962
|
+
}
|
|
963
|
+
return action;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
export function createGLTFSceneAnimationMixer(options: GLTFSceneAnimationMixerOptions): GLTFSceneAnimationMixerBinding {
|
|
968
|
+
return new GLTFSceneAnimationMixerBinding(options);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
function parseAnimationTarget(target: string): { readonly nodeName: string; readonly path: "translation" | "rotation" | "scale" | "weights" } | undefined {
|
|
972
|
+
const separator = target.lastIndexOf(".");
|
|
973
|
+
if (separator <= 0 || separator === target.length - 1) {
|
|
974
|
+
return undefined;
|
|
975
|
+
}
|
|
976
|
+
const path = target.slice(separator + 1);
|
|
977
|
+
if (path !== "translation" && path !== "rotation" && path !== "scale" && path !== "weights") {
|
|
978
|
+
return undefined;
|
|
979
|
+
}
|
|
980
|
+
return {
|
|
981
|
+
nodeName: target.slice(0, separator),
|
|
982
|
+
path
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function resolveIKSkin(skins: readonly GLTFSkinAsset[], skinName: string | undefined): GLTFSkinAsset {
|
|
987
|
+
if (skinName === undefined) {
|
|
988
|
+
const skin = skins.find((candidate) => candidate.jointNames.length >= 3);
|
|
989
|
+
if (!skin) {
|
|
990
|
+
throw new Error("glTF imported skeleton IK requires a skin with at least three joints.");
|
|
991
|
+
}
|
|
992
|
+
return skin;
|
|
993
|
+
}
|
|
994
|
+
const skin = skins.find((candidate) => candidate.name === skinName);
|
|
995
|
+
if (!skin) {
|
|
996
|
+
throw new Error(`glTF imported skeleton IK skin "${skinName}" was not found.`);
|
|
997
|
+
}
|
|
998
|
+
if (skin.jointNames.length < 3) {
|
|
999
|
+
throw new Error(`glTF imported skeleton IK skin "${skinName}" does not contain a two-bone chain.`);
|
|
1000
|
+
}
|
|
1001
|
+
return skin;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function firstTwoBoneJointChain(skin: GLTFSkinAsset): readonly [string, string, string] {
|
|
1005
|
+
const root = skin.jointNames[0];
|
|
1006
|
+
const mid = skin.jointNames[1];
|
|
1007
|
+
const end = skin.jointNames[2];
|
|
1008
|
+
if (!root || !mid || !end) {
|
|
1009
|
+
throw new Error(`glTF imported skeleton IK skin "${skin.name}" does not contain a two-bone chain.`);
|
|
1010
|
+
}
|
|
1011
|
+
return [root, mid, end];
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
function worldPosition(node: SceneNode): Vec3 {
|
|
1015
|
+
node.updateWorldTransform();
|
|
1016
|
+
return [node.transform.worldMatrix[12], node.transform.worldMatrix[13], node.transform.worldMatrix[14]];
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function setWorldPosition(node: SceneNode, position: readonly [number, number, number]): void {
|
|
1020
|
+
const local = node.parent
|
|
1021
|
+
? transformPoint(invertMat4(node.parent.transform.worldMatrix), [position[0], position[1], position[2]])
|
|
1022
|
+
: [position[0], position[1], position[2]] as Vec3;
|
|
1023
|
+
node.transform.setPosition(local[0], local[1], local[2]);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
function applyTransformValue(node: SceneNode, path: "translation" | "rotation" | "scale", value: AnimationValue, target: string): void {
|
|
1027
|
+
if (path === "translation") {
|
|
1028
|
+
const vector = asVec3(value, target);
|
|
1029
|
+
node.transform.setPosition(vector[0], vector[1], vector[2]);
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
if (path === "rotation") {
|
|
1033
|
+
const quat = asQuat(value, target);
|
|
1034
|
+
node.transform.setRotation(quat[0], quat[1], quat[2], quat[3]);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
const vector = asVec3(value, target);
|
|
1038
|
+
node.transform.setScale(vector[0], vector[1], vector[2]);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function asVec3(value: AnimationValue, target: string): [number, number, number] {
|
|
1042
|
+
if (!Array.isArray(value) || value.length !== 3 || value.some((component) => typeof component !== "number" || !Number.isFinite(component))) {
|
|
1043
|
+
throw new Error(`glTF animation track "${target}" sampled an invalid vec3.`);
|
|
1044
|
+
}
|
|
1045
|
+
return [value[0], value[1], value[2]];
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function asQuat(value: AnimationValue, target: string): [number, number, number, number] {
|
|
1049
|
+
if (!Array.isArray(value) || value.length !== 4 || value.some((component) => typeof component !== "number" || !Number.isFinite(component))) {
|
|
1050
|
+
throw new Error(`glTF animation track "${target}" sampled an invalid quaternion.`);
|
|
1051
|
+
}
|
|
1052
|
+
return [value[0], value[1], value[2], value[3]];
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function asNumberArray(value: AnimationValue): number[] {
|
|
1056
|
+
if (!Array.isArray(value) || value.some((component) => typeof component !== "number" || !Number.isFinite(component))) {
|
|
1057
|
+
throw new Error("glTF morph-weight animation sampled an invalid number array.");
|
|
1058
|
+
}
|
|
1059
|
+
return [...value];
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function sampleClipTracks(
|
|
1063
|
+
clip: AnimationClip,
|
|
1064
|
+
time: number,
|
|
1065
|
+
weight: number,
|
|
1066
|
+
additive: boolean
|
|
1067
|
+
): { readonly sampledTargets: ReadonlyMap<string, AnimationValue>; readonly unsupportedTracks: readonly string[] } {
|
|
1068
|
+
const accumulators = new Map<string, TargetAccumulator>();
|
|
1069
|
+
const unsupportedTracks: string[] = [];
|
|
1070
|
+
for (const track of clip.tracks) {
|
|
1071
|
+
if (!parseAnimationTarget(track.target)) {
|
|
1072
|
+
unsupportedTracks.push(track.target);
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
blendInto(accumulators, track.target, track.valueType, track.sample(time), weight, additive);
|
|
1076
|
+
}
|
|
1077
|
+
const sampledTargets = new Map<string, AnimationValue>();
|
|
1078
|
+
for (const [target, accumulator] of accumulators) {
|
|
1079
|
+
sampledTargets.set(target, finalizeTargetBlend(accumulator));
|
|
1080
|
+
}
|
|
1081
|
+
return { sampledTargets, unsupportedTracks };
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function blendInto(
|
|
1085
|
+
accumulators: Map<string, TargetAccumulator>,
|
|
1086
|
+
target: string,
|
|
1087
|
+
type: TrackValueType,
|
|
1088
|
+
value: AnimationValue,
|
|
1089
|
+
weight: number,
|
|
1090
|
+
additive: boolean
|
|
1091
|
+
): void {
|
|
1092
|
+
if (weight <= 0) return;
|
|
1093
|
+
const current = accumulators.get(target);
|
|
1094
|
+
if (!current) {
|
|
1095
|
+
const accumulator: TargetAccumulator = { type };
|
|
1096
|
+
if (additive) {
|
|
1097
|
+
accumulator.additive = additiveContribution(type, value, weight);
|
|
1098
|
+
} else {
|
|
1099
|
+
accumulator.base = { value: cloneAnimationValue(value), weight, type };
|
|
1100
|
+
}
|
|
1101
|
+
accumulators.set(target, accumulator);
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
if (current.type !== type) {
|
|
1105
|
+
throw new Error(`Cannot blend ${type} glTF animation track into existing ${current.type} target ${target}.`);
|
|
1106
|
+
}
|
|
1107
|
+
if (additive) {
|
|
1108
|
+
current.additive = current.additive === undefined
|
|
1109
|
+
? additiveContribution(type, value, weight)
|
|
1110
|
+
: combineAdditive(type, current.additive, value, weight);
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
if (!current.base) {
|
|
1114
|
+
current.base = { value: cloneAnimationValue(value), weight, type };
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
blendBase(current.base, type, value, weight);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function blendBase(current: WeightedAccumulator, type: TrackValueType, value: AnimationValue, weight: number): void {
|
|
1121
|
+
const total = current.weight + weight;
|
|
1122
|
+
const t = weight / total;
|
|
1123
|
+
if (type === "scalar") {
|
|
1124
|
+
current.value = (current.value as number) + ((value as number) - (current.value as number)) * t;
|
|
1125
|
+
} else if (type === "vector3") {
|
|
1126
|
+
const a = current.value as [number, number, number];
|
|
1127
|
+
const b = value as [number, number, number];
|
|
1128
|
+
current.value = [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];
|
|
1129
|
+
} else if (type === "quaternion") {
|
|
1130
|
+
current.value = slerpQuat(current.value as [number, number, number, number], value as [number, number, number, number], t);
|
|
1131
|
+
} else if (type === "number-array") {
|
|
1132
|
+
const a = current.value as readonly number[];
|
|
1133
|
+
const b = value as readonly number[];
|
|
1134
|
+
if (a.length !== b.length) {
|
|
1135
|
+
throw new Error("Cannot blend glTF morph animation values with different lengths.");
|
|
1136
|
+
}
|
|
1137
|
+
current.value = a.map((component, index) => component + (b[index]! - component) * t);
|
|
1138
|
+
} else {
|
|
1139
|
+
current.value = cloneAnimationValue(value);
|
|
1140
|
+
}
|
|
1141
|
+
current.weight = total;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function finalizeTargetBlend(accumulator: TargetAccumulator): AnimationValue {
|
|
1145
|
+
const base = accumulator.base
|
|
1146
|
+
? finalizeBaseBlend(accumulator.base)
|
|
1147
|
+
: accumulator.additive !== undefined
|
|
1148
|
+
? additiveNeutral(accumulator.type, accumulator.additive)
|
|
1149
|
+
: undefined;
|
|
1150
|
+
if (base === undefined) {
|
|
1151
|
+
throw new Error("glTF animation target accumulator has no sampled value.");
|
|
1152
|
+
}
|
|
1153
|
+
return accumulator.additive === undefined ? base : applyAdditive(accumulator.type, base, accumulator.additive);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function finalizeBaseBlend(accumulator: WeightedAccumulator): AnimationValue {
|
|
1157
|
+
return accumulator.type === "quaternion"
|
|
1158
|
+
? normalizeQuat(accumulator.value as [number, number, number, number])
|
|
1159
|
+
: cloneAnimationValue(accumulator.value);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function additiveContribution(type: TrackValueType, value: AnimationValue, weight: number): AnimationValue {
|
|
1163
|
+
if (type === "scalar") return (value as number) * weight;
|
|
1164
|
+
if (type === "vector3") {
|
|
1165
|
+
const vector = value as [number, number, number];
|
|
1166
|
+
return [vector[0] * weight, vector[1] * weight, vector[2] * weight];
|
|
1167
|
+
}
|
|
1168
|
+
if (type === "number-array") {
|
|
1169
|
+
return (value as readonly number[]).map((component) => component * weight);
|
|
1170
|
+
}
|
|
1171
|
+
if (type === "quaternion") {
|
|
1172
|
+
return slerpQuat([0, 0, 0, 1], normalizeQuat(value as [number, number, number, number]), weight);
|
|
1173
|
+
}
|
|
1174
|
+
throw new Error("Additive glTF animation layers require scalar, vector3, quaternion, or number-array tracks.");
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function combineAdditive(type: TrackValueType, current: AnimationValue, value: AnimationValue, weight: number): AnimationValue {
|
|
1178
|
+
return applyAdditive(type, current, additiveContribution(type, value, weight));
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
function applyAdditive(type: TrackValueType, base: AnimationValue, delta: AnimationValue): AnimationValue {
|
|
1182
|
+
if (type === "scalar") return (base as number) + (delta as number);
|
|
1183
|
+
if (type === "vector3") {
|
|
1184
|
+
const a = base as [number, number, number];
|
|
1185
|
+
const b = delta as [number, number, number];
|
|
1186
|
+
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
|
|
1187
|
+
}
|
|
1188
|
+
if (type === "number-array") {
|
|
1189
|
+
const a = base as readonly number[];
|
|
1190
|
+
const b = delta as readonly number[];
|
|
1191
|
+
if (a.length !== b.length) {
|
|
1192
|
+
throw new Error("Cannot add glTF morph animation values with different lengths.");
|
|
1193
|
+
}
|
|
1194
|
+
return a.map((component, index) => component + b[index]!);
|
|
1195
|
+
}
|
|
1196
|
+
if (type === "quaternion") {
|
|
1197
|
+
return normalizeQuat(multiplyQuat(base as [number, number, number, number], delta as [number, number, number, number]));
|
|
1198
|
+
}
|
|
1199
|
+
throw new Error("Additive glTF animation layers require scalar, vector3, quaternion, or number-array tracks.");
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
function additiveNeutral(type: TrackValueType, value: AnimationValue): AnimationValue {
|
|
1203
|
+
if (type === "scalar") return 0;
|
|
1204
|
+
if (type === "vector3") return [0, 0, 0];
|
|
1205
|
+
if (type === "number-array") return (value as readonly number[]).map(() => 0);
|
|
1206
|
+
if (type === "quaternion") return [0, 0, 0, 1];
|
|
1207
|
+
throw new Error("Additive glTF animation layers require scalar, vector3, quaternion, or number-array tracks.");
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function cloneAnimationValue(value: AnimationValue): AnimationValue {
|
|
1211
|
+
return Array.isArray(value) ? [...value] : value;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function multiplyQuat(a: [number, number, number, number], b: [number, number, number, number]): [number, number, number, number] {
|
|
1215
|
+
return [
|
|
1216
|
+
a[3] * b[0] + a[0] * b[3] + a[1] * b[2] - a[2] * b[1],
|
|
1217
|
+
a[3] * b[1] - a[0] * b[2] + a[1] * b[3] + a[2] * b[0],
|
|
1218
|
+
a[3] * b[2] + a[0] * b[1] - a[1] * b[0] + a[2] * b[3],
|
|
1219
|
+
a[3] * b[3] - a[0] * b[0] - a[1] * b[1] - a[2] * b[2]
|
|
1220
|
+
];
|
|
1221
|
+
}
|