@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.
Files changed (241) hide show
  1. package/dist/animation/animationset.js +563 -128
  2. package/dist/animation/animationset.js.map +1 -1
  3. package/dist/animation/cloth/gpu_cloth_system.js +2516 -0
  4. package/dist/animation/cloth/gpu_cloth_system.js.map +1 -0
  5. package/dist/animation/fixed_geometry_cache_track.js +138 -0
  6. package/dist/animation/fixed_geometry_cache_track.js.map +1 -0
  7. package/dist/animation/geometry_cache_utils.js +120 -0
  8. package/dist/animation/geometry_cache_utils.js.map +1 -0
  9. package/dist/animation/ik_modifier.js +9 -1
  10. package/dist/animation/ik_modifier.js.map +1 -1
  11. package/dist/animation/joint_dynamics/collision.js +469 -0
  12. package/dist/animation/joint_dynamics/collision.js.map +1 -0
  13. package/dist/animation/joint_dynamics/constraints.js +329 -0
  14. package/dist/animation/joint_dynamics/constraints.js.map +1 -0
  15. package/dist/animation/joint_dynamics/controller.js +699 -0
  16. package/dist/animation/joint_dynamics/controller.js.map +1 -0
  17. package/dist/animation/joint_dynamics/joint_dynamics_system.js +393 -0
  18. package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -0
  19. package/dist/animation/joint_dynamics/solver.js +794 -0
  20. package/dist/animation/joint_dynamics/solver.js.map +1 -0
  21. package/dist/animation/joint_dynamics/types.js +19 -0
  22. package/dist/animation/joint_dynamics/types.js.map +1 -0
  23. package/dist/animation/joint_dynamics/vrm_spring_bone.js +46 -0
  24. package/dist/animation/joint_dynamics/vrm_spring_bone.js.map +1 -0
  25. package/dist/animation/joint_dynamics_modifier.js +51 -0
  26. package/dist/animation/joint_dynamics_modifier.js.map +1 -0
  27. package/dist/animation/pca_geometry_cache_track.js +222 -0
  28. package/dist/animation/pca_geometry_cache_track.js.map +1 -0
  29. package/dist/animation/skeleton.js +1907 -3
  30. package/dist/animation/skeleton.js.map +1 -1
  31. package/dist/animation/skeleton_modifier.js +3 -5
  32. package/dist/animation/skeleton_modifier.js.map +1 -1
  33. package/dist/animation/spring/multi_chain_spring_system.js +2 -0
  34. package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
  35. package/dist/animation/spring/spring_chain.js +3 -2
  36. package/dist/animation/spring/spring_chain.js.map +1 -1
  37. package/dist/animation/spring/spring_collider.js +35 -5
  38. package/dist/animation/spring/spring_collider.js.map +1 -1
  39. package/dist/animation/spring/spring_particle.js +1 -0
  40. package/dist/animation/spring/spring_particle.js.map +1 -1
  41. package/dist/animation/spring/spring_system.js +325 -28
  42. package/dist/animation/spring/spring_system.js.map +1 -1
  43. package/dist/animation/spring2/collision.js +469 -0
  44. package/dist/animation/spring2/collision.js.map +1 -0
  45. package/dist/animation/spring2/constraints.js +329 -0
  46. package/dist/animation/spring2/constraints.js.map +1 -0
  47. package/dist/animation/spring2/controller.js +434 -0
  48. package/dist/animation/spring2/controller.js.map +1 -0
  49. package/dist/animation/spring2/math.js +16 -0
  50. package/dist/animation/spring2/math.js.map +1 -0
  51. package/dist/animation/spring2/solver.js +624 -0
  52. package/dist/animation/spring2/solver.js.map +1 -0
  53. package/dist/animation/spring2/spring_system.js +118 -0
  54. package/dist/animation/spring2/spring_system.js.map +1 -0
  55. package/dist/animation/spring2/types.js +19 -0
  56. package/dist/animation/spring2/types.js.map +1 -0
  57. package/dist/animation/spring_modifier.js +17 -1
  58. package/dist/animation/spring_modifier.js.map +1 -1
  59. package/dist/app/engine.js +42 -15
  60. package/dist/app/engine.js.map +1 -1
  61. package/dist/app/runtimescript.js +132 -1
  62. package/dist/app/runtimescript.js.map +1 -1
  63. package/dist/app/screen.js +3 -3
  64. package/dist/app/screen.js.map +1 -1
  65. package/dist/app/scriptingsystem.js +69 -37
  66. package/dist/app/scriptingsystem.js.map +1 -1
  67. package/dist/app/scriptregistry.js +157 -146
  68. package/dist/app/scriptregistry.js.map +1 -1
  69. package/dist/asset/assetmanager.js +2 -1
  70. package/dist/asset/assetmanager.js.map +1 -1
  71. package/dist/asset/loaders/zabc/zabc_loader.js +570 -0
  72. package/dist/asset/loaders/zabc/zabc_loader.js.map +1 -0
  73. package/dist/asset/model.js +192 -0
  74. package/dist/asset/model.js.map +1 -1
  75. package/dist/camera/base.js +26 -7
  76. package/dist/camera/base.js.map +1 -1
  77. package/dist/camera/camera.js +146 -24
  78. package/dist/camera/camera.js.map +1 -1
  79. package/dist/camera/fps.js +2 -2
  80. package/dist/camera/fps.js.map +1 -1
  81. package/dist/camera/orbit.js +2 -2
  82. package/dist/camera/orbit.js.map +1 -1
  83. package/dist/index.d.ts +11701 -8506
  84. package/dist/index.js +29 -8
  85. package/dist/index.js.map +1 -1
  86. package/dist/material/lambert.js +1 -1
  87. package/dist/material/lambert.js.map +1 -1
  88. package/dist/material/material.js +9 -3
  89. package/dist/material/material.js.map +1 -1
  90. package/dist/material/meshmaterial.js +54 -7
  91. package/dist/material/meshmaterial.js.map +1 -1
  92. package/dist/material/mixins/lightmodel/blinnphong.js +17 -7
  93. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  94. package/dist/material/mixins/lightmodel/lambert.js +7 -2
  95. package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
  96. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +108 -16
  97. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  98. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +222 -14
  99. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  100. package/dist/material/mixins/lightmodel/pbrspecularglossness.js +24 -12
  101. package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
  102. package/dist/material/mixins/lit.js +11 -8
  103. package/dist/material/mixins/lit.js.map +1 -1
  104. package/dist/material/mixins/pbr/common.js +72 -5
  105. package/dist/material/mixins/pbr/common.js.map +1 -1
  106. package/dist/material/pbrblueprint.js +148 -1
  107. package/dist/material/pbrblueprint.js.map +1 -1
  108. package/dist/material/pbrmr.js +125 -2
  109. package/dist/material/pbrmr.js.map +1 -1
  110. package/dist/material/shader/helper.js +10 -4
  111. package/dist/material/shader/helper.js.map +1 -1
  112. package/dist/material/water.js +2 -2
  113. package/dist/material/water.js.map +1 -1
  114. package/dist/posteffect/coloradjust.js +145 -0
  115. package/dist/posteffect/coloradjust.js.map +1 -0
  116. package/dist/posteffect/taa.js +68 -32
  117. package/dist/posteffect/taa.js.map +1 -1
  118. package/dist/render/cluster_light.js +17 -12
  119. package/dist/render/cluster_light.js.map +1 -1
  120. package/dist/render/deferredlightpass.js +510 -0
  121. package/dist/render/deferredlightpass.js.map +1 -0
  122. package/dist/render/deferredshadowlightpass.js +428 -0
  123. package/dist/render/deferredshadowlightpass.js.map +1 -0
  124. package/dist/render/drawable.js.map +1 -1
  125. package/dist/render/drawable_mixin.js +21 -12
  126. package/dist/render/drawable_mixin.js.map +1 -1
  127. package/dist/render/envlight.js +20 -22
  128. package/dist/render/envlight.js.map +1 -1
  129. package/dist/render/gbufferpass.js +50 -0
  130. package/dist/render/gbufferpass.js.map +1 -0
  131. package/dist/render/lightpass.js +1 -2
  132. package/dist/render/lightpass.js.map +1 -1
  133. package/dist/render/primitive.js +1 -1
  134. package/dist/render/primitive.js.map +1 -1
  135. package/dist/render/render_queue.js +3 -2
  136. package/dist/render/render_queue.js.map +1 -1
  137. package/dist/render/renderer.js +5 -358
  138. package/dist/render/renderer.js.map +1 -1
  139. package/dist/render/rendergraph/executor.js +15 -12
  140. package/dist/render/rendergraph/executor.js.map +1 -1
  141. package/dist/render/rendergraph/forward_plus_builder.js +144 -88
  142. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  143. package/dist/render/rendergraph/history_resource_manager.js +178 -0
  144. package/dist/render/rendergraph/history_resource_manager.js.map +1 -0
  145. package/dist/render/rendergraph/rendergraph.js +12 -1
  146. package/dist/render/rendergraph/rendergraph.js.map +1 -1
  147. package/dist/render/rendergraph/types.js +2 -2
  148. package/dist/render/rendergraph/types.js.map +1 -1
  149. package/dist/render/sky.js +26 -9
  150. package/dist/render/sky.js.map +1 -1
  151. package/dist/scene/basesprite.js +4 -3
  152. package/dist/scene/basesprite.js.map +1 -1
  153. package/dist/scene/batchgroup.js +4 -4
  154. package/dist/scene/environment.js +7 -4
  155. package/dist/scene/environment.js.map +1 -1
  156. package/dist/scene/light.js +184 -18
  157. package/dist/scene/light.js.map +1 -1
  158. package/dist/scene/mesh.js +30 -0
  159. package/dist/scene/mesh.js.map +1 -1
  160. package/dist/scene/particlesys.js +3 -2
  161. package/dist/scene/particlesys.js.map +1 -1
  162. package/dist/scene/raycast_visitor.js +29 -3
  163. package/dist/scene/raycast_visitor.js.map +1 -1
  164. package/dist/scene/scene.js +86 -19
  165. package/dist/scene/scene.js.map +1 -1
  166. package/dist/scene/scene_node.js +87 -31
  167. package/dist/scene/scene_node.js.map +1 -1
  168. package/dist/scene/script_attachment.js +59 -0
  169. package/dist/scene/script_attachment.js.map +1 -0
  170. package/dist/scene/terrain-cm/grass.js +3 -2
  171. package/dist/scene/terrain-cm/grass.js.map +1 -1
  172. package/dist/scene/terrain-cm/terrain-cm.js +4 -3
  173. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  174. package/dist/scene/water.js +3 -2
  175. package/dist/scene/water.js.map +1 -1
  176. package/dist/shaders/shadow.js +2 -2
  177. package/dist/shaders/shadow.js.map +1 -1
  178. package/dist/shadow/shadowmapper.js +74 -4
  179. package/dist/shadow/shadowmapper.js.map +1 -1
  180. package/dist/shapes/box.js +163 -0
  181. package/dist/shapes/box.js.map +1 -1
  182. package/dist/shapes/capsule.js +216 -0
  183. package/dist/shapes/capsule.js.map +1 -0
  184. package/dist/shapes/cylinder.js +91 -0
  185. package/dist/shapes/cylinder.js.map +1 -1
  186. package/dist/shapes/plane.js +32 -0
  187. package/dist/shapes/plane.js.map +1 -1
  188. package/dist/shapes/tetrahedron.js +211 -0
  189. package/dist/shapes/tetrahedron.js.map +1 -1
  190. package/dist/shapes/torus.js +173 -0
  191. package/dist/shapes/torus.js.map +1 -1
  192. package/dist/utility/blueprint/common/math.js +111 -1
  193. package/dist/utility/blueprint/common/math.js.map +1 -1
  194. package/dist/utility/blueprint/material/inputs.js +235 -1
  195. package/dist/utility/blueprint/material/inputs.js.map +1 -1
  196. package/dist/utility/blueprint/material/ir.js +154 -10
  197. package/dist/utility/blueprint/material/ir.js.map +1 -1
  198. package/dist/utility/blueprint/material/texture.js +273 -19
  199. package/dist/utility/blueprint/material/texture.js.map +1 -1
  200. package/dist/utility/serialization/json.js +12 -2
  201. package/dist/utility/serialization/json.js.map +1 -1
  202. package/dist/utility/serialization/manager.js +30 -11
  203. package/dist/utility/serialization/manager.js.map +1 -1
  204. package/dist/utility/serialization/scene/animation.js +250 -7
  205. package/dist/utility/serialization/scene/animation.js.map +1 -1
  206. package/dist/utility/serialization/scene/batch.js +4 -2
  207. package/dist/utility/serialization/scene/batch.js.map +1 -1
  208. package/dist/utility/serialization/scene/camera.js +154 -2
  209. package/dist/utility/serialization/scene/camera.js.map +1 -1
  210. package/dist/utility/serialization/scene/cloth_script.js +834 -0
  211. package/dist/utility/serialization/scene/cloth_script.js.map +1 -0
  212. package/dist/utility/serialization/scene/common.js +7 -0
  213. package/dist/utility/serialization/scene/common.js.map +1 -1
  214. package/dist/utility/serialization/scene/light.js +120 -4
  215. package/dist/utility/serialization/scene/light.js.map +1 -1
  216. package/dist/utility/serialization/scene/material.js +453 -1
  217. package/dist/utility/serialization/scene/material.js.map +1 -1
  218. package/dist/utility/serialization/scene/mesh.js +4 -2
  219. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  220. package/dist/utility/serialization/scene/node.js +298 -10
  221. package/dist/utility/serialization/scene/node.js.map +1 -1
  222. package/dist/utility/serialization/scene/particle.js +4 -2
  223. package/dist/utility/serialization/scene/particle.js.map +1 -1
  224. package/dist/utility/serialization/scene/primitive.js +100 -3
  225. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  226. package/dist/utility/serialization/scene/scene.js +130 -3
  227. package/dist/utility/serialization/scene/scene.js.map +1 -1
  228. package/dist/utility/serialization/scene/script.js +66 -0
  229. package/dist/utility/serialization/scene/script.js.map +1 -0
  230. package/dist/utility/serialization/scene/spring_script.js +596 -0
  231. package/dist/utility/serialization/scene/spring_script.js.map +1 -0
  232. package/dist/utility/serialization/scene/sprite.js +5 -3
  233. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  234. package/dist/utility/serialization/scene/terrain.js +4 -2
  235. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  236. package/dist/utility/serialization/scene/water.js +4 -2
  237. package/dist/utility/serialization/scene/water.js.map +1 -1
  238. package/dist/utility/serialization/types.js.map +1 -1
  239. package/dist/values.js +5 -1
  240. package/dist/values.js.map +1 -1
  241. 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
- for (const srcSkeletonId of sourceClip.skeletons){
361
- const srcSkeleton = Skeleton.findSkeletonById(srcSkeletonId);
362
- if (!srcSkeleton) {
363
- console.error(`copyAnimationFrom: source skeleton '${srcSkeletonId}' not found`);
364
- return null;
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
- // Build filtered joint lists for matching (exclude joints rejected by filterJoint)
367
- const srcJointsFiltered = srcSkeleton.joints.filter((j)=>!excludeJoint?.(j.name));
368
- const dstSkeleton = this._skeletons.map((ref)=>ref.get()).find((sk)=>{
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
- // Build remap only for joints that pass the filter
380
- const dstJointsFiltered = dstSkeleton.joints.filter((j)=>!excludeJoint?.(j.name));
381
- for(let fi = 0; fi < srcJointsFiltered.length; fi++){
382
- const srcJoint = srcJointsFiltered[fi];
383
- const dstJoint = dstJointsFiltered[fi];
384
- const si = srcSkeleton.joints.indexOf(srcJoint);
385
- const di = dstSkeleton.joints.indexOf(dstJoint);
386
- const srcLen = srcSkeleton.bindPose[si].position.magnitude;
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 ids
406
- for (const srcSkeletonId of sourceClip.skeletons){
407
- const srcSkeleton = Skeleton.findSkeletonById(srcSkeletonId);
408
- const firstSrcJoint = srcSkeleton.joints[0];
409
- const firstDstJoint = nodeMap.get(firstSrcJoint);
410
- const dstSkeleton = this._skeletons.map((r)=>r.get()).find((sk)=>sk && sk.joints[0] === firstDstJoint);
411
- if (dstSkeleton) {
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
- if (!dstNode) {
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 = srcTrack.target;
439
- dstTrack.jointIndex = srcTrack.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 retargetTranslationTrack(src, remap) {
445
- if (!remap || Math.abs(remap.translationScale - 1) < 1e-6) {
446
- return new NodeTranslationTrack(cloneInterpolator(src.interpolator));
447
- }
448
- const scale = remap.translationScale;
449
- const srcOutputs = src.interpolator.outputs;
450
- const newOutputs = new Float32Array(srcOutputs.length);
451
- for(let i = 0; i < newOutputs.length; i++){
452
- newOutputs[i] = srcOutputs[i] * scale;
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
- return new NodeTranslationTrack(new Interpolator(src.interpolator.mode, 'vec3', new Float32Array(src.interpolator.inputs), newOutputs));
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
- tmpSrcBind.set(remap.srcBindRot);
468
- Quaternion.conjugate(remap.dstBindRot, tmpDstBindInv);
469
- const isCubic = src.interpolator.mode === 'cubicspline';
470
- const frameStride = isCubic ? 12 : 4;
471
- const numFrames = src.interpolator.inputs.length;
472
- const srcOutputs = src.interpolator.outputs;
473
- const newOutputs = new Float32Array(srcOutputs.length);
474
- const q = new Quaternion();
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
- return new NodeRotationTrack(new Interpolator(src.interpolator.mode, 'quat', new Float32Array(src.interpolator.inputs), newOutputs));
499
- }
500
- function retargetEulerToRotationTrack(src, remap) {
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 new NodeRotationTrack(new Interpolator('linear', 'quat', new Float32Array(srcInputs), newOutputs));
958
+ return ordered;
524
959
  }
525
960
  }
526
961
  /**