@zephyr3d/scene 0.8.1 → 0.9.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/dist/animation/animationset.js +563 -128
- package/dist/animation/animationset.js.map +1 -1
- package/dist/animation/cloth/gpu_cloth_system.js +2516 -0
- package/dist/animation/cloth/gpu_cloth_system.js.map +1 -0
- package/dist/animation/fixed_geometry_cache_track.js +138 -0
- package/dist/animation/fixed_geometry_cache_track.js.map +1 -0
- package/dist/animation/geometry_cache_utils.js +120 -0
- package/dist/animation/geometry_cache_utils.js.map +1 -0
- package/dist/animation/ik_modifier.js +9 -1
- package/dist/animation/ik_modifier.js.map +1 -1
- package/dist/animation/joint_dynamics/collision.js +469 -0
- package/dist/animation/joint_dynamics/collision.js.map +1 -0
- package/dist/animation/joint_dynamics/constraints.js +329 -0
- package/dist/animation/joint_dynamics/constraints.js.map +1 -0
- package/dist/animation/joint_dynamics/controller.js +699 -0
- package/dist/animation/joint_dynamics/controller.js.map +1 -0
- package/dist/animation/joint_dynamics/joint_dynamics_system.js +393 -0
- package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -0
- package/dist/animation/joint_dynamics/solver.js +794 -0
- package/dist/animation/joint_dynamics/solver.js.map +1 -0
- package/dist/animation/joint_dynamics/types.js +19 -0
- package/dist/animation/joint_dynamics/types.js.map +1 -0
- package/dist/animation/joint_dynamics/vrm_spring_bone.js +46 -0
- package/dist/animation/joint_dynamics/vrm_spring_bone.js.map +1 -0
- package/dist/animation/joint_dynamics_modifier.js +51 -0
- package/dist/animation/joint_dynamics_modifier.js.map +1 -0
- package/dist/animation/pca_geometry_cache_track.js +222 -0
- package/dist/animation/pca_geometry_cache_track.js.map +1 -0
- package/dist/animation/skeleton.js +1907 -3
- package/dist/animation/skeleton.js.map +1 -1
- package/dist/animation/skeleton_modifier.js +3 -5
- package/dist/animation/skeleton_modifier.js.map +1 -1
- package/dist/animation/spring/multi_chain_spring_system.js +2 -0
- package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
- package/dist/animation/spring/spring_chain.js +3 -2
- package/dist/animation/spring/spring_chain.js.map +1 -1
- package/dist/animation/spring/spring_collider.js +35 -5
- package/dist/animation/spring/spring_collider.js.map +1 -1
- package/dist/animation/spring/spring_particle.js +1 -0
- package/dist/animation/spring/spring_particle.js.map +1 -1
- package/dist/animation/spring/spring_system.js +325 -28
- package/dist/animation/spring/spring_system.js.map +1 -1
- package/dist/animation/spring2/collision.js +469 -0
- package/dist/animation/spring2/collision.js.map +1 -0
- package/dist/animation/spring2/constraints.js +329 -0
- package/dist/animation/spring2/constraints.js.map +1 -0
- package/dist/animation/spring2/controller.js +434 -0
- package/dist/animation/spring2/controller.js.map +1 -0
- package/dist/animation/spring2/math.js +16 -0
- package/dist/animation/spring2/math.js.map +1 -0
- package/dist/animation/spring2/solver.js +624 -0
- package/dist/animation/spring2/solver.js.map +1 -0
- package/dist/animation/spring2/spring_system.js +118 -0
- package/dist/animation/spring2/spring_system.js.map +1 -0
- package/dist/animation/spring2/types.js +19 -0
- package/dist/animation/spring2/types.js.map +1 -0
- package/dist/animation/spring_modifier.js +17 -1
- package/dist/animation/spring_modifier.js.map +1 -1
- package/dist/app/engine.js +42 -15
- package/dist/app/engine.js.map +1 -1
- package/dist/app/runtimescript.js +132 -1
- package/dist/app/runtimescript.js.map +1 -1
- package/dist/app/screen.js +3 -3
- package/dist/app/screen.js.map +1 -1
- package/dist/app/scriptingsystem.js +69 -37
- package/dist/app/scriptingsystem.js.map +1 -1
- package/dist/app/scriptregistry.js +157 -146
- package/dist/app/scriptregistry.js.map +1 -1
- package/dist/asset/assetmanager.js +2 -1
- package/dist/asset/assetmanager.js.map +1 -1
- package/dist/asset/loaders/zabc/zabc_loader.js +570 -0
- package/dist/asset/loaders/zabc/zabc_loader.js.map +1 -0
- package/dist/asset/model.js +192 -0
- package/dist/asset/model.js.map +1 -1
- package/dist/camera/base.js +26 -7
- package/dist/camera/base.js.map +1 -1
- package/dist/camera/camera.js +146 -24
- package/dist/camera/camera.js.map +1 -1
- package/dist/camera/fps.js +2 -2
- package/dist/camera/fps.js.map +1 -1
- package/dist/camera/orbit.js +2 -2
- package/dist/camera/orbit.js.map +1 -1
- package/dist/index.d.ts +11701 -8506
- package/dist/index.js +29 -8
- package/dist/index.js.map +1 -1
- package/dist/material/lambert.js +1 -1
- package/dist/material/lambert.js.map +1 -1
- package/dist/material/material.js +9 -3
- package/dist/material/material.js.map +1 -1
- package/dist/material/meshmaterial.js +54 -7
- package/dist/material/meshmaterial.js.map +1 -1
- package/dist/material/mixins/lightmodel/blinnphong.js +17 -7
- package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
- package/dist/material/mixins/lightmodel/lambert.js +7 -2
- package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +108 -16
- package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +222 -14
- package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrspecularglossness.js +24 -12
- package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
- package/dist/material/mixins/lit.js +11 -8
- package/dist/material/mixins/lit.js.map +1 -1
- package/dist/material/mixins/pbr/common.js +72 -5
- package/dist/material/mixins/pbr/common.js.map +1 -1
- package/dist/material/pbrblueprint.js +148 -1
- package/dist/material/pbrblueprint.js.map +1 -1
- package/dist/material/pbrmr.js +125 -2
- package/dist/material/pbrmr.js.map +1 -1
- package/dist/material/shader/helper.js +10 -4
- package/dist/material/shader/helper.js.map +1 -1
- package/dist/material/water.js +2 -2
- package/dist/material/water.js.map +1 -1
- package/dist/posteffect/coloradjust.js +145 -0
- package/dist/posteffect/coloradjust.js.map +1 -0
- package/dist/posteffect/taa.js +68 -32
- package/dist/posteffect/taa.js.map +1 -1
- package/dist/render/cluster_light.js +17 -12
- package/dist/render/cluster_light.js.map +1 -1
- package/dist/render/deferredlightpass.js +510 -0
- package/dist/render/deferredlightpass.js.map +1 -0
- package/dist/render/deferredshadowlightpass.js +428 -0
- package/dist/render/deferredshadowlightpass.js.map +1 -0
- package/dist/render/drawable.js.map +1 -1
- package/dist/render/drawable_mixin.js +21 -12
- package/dist/render/drawable_mixin.js.map +1 -1
- package/dist/render/envlight.js +20 -22
- package/dist/render/envlight.js.map +1 -1
- package/dist/render/gbufferpass.js +50 -0
- package/dist/render/gbufferpass.js.map +1 -0
- package/dist/render/lightpass.js +1 -2
- package/dist/render/lightpass.js.map +1 -1
- package/dist/render/primitive.js +1 -1
- package/dist/render/primitive.js.map +1 -1
- package/dist/render/render_queue.js +3 -2
- package/dist/render/render_queue.js.map +1 -1
- package/dist/render/renderer.js +5 -358
- package/dist/render/renderer.js.map +1 -1
- package/dist/render/rendergraph/executor.js +15 -12
- package/dist/render/rendergraph/executor.js.map +1 -1
- package/dist/render/rendergraph/forward_plus_builder.js +144 -88
- package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
- package/dist/render/rendergraph/history_resource_manager.js +178 -0
- package/dist/render/rendergraph/history_resource_manager.js.map +1 -0
- package/dist/render/rendergraph/rendergraph.js +12 -1
- package/dist/render/rendergraph/rendergraph.js.map +1 -1
- package/dist/render/rendergraph/types.js +2 -2
- package/dist/render/rendergraph/types.js.map +1 -1
- package/dist/render/sky.js +26 -9
- package/dist/render/sky.js.map +1 -1
- package/dist/scene/basesprite.js +4 -3
- package/dist/scene/basesprite.js.map +1 -1
- package/dist/scene/batchgroup.js +4 -4
- package/dist/scene/environment.js +7 -4
- package/dist/scene/environment.js.map +1 -1
- package/dist/scene/light.js +184 -18
- package/dist/scene/light.js.map +1 -1
- package/dist/scene/mesh.js +30 -0
- package/dist/scene/mesh.js.map +1 -1
- package/dist/scene/particlesys.js +3 -2
- package/dist/scene/particlesys.js.map +1 -1
- package/dist/scene/raycast_visitor.js +29 -3
- package/dist/scene/raycast_visitor.js.map +1 -1
- package/dist/scene/scene.js +86 -19
- package/dist/scene/scene.js.map +1 -1
- package/dist/scene/scene_node.js +87 -31
- package/dist/scene/scene_node.js.map +1 -1
- package/dist/scene/script_attachment.js +59 -0
- package/dist/scene/script_attachment.js.map +1 -0
- package/dist/scene/terrain-cm/grass.js +3 -2
- package/dist/scene/terrain-cm/grass.js.map +1 -1
- package/dist/scene/terrain-cm/terrain-cm.js +4 -3
- package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
- package/dist/scene/water.js +3 -2
- package/dist/scene/water.js.map +1 -1
- package/dist/shaders/shadow.js +2 -2
- package/dist/shaders/shadow.js.map +1 -1
- package/dist/shadow/shadowmapper.js +74 -4
- package/dist/shadow/shadowmapper.js.map +1 -1
- package/dist/shapes/box.js +163 -0
- package/dist/shapes/box.js.map +1 -1
- package/dist/shapes/capsule.js +216 -0
- package/dist/shapes/capsule.js.map +1 -0
- package/dist/shapes/cylinder.js +91 -0
- package/dist/shapes/cylinder.js.map +1 -1
- package/dist/shapes/plane.js +32 -0
- package/dist/shapes/plane.js.map +1 -1
- package/dist/shapes/tetrahedron.js +211 -0
- package/dist/shapes/tetrahedron.js.map +1 -1
- package/dist/shapes/torus.js +173 -0
- package/dist/shapes/torus.js.map +1 -1
- package/dist/utility/blueprint/common/math.js +111 -1
- package/dist/utility/blueprint/common/math.js.map +1 -1
- package/dist/utility/blueprint/material/inputs.js +235 -1
- package/dist/utility/blueprint/material/inputs.js.map +1 -1
- package/dist/utility/blueprint/material/ir.js +154 -10
- package/dist/utility/blueprint/material/ir.js.map +1 -1
- package/dist/utility/blueprint/material/texture.js +273 -19
- package/dist/utility/blueprint/material/texture.js.map +1 -1
- package/dist/utility/serialization/json.js +12 -2
- package/dist/utility/serialization/json.js.map +1 -1
- package/dist/utility/serialization/manager.js +30 -11
- package/dist/utility/serialization/manager.js.map +1 -1
- package/dist/utility/serialization/scene/animation.js +250 -7
- package/dist/utility/serialization/scene/animation.js.map +1 -1
- package/dist/utility/serialization/scene/batch.js +4 -2
- package/dist/utility/serialization/scene/batch.js.map +1 -1
- package/dist/utility/serialization/scene/camera.js +154 -2
- package/dist/utility/serialization/scene/camera.js.map +1 -1
- package/dist/utility/serialization/scene/cloth_script.js +834 -0
- package/dist/utility/serialization/scene/cloth_script.js.map +1 -0
- package/dist/utility/serialization/scene/common.js +7 -0
- package/dist/utility/serialization/scene/common.js.map +1 -1
- package/dist/utility/serialization/scene/light.js +120 -4
- package/dist/utility/serialization/scene/light.js.map +1 -1
- package/dist/utility/serialization/scene/material.js +453 -1
- package/dist/utility/serialization/scene/material.js.map +1 -1
- package/dist/utility/serialization/scene/mesh.js +4 -2
- package/dist/utility/serialization/scene/mesh.js.map +1 -1
- package/dist/utility/serialization/scene/node.js +298 -10
- package/dist/utility/serialization/scene/node.js.map +1 -1
- package/dist/utility/serialization/scene/particle.js +4 -2
- package/dist/utility/serialization/scene/particle.js.map +1 -1
- package/dist/utility/serialization/scene/primitive.js +100 -3
- package/dist/utility/serialization/scene/primitive.js.map +1 -1
- package/dist/utility/serialization/scene/scene.js +130 -3
- package/dist/utility/serialization/scene/scene.js.map +1 -1
- package/dist/utility/serialization/scene/script.js +66 -0
- package/dist/utility/serialization/scene/script.js.map +1 -0
- package/dist/utility/serialization/scene/spring_script.js +596 -0
- package/dist/utility/serialization/scene/spring_script.js.map +1 -0
- package/dist/utility/serialization/scene/sprite.js +5 -3
- package/dist/utility/serialization/scene/sprite.js.map +1 -1
- package/dist/utility/serialization/scene/terrain.js +4 -2
- package/dist/utility/serialization/scene/terrain.js.map +1 -1
- package/dist/utility/serialization/scene/water.js +4 -2
- package/dist/utility/serialization/scene/water.js.map +1 -1
- package/dist/utility/serialization/types.js.map +1 -1
- package/dist/values.js +5 -1
- package/dist/values.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,11 +1,297 @@
|
|
|
1
|
-
import { Disposable, weightedAverage, Quaternion, Interpolator } from '@zephyr3d/base';
|
|
1
|
+
import { Disposable, weightedAverage, Quaternion, Interpolator, Vector3 } from '@zephyr3d/base';
|
|
2
2
|
import { AnimationClip } from './animation.js';
|
|
3
3
|
import { NodeRotationTrack } from './rotationtrack.js';
|
|
4
4
|
import { NodeEulerRotationTrack } from './eulerrotationtrack.js';
|
|
5
5
|
import { NodeTranslationTrack } from './translationtrack.js';
|
|
6
6
|
import { NodeScaleTrack } from './scaletrack.js';
|
|
7
|
-
import { Skeleton } from './skeleton.js';
|
|
7
|
+
import { Skeleton, HumanoidBodyRig } from './skeleton.js';
|
|
8
8
|
|
|
9
|
+
function cloneInterpolator(src) {
|
|
10
|
+
return new Interpolator(src.mode, src.target, src.inputs instanceof Float32Array ? new Float32Array(src.inputs) : [
|
|
11
|
+
...src.inputs
|
|
12
|
+
], src.outputs instanceof Float32Array ? new Float32Array(src.outputs) : [
|
|
13
|
+
...src.outputs
|
|
14
|
+
]);
|
|
15
|
+
}
|
|
16
|
+
function createJointRetargetRemap(srcSkeleton, dstSkeleton, srcJoint, dstJoint, translationRotation) {
|
|
17
|
+
const si = srcSkeleton.joints.indexOf(srcJoint);
|
|
18
|
+
const di = dstSkeleton.joints.indexOf(dstJoint);
|
|
19
|
+
const srcBindPose = srcSkeleton.bindPose[si];
|
|
20
|
+
const dstBindPose = dstSkeleton.bindPose[di];
|
|
21
|
+
const srcLen = srcBindPose.position.magnitude;
|
|
22
|
+
const dstLen = dstBindPose.position.magnitude;
|
|
23
|
+
return {
|
|
24
|
+
dstNode: dstJoint,
|
|
25
|
+
dstJointIndex: di,
|
|
26
|
+
srcNode: srcJoint,
|
|
27
|
+
srcBindRotInv: Quaternion.inverse(srcBindPose.rotation),
|
|
28
|
+
dstBindRot: dstBindPose.rotation.clone(),
|
|
29
|
+
srcBindPos: srcBindPose.position.clone(),
|
|
30
|
+
dstBindPos: dstBindPose.position.clone(),
|
|
31
|
+
translationScale: srcLen > 1e-6 ? dstLen / srcLen : 1,
|
|
32
|
+
translationRotation
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function retargetRotation(qSrcAnim, remap, out) {
|
|
36
|
+
Quaternion.multiply(remap.srcBindRotInv, qSrcAnim, out);
|
|
37
|
+
Quaternion.multiply(remap.dstBindRot, out, out);
|
|
38
|
+
return out.inplaceNormalize();
|
|
39
|
+
}
|
|
40
|
+
function retargetRotationTangent(qSrcTangent, remap, out) {
|
|
41
|
+
Quaternion.multiply(remap.srcBindRotInv, qSrcTangent, out);
|
|
42
|
+
Quaternion.multiply(remap.dstBindRot, out, out);
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
function retargetTranslationValue(srcValue, remap, out) {
|
|
46
|
+
out.setXYZ(srcValue.x, srcValue.y, srcValue.z);
|
|
47
|
+
out.subBy(remap.srcBindPos);
|
|
48
|
+
out.scaleBy(remap.translationScale);
|
|
49
|
+
if (remap.translationRotation) {
|
|
50
|
+
remap.translationRotation.transform(out, out);
|
|
51
|
+
}
|
|
52
|
+
out.addBy(remap.dstBindPos);
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
function retargetTranslationTangent(srcValue, remap, out) {
|
|
56
|
+
out.setXYZ(srcValue.x, srcValue.y, srcValue.z);
|
|
57
|
+
out.scaleBy(remap.translationScale);
|
|
58
|
+
if (remap.translationRotation) {
|
|
59
|
+
remap.translationRotation.transform(out, out);
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
function retargetRotationTrack(src, remap) {
|
|
64
|
+
const isCubic = src.interpolator.mode === 'cubicspline';
|
|
65
|
+
const frameStride = isCubic ? 12 : 4;
|
|
66
|
+
const numFrames = src.interpolator.inputs.length;
|
|
67
|
+
const srcOutputs = src.interpolator.outputs;
|
|
68
|
+
const newOutputs = new Float32Array(srcOutputs.length);
|
|
69
|
+
const t = isCubic ? 4 : 0;
|
|
70
|
+
const q = new Quaternion();
|
|
71
|
+
for(let f = 0; f < numFrames; f++){
|
|
72
|
+
const base = f * frameStride;
|
|
73
|
+
q.setXYZW(srcOutputs[base + t], srcOutputs[base + t + 1], srcOutputs[base + t + 2], srcOutputs[base + t + 3]);
|
|
74
|
+
retargetRotation(q, remap, q);
|
|
75
|
+
if (isCubic) {
|
|
76
|
+
// Cubic quaternion tangents live in the same component space, so apply the
|
|
77
|
+
// same constant bind-pose transform without normalizing them.
|
|
78
|
+
q.setXYZW(srcOutputs[base], srcOutputs[base + 1], srcOutputs[base + 2], srcOutputs[base + 3]);
|
|
79
|
+
newOutputs.set(retargetRotationTangent(q, remap, q), base);
|
|
80
|
+
newOutputs.set(retargetRotation(new Quaternion(srcOutputs.subarray(base + 4, base + 8)), remap, q), base + 4);
|
|
81
|
+
q.setXYZW(srcOutputs[base + 8], srcOutputs[base + 9], srcOutputs[base + 10], srcOutputs[base + 11]);
|
|
82
|
+
newOutputs.set(retargetRotationTangent(q, remap, q), base + 8);
|
|
83
|
+
} else {
|
|
84
|
+
newOutputs.set(q, base);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return new NodeRotationTrack(new Interpolator(src.interpolator.mode, 'quat', new Float32Array(src.interpolator.inputs), newOutputs));
|
|
88
|
+
}
|
|
89
|
+
function retargetEulerToRotationTrack(src, remap) {
|
|
90
|
+
const srcInputs = src.interpolator.inputs;
|
|
91
|
+
const srcOutputs = src.interpolator.outputs;
|
|
92
|
+
const isCubic = src.interpolator.mode === 'cubicspline';
|
|
93
|
+
const frameStride = isCubic ? 9 : 3;
|
|
94
|
+
const t = isCubic ? 3 : 0;
|
|
95
|
+
const numFrames = srcInputs.length;
|
|
96
|
+
const newOutputs = new Float32Array(numFrames * 4);
|
|
97
|
+
const q = new Quaternion();
|
|
98
|
+
for(let f = 0; f < numFrames; f++){
|
|
99
|
+
const base = f * frameStride + t;
|
|
100
|
+
q.fromEulerAngle(srcOutputs[base], srcOutputs[base + 1], srcOutputs[base + 2]);
|
|
101
|
+
retargetRotation(q, remap, q);
|
|
102
|
+
newOutputs.set(q, f * 4);
|
|
103
|
+
}
|
|
104
|
+
return new NodeRotationTrack(new Interpolator(src.interpolator.mode === 'step' ? 'step' : 'linear', 'quat', new Float32Array(srcInputs), newOutputs));
|
|
105
|
+
}
|
|
106
|
+
function retargetTranslationTrack(src, remap) {
|
|
107
|
+
const isCubic = src.interpolator.mode === 'cubicspline';
|
|
108
|
+
const frameStride = isCubic ? 9 : 3;
|
|
109
|
+
const numFrames = src.interpolator.inputs.length;
|
|
110
|
+
const srcOutputs = src.interpolator.outputs;
|
|
111
|
+
const newOutputs = new Float32Array(srcOutputs.length);
|
|
112
|
+
const t = isCubic ? 3 : 0;
|
|
113
|
+
const v = new Vector3();
|
|
114
|
+
for(let f = 0; f < numFrames; f++){
|
|
115
|
+
const base = f * frameStride;
|
|
116
|
+
v.setXYZ(srcOutputs[base + t], srcOutputs[base + t + 1], srcOutputs[base + t + 2]);
|
|
117
|
+
retargetTranslationValue(v, remap, v);
|
|
118
|
+
if (isCubic) {
|
|
119
|
+
v.setXYZ(srcOutputs[base], srcOutputs[base + 1], srcOutputs[base + 2]);
|
|
120
|
+
newOutputs.set(retargetTranslationTangent(v, remap, v), base);
|
|
121
|
+
v.setXYZ(srcOutputs[base + 3], srcOutputs[base + 4], srcOutputs[base + 5]);
|
|
122
|
+
newOutputs.set(retargetTranslationValue(v, remap, v), base + 3);
|
|
123
|
+
v.setXYZ(srcOutputs[base + 6], srcOutputs[base + 7], srcOutputs[base + 8]);
|
|
124
|
+
newOutputs.set(retargetTranslationTangent(v, remap, v), base + 6);
|
|
125
|
+
} else {
|
|
126
|
+
newOutputs.set(v, base);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return new NodeTranslationTrack(new Interpolator(src.interpolator.mode, 'vec3', new Float32Array(src.interpolator.inputs), newOutputs));
|
|
130
|
+
}
|
|
131
|
+
function findRotationTrack(tracks) {
|
|
132
|
+
return tracks?.find((track)=>track instanceof NodeRotationTrack || track instanceof NodeEulerRotationTrack) ?? null;
|
|
133
|
+
}
|
|
134
|
+
function collectTrackTimes(track, times) {
|
|
135
|
+
if (track instanceof NodeRotationTrack || track instanceof NodeEulerRotationTrack) {
|
|
136
|
+
for (const time of track.interpolator.inputs){
|
|
137
|
+
times.add(time);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function sampleRotationTrack(track, time, fallback, out) {
|
|
142
|
+
if (!track) {
|
|
143
|
+
out.set(fallback);
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
const state = track.calculateState({}, time);
|
|
147
|
+
out.set(state);
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
function bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRootRotation, dstRootRotation, dstClip, remaps) {
|
|
151
|
+
const rotationTracksByRemap = new Map();
|
|
152
|
+
const times = new Set([
|
|
153
|
+
0,
|
|
154
|
+
sourceClip.timeDuration
|
|
155
|
+
]);
|
|
156
|
+
const remapsByDstNode = new Map();
|
|
157
|
+
const srcJointSet = new Set(srcSkeleton.joints);
|
|
158
|
+
const dstJointSet = new Set(dstSkeleton.joints);
|
|
159
|
+
const srcBindRotByNode = new Map();
|
|
160
|
+
const dstBindRotByNode = new Map();
|
|
161
|
+
const srcRotationTrackByNode = new Map();
|
|
162
|
+
for(let i = 0; i < srcSkeleton.joints.length; i++){
|
|
163
|
+
const joint = srcSkeleton.joints[i];
|
|
164
|
+
srcBindRotByNode.set(joint, srcSkeleton.bindPose[i].rotation);
|
|
165
|
+
const track = findRotationTrack(sourceClip.tracks.get(joint));
|
|
166
|
+
srcRotationTrackByNode.set(joint, track);
|
|
167
|
+
collectTrackTimes(track, times);
|
|
168
|
+
}
|
|
169
|
+
for(let i = 0; i < dstSkeleton.joints.length; i++){
|
|
170
|
+
dstBindRotByNode.set(dstSkeleton.joints[i], dstSkeleton.bindPose[i].rotation);
|
|
171
|
+
}
|
|
172
|
+
for (const remap of remaps){
|
|
173
|
+
const track = findRotationTrack(sourceClip.tracks.get(remap.srcNode));
|
|
174
|
+
rotationTracksByRemap.set(remap, track);
|
|
175
|
+
remapsByDstNode.set(remap.dstNode, remap);
|
|
176
|
+
}
|
|
177
|
+
const inputs = new Float32Array([
|
|
178
|
+
...times
|
|
179
|
+
].sort((a, b)=>a - b));
|
|
180
|
+
const srcBindWorldRots = new Map();
|
|
181
|
+
const dstBindWorldRots = new Map();
|
|
182
|
+
const srcAnimWorldRots = new Map();
|
|
183
|
+
const dstAnimWorldRots = new Map();
|
|
184
|
+
const outputsByRemap = new Map();
|
|
185
|
+
const tmpLocalRot = new Quaternion();
|
|
186
|
+
const tmpWorldDelta = new Quaternion();
|
|
187
|
+
const tmpParentInv = new Quaternion();
|
|
188
|
+
for (const remap of remaps){
|
|
189
|
+
outputsByRemap.set(remap, new Float32Array(inputs.length * 4));
|
|
190
|
+
}
|
|
191
|
+
function getSrcBindWorldRot(node) {
|
|
192
|
+
let rot = srcBindWorldRots.get(node);
|
|
193
|
+
if (!rot) {
|
|
194
|
+
rot = (srcBindRotByNode.get(node) ?? Quaternion.identity()).clone();
|
|
195
|
+
const parent = node.parent;
|
|
196
|
+
if (parent && srcJointSet.has(parent)) {
|
|
197
|
+
Quaternion.multiply(getSrcBindWorldRot(parent), rot, rot);
|
|
198
|
+
} else {
|
|
199
|
+
Quaternion.multiply(srcRootRotation, rot, rot);
|
|
200
|
+
}
|
|
201
|
+
srcBindWorldRots.set(node, rot);
|
|
202
|
+
}
|
|
203
|
+
return rot;
|
|
204
|
+
}
|
|
205
|
+
function getDstBindWorldRot(node) {
|
|
206
|
+
let rot = dstBindWorldRots.get(node);
|
|
207
|
+
if (!rot) {
|
|
208
|
+
rot = (dstBindRotByNode.get(node) ?? Quaternion.identity()).clone();
|
|
209
|
+
const parent = node.parent;
|
|
210
|
+
if (parent && dstJointSet.has(parent)) {
|
|
211
|
+
Quaternion.multiply(getDstBindWorldRot(parent), rot, rot);
|
|
212
|
+
} else {
|
|
213
|
+
Quaternion.multiply(dstRootRotation, rot, rot);
|
|
214
|
+
}
|
|
215
|
+
dstBindWorldRots.set(node, rot);
|
|
216
|
+
}
|
|
217
|
+
return rot;
|
|
218
|
+
}
|
|
219
|
+
function getSrcAnimWorldRot(node, time) {
|
|
220
|
+
let rot = srcAnimWorldRots.get(node);
|
|
221
|
+
if (!rot) {
|
|
222
|
+
rot = new Quaternion();
|
|
223
|
+
srcAnimWorldRots.set(node, rot);
|
|
224
|
+
}
|
|
225
|
+
const parent = node.parent;
|
|
226
|
+
const parentWorldRot = parent && srcJointSet.has(parent) ? getSrcAnimWorldRot(parent, time) : null;
|
|
227
|
+
sampleRotationTrack(srcRotationTrackByNode.get(node) ?? null, time, srcBindRotByNode.get(node) ?? Quaternion.identity(), tmpLocalRot);
|
|
228
|
+
if (parentWorldRot) {
|
|
229
|
+
Quaternion.multiply(parentWorldRot, tmpLocalRot, rot);
|
|
230
|
+
} else {
|
|
231
|
+
Quaternion.multiply(srcRootRotation, tmpLocalRot, rot);
|
|
232
|
+
}
|
|
233
|
+
return rot;
|
|
234
|
+
}
|
|
235
|
+
function getDstAnimWorldRot(remap, time) {
|
|
236
|
+
let rot = dstAnimWorldRots.get(remap.dstNode);
|
|
237
|
+
if (!rot) {
|
|
238
|
+
rot = new Quaternion();
|
|
239
|
+
dstAnimWorldRots.set(remap.dstNode, rot);
|
|
240
|
+
}
|
|
241
|
+
const srcBindWorldRot = getSrcBindWorldRot(remap.srcNode);
|
|
242
|
+
const srcAnimWorldRot = getSrcAnimWorldRot(remap.srcNode, time);
|
|
243
|
+
Quaternion.multiply(srcAnimWorldRot, Quaternion.inverse(srcBindWorldRot, tmpWorldDelta), tmpWorldDelta);
|
|
244
|
+
Quaternion.multiply(tmpWorldDelta, getDstBindWorldRot(remap.dstNode), rot);
|
|
245
|
+
return rot;
|
|
246
|
+
}
|
|
247
|
+
function getDstParentAnimWorldRot(node, time) {
|
|
248
|
+
const parent = node.parent;
|
|
249
|
+
if (!parent || !dstJointSet.has(parent)) {
|
|
250
|
+
return dstRootRotation;
|
|
251
|
+
}
|
|
252
|
+
const mappedParent = remapsByDstNode.get(parent);
|
|
253
|
+
if (mappedParent) {
|
|
254
|
+
return getDstAnimWorldRot(mappedParent, time);
|
|
255
|
+
}
|
|
256
|
+
let rot = dstAnimWorldRots.get(parent);
|
|
257
|
+
if (!rot) {
|
|
258
|
+
rot = (dstBindRotByNode.get(parent) ?? Quaternion.identity()).clone();
|
|
259
|
+
const parentWorldRot = getDstParentAnimWorldRot(parent, time);
|
|
260
|
+
if (parentWorldRot) {
|
|
261
|
+
Quaternion.multiply(parentWorldRot, rot, rot);
|
|
262
|
+
}
|
|
263
|
+
dstAnimWorldRots.set(parent, rot);
|
|
264
|
+
}
|
|
265
|
+
return rot;
|
|
266
|
+
}
|
|
267
|
+
for(let i = 0; i < inputs.length; i++){
|
|
268
|
+
const time = inputs[i];
|
|
269
|
+
srcAnimWorldRots.clear();
|
|
270
|
+
dstAnimWorldRots.clear();
|
|
271
|
+
for (const remap of remaps){
|
|
272
|
+
const dstAnimWorldRot = getDstAnimWorldRot(remap, time);
|
|
273
|
+
const parentWorldRot = getDstParentAnimWorldRot(remap.dstNode, time);
|
|
274
|
+
if (parentWorldRot) {
|
|
275
|
+
Quaternion.multiply(Quaternion.inverse(parentWorldRot, tmpParentInv), dstAnimWorldRot, tmpLocalRot);
|
|
276
|
+
} else {
|
|
277
|
+
tmpLocalRot.set(dstAnimWorldRot);
|
|
278
|
+
}
|
|
279
|
+
tmpLocalRot.inplaceNormalize();
|
|
280
|
+
outputsByRemap.get(remap).set(tmpLocalRot, i * 4);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
for (const remap of remaps){
|
|
284
|
+
if (!rotationTracksByRemap.get(remap)) {
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const outputs = new Float32Array(outputsByRemap.get(remap));
|
|
288
|
+
const track = new NodeRotationTrack(new Interpolator('linear', 'quat', new Float32Array(inputs), outputs));
|
|
289
|
+
track.name = 'rotation';
|
|
290
|
+
track.target = remap.dstNode.persistentId;
|
|
291
|
+
track.jointIndex = remap.dstJointIndex;
|
|
292
|
+
dstClip.addTrack(remap.dstNode, track);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
9
295
|
/**
|
|
10
296
|
* Animation set
|
|
11
297
|
*
|
|
@@ -332,6 +618,185 @@ import { Skeleton } from './skeleton.js';
|
|
|
332
618
|
}
|
|
333
619
|
}
|
|
334
620
|
/**
|
|
621
|
+
* Copy a humanoid animation clip from another AnimationSet into this one via humanoid rig mapping.
|
|
622
|
+
*
|
|
623
|
+
* Prerequisites:
|
|
624
|
+
* - Both source and destination skeletons must have a non-null `humanoidJointMapping`.
|
|
625
|
+
* - Joints are matched by shared `HumanoidBodyRig` / `HumanoidHandRig` keys instead of joint names.
|
|
626
|
+
* - The source clip must exist in `sourceSet` and must be driven by exactly one skeleton.
|
|
627
|
+
*
|
|
628
|
+
* @param sourceSet - The AnimationSet to copy from.
|
|
629
|
+
* @param animationName - Name of the clip to copy.
|
|
630
|
+
* @param targetName - Name for the new clip in this set. Defaults to `animationName`.
|
|
631
|
+
* @returns The newly created AnimationClip, or null on failure.
|
|
632
|
+
*/ copyHumanoidAnimationFrom(sourceSet, animationName, targetName) {
|
|
633
|
+
const destName = targetName ?? animationName;
|
|
634
|
+
const sourceClip = sourceSet.get(animationName);
|
|
635
|
+
if (!sourceClip) {
|
|
636
|
+
console.error(`copyHumanoidAnimationFrom: animation '${animationName}' not found in source set`);
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
if (this._animations[destName]) {
|
|
640
|
+
console.error(`copyHumanoidAnimationFrom: animation '${destName}' already exists in target set`);
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
if (sourceClip.skeletons.size !== 1) {
|
|
644
|
+
console.error(`copyHumanoidAnimationFrom: source animation clip must be affected by exactly one skeleton`);
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
const srcSkeletonId = [
|
|
648
|
+
...sourceClip.skeletons
|
|
649
|
+
][0];
|
|
650
|
+
const srcSkeleton = Skeleton.findSkeletonById(srcSkeletonId);
|
|
651
|
+
if (!srcSkeleton) {
|
|
652
|
+
console.error(`copyHumanoidAnimationFrom: source skeleton '${srcSkeletonId}' not found`);
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
// Check that source skeleton has humanoid mapping
|
|
656
|
+
const srcHumanoidMapping = srcSkeleton.humanoidJointMapping;
|
|
657
|
+
if (!srcHumanoidMapping) {
|
|
658
|
+
console.error(`copyHumanoidAnimationFrom: source skeleton does not have a humanoid joint mapping`);
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
const nodeMap = new Map();
|
|
662
|
+
const jointRemapBySrcNode = new Map();
|
|
663
|
+
// Find a destination skeleton that has a humanoid mapping and share at least the body rig keys
|
|
664
|
+
let srcJointsFiltered = [];
|
|
665
|
+
let dstJointsFiltered = [];
|
|
666
|
+
const dstSkeleton = this._skeletons.map((ref)=>ref.get()).find((sk)=>{
|
|
667
|
+
if (!sk) {
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
// Check that destination skeleton has humanoid mapping
|
|
671
|
+
const dstHumanoidMapping = sk.humanoidJointMapping;
|
|
672
|
+
if (!dstHumanoidMapping) {
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
675
|
+
// Collect matched (srcJoint, dstJoint) pairs via shared humanoid rig keys
|
|
676
|
+
const srcMatched = [];
|
|
677
|
+
const dstMatched = [];
|
|
678
|
+
// Match body rig joints
|
|
679
|
+
for (const key of Object.keys(srcHumanoidMapping.body)){
|
|
680
|
+
const srcJoint = srcHumanoidMapping.body[key];
|
|
681
|
+
const dstJoint = dstHumanoidMapping.body[key];
|
|
682
|
+
if (srcJoint && dstJoint) {
|
|
683
|
+
srcMatched.push(srcJoint);
|
|
684
|
+
dstMatched.push(dstJoint);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
// Match left hand rig joints only when both sides define them; skip silently if either is absent
|
|
688
|
+
if (srcHumanoidMapping.leftHand && dstHumanoidMapping.leftHand) {
|
|
689
|
+
for (const key of Object.keys(srcHumanoidMapping.leftHand)){
|
|
690
|
+
const srcJoint = srcHumanoidMapping.leftHand[key];
|
|
691
|
+
const dstJoint = dstHumanoidMapping.leftHand[key];
|
|
692
|
+
if (srcJoint && dstJoint) {
|
|
693
|
+
srcMatched.push(srcJoint);
|
|
694
|
+
dstMatched.push(dstJoint);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
// Match right hand rig joints only when both sides define them; skip silently if either is absent
|
|
699
|
+
if (srcHumanoidMapping.rightHand && dstHumanoidMapping.rightHand) {
|
|
700
|
+
for (const key of Object.keys(srcHumanoidMapping.rightHand)){
|
|
701
|
+
const srcJoint = srcHumanoidMapping.rightHand[key];
|
|
702
|
+
const dstJoint = dstHumanoidMapping.rightHand[key];
|
|
703
|
+
if (srcJoint && dstJoint) {
|
|
704
|
+
srcMatched.push(srcJoint);
|
|
705
|
+
dstMatched.push(dstJoint);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
if (srcMatched.length === 0) {
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
srcJointsFiltered = srcMatched;
|
|
713
|
+
dstJointsFiltered = dstMatched;
|
|
714
|
+
return true;
|
|
715
|
+
});
|
|
716
|
+
if (!dstSkeleton) {
|
|
717
|
+
console.error(`copyHumanoidAnimationFrom: no matching humanoid skeleton in target set for '${srcSkeletonId}'`);
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
// Compute the world-space rotation of the parent chain above the Hips joint,
|
|
721
|
+
// walking ALL ancestor nodes up to (but not including) the model root.
|
|
722
|
+
// This must include non-joint nodes such as "Armature" in Mixamo rigs, which
|
|
723
|
+
// carry a 90° X rotation that is NOT captured by humanoidRootRotation (which
|
|
724
|
+
// only traverses nodes inside the skeleton joints list).
|
|
725
|
+
function computeHipsParentChainRotation(hipsNode, modelRoot, skeleton) {
|
|
726
|
+
const result = Quaternion.identity();
|
|
727
|
+
const jointSet = new Set(skeleton.joints);
|
|
728
|
+
let p = hipsNode.parent;
|
|
729
|
+
while(p && p !== modelRoot){
|
|
730
|
+
if (!jointSet.has(p)) {
|
|
731
|
+
Quaternion.multiply(p.rotation, result, result);
|
|
732
|
+
}
|
|
733
|
+
p = p.parent;
|
|
734
|
+
}
|
|
735
|
+
return result;
|
|
736
|
+
}
|
|
737
|
+
const srcHipsNode = srcSkeleton.humanoidJointMapping.body[HumanoidBodyRig.Hips];
|
|
738
|
+
const dstHipsNode = dstSkeleton.humanoidJointMapping.body[HumanoidBodyRig.Hips];
|
|
739
|
+
const srcRootRot = computeHipsParentChainRotation(srcHipsNode, sourceSet.model, srcSkeleton);
|
|
740
|
+
const dstRootRot = computeHipsParentChainRotation(dstHipsNode, this._model, dstSkeleton);
|
|
741
|
+
const hipsTranslationRotation = Quaternion.multiply(Quaternion.inverse(dstRootRot), srcRootRot);
|
|
742
|
+
const jointRemaps = [];
|
|
743
|
+
const mappedSrcNodes = new Set();
|
|
744
|
+
const mappedDstNodes = new Set();
|
|
745
|
+
// Build remap for matched joint pairs
|
|
746
|
+
for(let fi = 0; fi < srcJointsFiltered.length; fi++){
|
|
747
|
+
const srcJoint = srcJointsFiltered[fi];
|
|
748
|
+
const dstJoint = dstJointsFiltered[fi];
|
|
749
|
+
if (mappedSrcNodes.has(srcJoint) || mappedDstNodes.has(dstJoint)) {
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
mappedSrcNodes.add(srcJoint);
|
|
753
|
+
mappedDstNodes.add(dstJoint);
|
|
754
|
+
const isHipsBone = srcJoint === srcHipsNode;
|
|
755
|
+
nodeMap.set(srcJoint, dstJoint);
|
|
756
|
+
const remap = createJointRetargetRemap(srcSkeleton, dstSkeleton, srcJoint, dstJoint, isHipsBone ? hipsTranslationRotation : undefined);
|
|
757
|
+
jointRemaps.push(remap);
|
|
758
|
+
jointRemapBySrcNode.set(srcJoint, remap);
|
|
759
|
+
}
|
|
760
|
+
const dstClip = this.createAnimation(destName);
|
|
761
|
+
if (!dstClip) {
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
dstClip.timeDuration = sourceClip.timeDuration;
|
|
765
|
+
dstClip.weight = sourceClip.weight;
|
|
766
|
+
dstClip.autoPlay = sourceClip.autoPlay;
|
|
767
|
+
// Register destination skeleton
|
|
768
|
+
dstClip.addSkeleton(dstSkeleton.persistentId);
|
|
769
|
+
bakeHumanoidRotationTracks(sourceClip, srcSkeleton, dstSkeleton, srcRootRot, dstRootRot, dstClip, jointRemaps);
|
|
770
|
+
for (const srcNode of srcJointsFiltered){
|
|
771
|
+
const srcTracks = sourceClip.tracks.get(srcNode);
|
|
772
|
+
if (!srcTracks) {
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
const dstNode = nodeMap.get(srcNode);
|
|
776
|
+
const remap = jointRemapBySrcNode.get(srcNode);
|
|
777
|
+
for (const srcTrack of srcTracks){
|
|
778
|
+
let dstTrack;
|
|
779
|
+
if (srcTrack instanceof NodeRotationTrack) {
|
|
780
|
+
continue;
|
|
781
|
+
} else if (srcTrack instanceof NodeEulerRotationTrack) {
|
|
782
|
+
continue;
|
|
783
|
+
} else if (srcTrack instanceof NodeTranslationTrack) {
|
|
784
|
+
dstTrack = retargetTranslationTrack(srcTrack, remap);
|
|
785
|
+
} else if (srcTrack instanceof NodeScaleTrack) {
|
|
786
|
+
dstTrack = new NodeScaleTrack(cloneInterpolator(srcTrack.interpolator));
|
|
787
|
+
} else {
|
|
788
|
+
console.warn(`copyHumanoidAnimationFrom: unsupported track type '${srcTrack.constructor.name}', skipping`);
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
dstTrack.name = srcTrack.name;
|
|
792
|
+
dstTrack.target = dstNode.persistentId;
|
|
793
|
+
dstTrack.jointIndex = remap.dstJointIndex;
|
|
794
|
+
dstClip.addTrack(dstNode, dstTrack);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return dstClip;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
335
800
|
* Copy an animation clip from another AnimationSet into this one.
|
|
336
801
|
*
|
|
337
802
|
* Prerequisites:
|
|
@@ -355,45 +820,62 @@ import { Skeleton } from './skeleton.js';
|
|
|
355
820
|
console.error(`copyAnimationFrom: animation '${destName}' already exists in target set`);
|
|
356
821
|
return null;
|
|
357
822
|
}
|
|
823
|
+
if (sourceClip.skeletons.size !== 1) {
|
|
824
|
+
console.error(`copyAnimationFrom: source animation clip must be affected by exactly one skeleton`);
|
|
825
|
+
return null;
|
|
826
|
+
}
|
|
827
|
+
const srcSkeletonId = [
|
|
828
|
+
...sourceClip.skeletons
|
|
829
|
+
][0];
|
|
830
|
+
const srcSkeleton = Skeleton.findSkeletonById(srcSkeletonId);
|
|
831
|
+
if (!srcSkeleton) {
|
|
832
|
+
console.error(`copyAnimationFrom: source skeleton '${srcSkeletonId}' not found`);
|
|
833
|
+
return null;
|
|
834
|
+
}
|
|
358
835
|
const nodeMap = new Map();
|
|
359
836
|
const jointRemapBySrcNode = new Map();
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
837
|
+
const jointsFiltered = srcSkeleton.joints.filter((j)=>!excludeJoint?.(j.name));
|
|
838
|
+
const srcRootNode = findRootJoint(jointsFiltered);
|
|
839
|
+
if (!srcRootNode) {
|
|
840
|
+
console.error(`copyAnimationFrom: cannot determine the root joint for source skeleton`);
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
// Build filtered joint lists for matching (exclude joints rejected by filterJoint)
|
|
844
|
+
const srcJointsFiltered = sortJoints(srcRootNode, jointsFiltered);
|
|
845
|
+
if (!srcJointsFiltered) {
|
|
846
|
+
console.error(`copyAnimationFrom: invalid source skeleton structure`);
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
let dstJointsFiltered = [];
|
|
850
|
+
const dstSkeleton = this._skeletons.map((ref)=>ref.get()).find((sk)=>{
|
|
851
|
+
if (!sk) {
|
|
852
|
+
return false;
|
|
365
853
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (!sk) {
|
|
370
|
-
return false;
|
|
371
|
-
}
|
|
372
|
-
const dstJointsFiltered = sk.joints.filter((j)=>!excludeJoint?.(j.name));
|
|
373
|
-
return dstJointsFiltered.length === srcJointsFiltered.length && srcJointsFiltered.every((j, i)=>j.name === dstJointsFiltered[i].name);
|
|
374
|
-
});
|
|
375
|
-
if (!dstSkeleton) {
|
|
376
|
-
console.error(`copyAnimationFrom: no matching skeleton in target set for '${srcSkeletonId}'`);
|
|
377
|
-
return null;
|
|
854
|
+
const jointsFiltered = sk.joints.filter((j)=>!excludeJoint?.(j.name));
|
|
855
|
+
if (jointsFiltered.length !== srcJointsFiltered.length) {
|
|
856
|
+
return false;
|
|
378
857
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const dstLen = dstSkeleton.bindPose[di].position.magnitude;
|
|
388
|
-
const translationScale = srcLen > 1e-6 ? dstLen / srcLen : 1;
|
|
389
|
-
nodeMap.set(srcJoint, dstJoint);
|
|
390
|
-
jointRemapBySrcNode.set(srcJoint, {
|
|
391
|
-
dstNode: dstJoint,
|
|
392
|
-
srcBindRot: srcSkeleton.bindPose[si].rotation,
|
|
393
|
-
dstBindRot: dstSkeleton.bindPose[di].rotation,
|
|
394
|
-
translationScale
|
|
395
|
-
});
|
|
858
|
+
const rootNode = findRootJoint(jointsFiltered);
|
|
859
|
+
if (!rootNode) {
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
const sortedJointsFiltered = sortJoints(rootNode, jointsFiltered);
|
|
863
|
+
if (sortedJointsFiltered && srcJointsFiltered.every((j, i)=>j.name === sortedJointsFiltered[i].name)) {
|
|
864
|
+
dstJointsFiltered = sortedJointsFiltered;
|
|
865
|
+
return true;
|
|
396
866
|
}
|
|
867
|
+
return false;
|
|
868
|
+
});
|
|
869
|
+
if (!dstJointsFiltered || !dstSkeleton) {
|
|
870
|
+
console.error(`copyAnimationFrom: no matching skeleton in target set for '${srcSkeletonId}'`);
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
// Build remap only for joints that pass the filter
|
|
874
|
+
for(let fi = 0; fi < srcJointsFiltered.length; fi++){
|
|
875
|
+
const srcJoint = srcJointsFiltered[fi];
|
|
876
|
+
const dstJoint = dstJointsFiltered[fi];
|
|
877
|
+
nodeMap.set(srcJoint, dstJoint);
|
|
878
|
+
jointRemapBySrcNode.set(srcJoint, createJointRetargetRemap(srcSkeleton, dstSkeleton, srcJoint, dstJoint));
|
|
397
879
|
}
|
|
398
880
|
const dstClip = this.createAnimation(destName);
|
|
399
881
|
if (!dstClip) {
|
|
@@ -402,24 +884,16 @@ import { Skeleton } from './skeleton.js';
|
|
|
402
884
|
dstClip.timeDuration = sourceClip.timeDuration;
|
|
403
885
|
dstClip.weight = sourceClip.weight;
|
|
404
886
|
dstClip.autoPlay = sourceClip.autoPlay;
|
|
405
|
-
// Register destination skeleton
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
dstClip.addSkeleton(dstSkeleton.persistentId);
|
|
887
|
+
// Register destination skeleton
|
|
888
|
+
dstClip.addSkeleton(dstSkeleton.persistentId);
|
|
889
|
+
for (const srcNode of srcJointsFiltered){
|
|
890
|
+
const srcTracks = sourceClip.tracks.get(srcNode);
|
|
891
|
+
if (!srcTracks) {
|
|
892
|
+
console.error(`copyAnimationFrom: no track for joint: ${srcNode.name}`);
|
|
893
|
+
return null;
|
|
413
894
|
}
|
|
414
|
-
}
|
|
415
|
-
const tmpSrcBind = new Quaternion();
|
|
416
|
-
const tmpDstBindInv = new Quaternion();
|
|
417
|
-
for (const [srcNode, srcTracks] of sourceClip.tracks){
|
|
418
895
|
const dstNode = nodeMap.get(srcNode);
|
|
419
|
-
|
|
420
|
-
continue;
|
|
421
|
-
}
|
|
422
|
-
const remap = jointRemapBySrcNode.get(srcNode) ?? null;
|
|
896
|
+
const remap = jointRemapBySrcNode.get(srcNode);
|
|
423
897
|
for (const srcTrack of srcTracks){
|
|
424
898
|
let dstTrack;
|
|
425
899
|
if (srcTrack instanceof NodeRotationTrack) {
|
|
@@ -435,92 +909,53 @@ import { Skeleton } from './skeleton.js';
|
|
|
435
909
|
continue;
|
|
436
910
|
}
|
|
437
911
|
dstTrack.name = srcTrack.name;
|
|
438
|
-
dstTrack.target =
|
|
439
|
-
dstTrack.jointIndex =
|
|
912
|
+
dstTrack.target = dstNode.persistentId;
|
|
913
|
+
dstTrack.jointIndex = remap.dstJointIndex;
|
|
440
914
|
dstClip.addTrack(dstNode, dstTrack);
|
|
441
915
|
}
|
|
442
916
|
}
|
|
443
917
|
return dstClip;
|
|
444
|
-
function
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
918
|
+
function findRootJoint(joints) {
|
|
919
|
+
let root = null;
|
|
920
|
+
for (const joint of joints){
|
|
921
|
+
if (!root) {
|
|
922
|
+
root = joint;
|
|
923
|
+
}
|
|
924
|
+
while(!root.isParentOf(joint)){
|
|
925
|
+
root = root.parent;
|
|
926
|
+
}
|
|
927
|
+
if (!root) {
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
453
930
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
function cloneInterpolator(src) {
|
|
457
|
-
return new Interpolator(src.mode, src.target, src.inputs instanceof Float32Array ? new Float32Array(src.inputs) : [
|
|
458
|
-
...src.inputs
|
|
459
|
-
], src.outputs instanceof Float32Array ? new Float32Array(src.outputs) : [
|
|
460
|
-
...src.outputs
|
|
461
|
-
]);
|
|
462
|
-
}
|
|
463
|
-
function retargetRotationTrack(src, remap) {
|
|
464
|
-
if (!remap) {
|
|
465
|
-
return new NodeRotationTrack(cloneInterpolator(src.interpolator));
|
|
931
|
+
if (!root || !joints.includes(root)) {
|
|
932
|
+
return null;
|
|
466
933
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
for(let f = 0; f < numFrames; f++){
|
|
476
|
-
const base = f * frameStride;
|
|
477
|
-
if (isCubic) {
|
|
478
|
-
// layout per frame: [inTangent×4, value×4, outTangent×4] — only retarget value
|
|
479
|
-
newOutputs.set(srcOutputs.subarray(base, base + 4), base);
|
|
480
|
-
q.set(srcOutputs.subarray(base + 4, base + 8));
|
|
481
|
-
Quaternion.multiply(tmpSrcBind, q, q);
|
|
482
|
-
Quaternion.multiply(tmpDstBindInv, q, q);
|
|
483
|
-
newOutputs[base + 4] = q.x;
|
|
484
|
-
newOutputs[base + 5] = q.y;
|
|
485
|
-
newOutputs[base + 6] = q.z;
|
|
486
|
-
newOutputs[base + 7] = q.w;
|
|
487
|
-
newOutputs.set(srcOutputs.subarray(base + 8, base + 12), base + 8);
|
|
488
|
-
} else {
|
|
489
|
-
q.set(srcOutputs.subarray(base, base + 4));
|
|
490
|
-
Quaternion.multiply(tmpSrcBind, q, q);
|
|
491
|
-
Quaternion.multiply(tmpDstBindInv, q, q);
|
|
492
|
-
newOutputs[base] = q.x;
|
|
493
|
-
newOutputs[base + 1] = q.y;
|
|
494
|
-
newOutputs[base + 2] = q.z;
|
|
495
|
-
newOutputs[base + 3] = q.w;
|
|
934
|
+
return root;
|
|
935
|
+
}
|
|
936
|
+
function sortJoints(root, joints) {
|
|
937
|
+
const ordered = [];
|
|
938
|
+
const visited = new Set();
|
|
939
|
+
function visit(joint) {
|
|
940
|
+
if (visited.has(joint)) {
|
|
941
|
+
return true;
|
|
496
942
|
}
|
|
943
|
+
if (!joints.includes(joint)) {
|
|
944
|
+
return false;
|
|
945
|
+
}
|
|
946
|
+
if (joint !== root) {
|
|
947
|
+
visit(joint.parent);
|
|
948
|
+
}
|
|
949
|
+
visited.add(joint);
|
|
950
|
+
ordered.push(joint);
|
|
951
|
+
return true;
|
|
497
952
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const srcInputs = src.interpolator.inputs;
|
|
502
|
-
const srcOutputs = src.interpolator.outputs;
|
|
503
|
-
const numFrames = srcInputs.length;
|
|
504
|
-
const newOutputs = new Float32Array(numFrames * 4);
|
|
505
|
-
const q = new Quaternion();
|
|
506
|
-
if (remap) {
|
|
507
|
-
tmpSrcBind.set(remap.srcBindRot);
|
|
508
|
-
Quaternion.conjugate(remap.dstBindRot, tmpDstBindInv);
|
|
509
|
-
}
|
|
510
|
-
for(let f = 0; f < numFrames; f++){
|
|
511
|
-
const b3 = f * 3;
|
|
512
|
-
const b4 = f * 4;
|
|
513
|
-
q.fromEulerAngle(srcOutputs[b3], srcOutputs[b3 + 1], srcOutputs[b3 + 2]);
|
|
514
|
-
if (remap) {
|
|
515
|
-
Quaternion.multiply(tmpSrcBind, q, q);
|
|
516
|
-
Quaternion.multiply(tmpDstBindInv, q, q);
|
|
953
|
+
for (const joint of joints){
|
|
954
|
+
if (!visit(joint)) {
|
|
955
|
+
return null;
|
|
517
956
|
}
|
|
518
|
-
newOutputs[b4] = q.x;
|
|
519
|
-
newOutputs[b4 + 1] = q.y;
|
|
520
|
-
newOutputs[b4 + 2] = q.z;
|
|
521
|
-
newOutputs[b4 + 3] = q.w;
|
|
522
957
|
}
|
|
523
|
-
return
|
|
958
|
+
return ordered;
|
|
524
959
|
}
|
|
525
960
|
}
|
|
526
961
|
/**
|