@zephyr3d/scene 0.8.2 → 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 +473 -60
  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 +2011 -139
  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 +11728 -8535
  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, 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:
@@ -409,18 +874,8 @@ import { Skeleton } from './skeleton.js';
409
874
  for(let fi = 0; fi < srcJointsFiltered.length; fi++){
410
875
  const srcJoint = srcJointsFiltered[fi];
411
876
  const dstJoint = dstJointsFiltered[fi];
412
- const si = srcSkeleton.joints.indexOf(srcJoint);
413
- const di = dstSkeleton.joints.indexOf(dstJoint);
414
- const srcLen = srcSkeleton.bindPose[si].position.magnitude;
415
- const dstLen = dstSkeleton.bindPose[di].position.magnitude;
416
- const translationScale = srcLen > 1e-6 ? dstLen / srcLen : 1;
417
877
  nodeMap.set(srcJoint, dstJoint);
418
- jointRemapBySrcNode.set(srcJoint, {
419
- dstNode: dstJoint,
420
- srcBindRot: srcSkeleton.bindPose[si].rotation,
421
- dstBindRot: dstSkeleton.bindPose[di].rotation,
422
- translationScale
423
- });
878
+ jointRemapBySrcNode.set(srcJoint, createJointRetargetRemap(srcSkeleton, dstSkeleton, srcJoint, dstJoint));
424
879
  }
425
880
  const dstClip = this.createAnimation(destName);
426
881
  if (!dstClip) {
@@ -438,13 +893,13 @@ import { Skeleton } from './skeleton.js';
438
893
  return null;
439
894
  }
440
895
  const dstNode = nodeMap.get(srcNode);
441
- const remap = jointRemapBySrcNode.get(srcNode) ?? null;
896
+ const remap = jointRemapBySrcNode.get(srcNode);
442
897
  for (const srcTrack of srcTracks){
443
898
  let dstTrack;
444
899
  if (srcTrack instanceof NodeRotationTrack) {
445
- dstTrack = retargetRotationTrack(srcTrack);
900
+ dstTrack = retargetRotationTrack(srcTrack, remap);
446
901
  } else if (srcTrack instanceof NodeEulerRotationTrack) {
447
- dstTrack = retargetEulerToRotationTrack(srcTrack);
902
+ dstTrack = retargetEulerToRotationTrack(srcTrack, remap);
448
903
  } else if (srcTrack instanceof NodeTranslationTrack) {
449
904
  dstTrack = retargetTranslationTrack(srcTrack, remap);
450
905
  } else if (srcTrack instanceof NodeScaleTrack) {
@@ -454,8 +909,8 @@ import { Skeleton } from './skeleton.js';
454
909
  continue;
455
910
  }
456
911
  dstTrack.name = srcTrack.name;
457
- dstTrack.target = srcTrack.target;
458
- dstTrack.jointIndex = srcTrack.jointIndex;
912
+ dstTrack.target = dstNode.persistentId;
913
+ dstTrack.jointIndex = remap.dstJointIndex;
459
914
  dstClip.addTrack(dstNode, dstTrack);
460
915
  }
461
916
  }
@@ -502,48 +957,6 @@ import { Skeleton } from './skeleton.js';
502
957
  }
503
958
  return ordered;
504
959
  }
505
- function retargetTranslationTrack(src, remap) {
506
- if (!remap || Math.abs(remap.translationScale - 1) < 1e-6) {
507
- return new NodeTranslationTrack(cloneInterpolator(src.interpolator));
508
- }
509
- const scale = remap.translationScale;
510
- const srcOutputs = src.interpolator.outputs;
511
- const newOutputs = new Float32Array(srcOutputs.length);
512
- for(let i = 0; i < newOutputs.length; i++){
513
- newOutputs[i] = srcOutputs[i] * scale;
514
- }
515
- return new NodeTranslationTrack(new Interpolator(src.interpolator.mode, 'vec3', new Float32Array(src.interpolator.inputs), newOutputs));
516
- }
517
- function cloneInterpolator(src) {
518
- return new Interpolator(src.mode, src.target, src.inputs instanceof Float32Array ? new Float32Array(src.inputs) : [
519
- ...src.inputs
520
- ], src.outputs instanceof Float32Array ? new Float32Array(src.outputs) : [
521
- ...src.outputs
522
- ]);
523
- }
524
- function retargetRotationTrack(src) {
525
- const isCubic = src.interpolator.mode === 'cubicspline';
526
- const frameStride = isCubic ? 12 : 4;
527
- const numFrames = src.interpolator.inputs.length;
528
- const srcOutputs = src.interpolator.outputs;
529
- const newOutputs = new Float32Array(srcOutputs.length);
530
- for(let f = 0; f < numFrames; f++){
531
- const base = f * frameStride;
532
- newOutputs.set(srcOutputs.subarray(base, base + (isCubic ? 12 : 4)), base);
533
- }
534
- return new NodeRotationTrack(new Interpolator(src.interpolator.mode, 'quat', new Float32Array(src.interpolator.inputs), newOutputs));
535
- }
536
- function retargetEulerToRotationTrack(src) {
537
- const srcInputs = src.interpolator.inputs;
538
- const srcOutputs = src.interpolator.outputs;
539
- const numFrames = srcInputs.length;
540
- const newOutputs = new Float32Array(numFrames * 3);
541
- for(let f = 0; f < numFrames; f++){
542
- const base = f * 3;
543
- newOutputs.set(srcOutputs.subarray(base, base + 3), base);
544
- }
545
- return new NodeRotationTrack(new Interpolator('linear', 'quat', new Float32Array(srcInputs), newOutputs));
546
- }
547
960
  }
548
961
  /**
549
962
  * Dispose the animation set and release owned resources.