@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,38 +1,120 @@
1
- import { Vector3, Disposable, randomUUID, DRef, Matrix4x4, DWeakRef, nextPowerOf2 } from '@zephyr3d/base';
1
+ import { Vector3, Disposable, randomUUID, DRef, Quaternion, DWeakRef, Matrix4x4, nextPowerOf2 } from '@zephyr3d/base';
2
2
  import { BoundingBox } from '../utility/bounding_volume.js';
3
3
  import { getDevice } from '../app/api.js';
4
4
 
5
+ /**
6
+ * Standardized humanoid joint names for consistent skeleton mapping across models and animations.
7
+ *
8
+ * These names align with common conventions used in 3D modeling and animation tools, facilitating interoperability and reuse of assets.
9
+ *
10
+ * @public
11
+ */ var HumanoidBodyRig = /*#__PURE__*/ function(HumanoidBodyRig) {
12
+ HumanoidBodyRig["Hips"] = "Hips";
13
+ HumanoidBodyRig["Spine"] = "Spine";
14
+ HumanoidBodyRig["Chest"] = "Chest";
15
+ HumanoidBodyRig["UpperChest"] = "UpperChest";
16
+ HumanoidBodyRig["Neck"] = "Neck";
17
+ HumanoidBodyRig["Head"] = "Head";
18
+ HumanoidBodyRig["LeftShoulder"] = "LeftShoulder";
19
+ HumanoidBodyRig["LeftUpperArm"] = "LeftUpperArm";
20
+ HumanoidBodyRig["LeftLowerArm"] = "LeftLowerArm";
21
+ HumanoidBodyRig["LeftHand"] = "LeftHand";
22
+ HumanoidBodyRig["RightShoulder"] = "RightShoulder";
23
+ HumanoidBodyRig["RightUpperArm"] = "RightUpperArm";
24
+ HumanoidBodyRig["RightLowerArm"] = "RightLowerArm";
25
+ HumanoidBodyRig["RightHand"] = "RightHand";
26
+ HumanoidBodyRig["LeftUpperLeg"] = "LeftUpperLeg";
27
+ HumanoidBodyRig["LeftLowerLeg"] = "LeftLowerLeg";
28
+ HumanoidBodyRig["LeftFoot"] = "LeftFoot";
29
+ HumanoidBodyRig["LeftToes"] = "LeftToes";
30
+ HumanoidBodyRig["RightUpperLeg"] = "RightUpperLeg";
31
+ HumanoidBodyRig["RightLowerLeg"] = "RightLowerLeg";
32
+ HumanoidBodyRig["RightFoot"] = "RightFoot";
33
+ HumanoidBodyRig["RightToes"] = "RightToes";
34
+ return HumanoidBodyRig;
35
+ }({});
36
+ /**
37
+ * Standardized humanoid hand joint names for consistent skeleton mapping across models and animations.
38
+ *
39
+ * These names align with common conventions used in 3D modeling and animation tools, facilitating interoperability and reuse of assets.
40
+ *
41
+ * @public
42
+ */ var HumanoidHandRig = /*#__PURE__*/ function(HumanoidHandRig) {
43
+ HumanoidHandRig["ThumbProximal"] = "ThumbProximal";
44
+ HumanoidHandRig["ThumbIntermediate"] = "ThumbIntermediate";
45
+ HumanoidHandRig["ThumbDistal"] = "ThumbDistal";
46
+ HumanoidHandRig["IndexProximal"] = "IndexProximal";
47
+ HumanoidHandRig["IndexIntermediate"] = "IndexIntermediate";
48
+ HumanoidHandRig["IndexDistal"] = "IndexDistal";
49
+ HumanoidHandRig["MiddleProximal"] = "MiddleProximal";
50
+ HumanoidHandRig["MiddleIntermediate"] = "MiddleIntermediate";
51
+ HumanoidHandRig["MiddleDistal"] = "MiddleDistal";
52
+ HumanoidHandRig["RingProximal"] = "RingProximal";
53
+ HumanoidHandRig["RingIntermediate"] = "RingIntermediate";
54
+ HumanoidHandRig["RingDistal"] = "RingDistal";
55
+ HumanoidHandRig["PinkyProximal"] = "PinkyProximal";
56
+ HumanoidHandRig["PinkyIntermediate"] = "PinkyIntermediate";
57
+ HumanoidHandRig["PinkyDistal"] = "PinkyDistal";
58
+ return HumanoidHandRig;
59
+ }({});
5
60
  const tmpV0 = new Vector3();
6
61
  const tmpV1 = new Vector3();
7
62
  const tmpV2 = new Vector3();
8
63
  const tmpV3 = new Vector3();
9
- /**
10
- * Skeleton for skinned animation.
11
- *
12
- * Responsibilities:
13
- * - Maintains joint transforms: inverse bind, bind pose, and current skinning matrices.
14
- * - Provides a texture containing joint matrices for GPU skinning.
15
- * - Applies skinning state to associated meshes each frame.
16
- * - Computes animated axis-aligned bounding boxes using representative skinned vertices.
17
- *
18
- * Joint matrix texture layout:
19
- * - Texture format: `rgba32f`.
20
- * - Stored as a 2-layered ring buffer: current and previous joint transforms to support
21
- * temporal addressing if needed. Offsets are tracked in `_jointOffsets[0]` (current)
22
- * and `_jointOffsets[1]` (previous).
23
- *
24
- * Usage:
25
- * - Construct with joints, bind data, meshes and submesh bounding info.
26
- * - Call `apply()` each frame to update joint texture, bind to meshes, and update bounds.
27
- * - Call `reset()` to clear skinning on meshes.
28
- *
29
- * @public
64
+ function jointPattern(all, none, any) {
65
+ return {
66
+ all,
67
+ any: any?.length ? any : undefined,
68
+ none: none?.length ? none : undefined
69
+ };
70
+ }
71
+ function sideJointPatterns(side, patterns) {
72
+ const sideTokens = side === 'left' ? [
73
+ 'left',
74
+ 'l'
75
+ ] : [
76
+ 'right',
77
+ 'r'
78
+ ];
79
+ return sideTokens.flatMap((sideToken)=>patterns.map((pattern)=>({
80
+ all: [
81
+ sideToken,
82
+ ...pattern.all
83
+ ],
84
+ any: pattern.any?.length ? [
85
+ ...pattern.any
86
+ ] : undefined,
87
+ none: pattern.none?.length ? [
88
+ ...pattern.none
89
+ ] : undefined
90
+ })));
91
+ }
92
+ /**
93
+ * Skeleton for skinned animation.
94
+ *
95
+ * Responsibilities:
96
+ * - Maintains joint transforms: inverse bind, bind pose, and current skinning matrices.
97
+ * - Provides a texture containing joint matrices for GPU skinning.
98
+ * - Applies skinning state to associated meshes each frame.
99
+ * - Computes animated axis-aligned bounding boxes using representative skinned vertices.
100
+ *
101
+ * Joint matrix texture layout:
102
+ * - Texture format: `rgba32f`.
103
+ * - Stored as a 2-layered ring buffer: current and previous joint transforms to support
104
+ * temporal addressing if needed. Offsets are tracked in `_jointOffsets[0]` (current)
105
+ * and `_jointOffsets[1]` (previous).
106
+ *
107
+ * Usage:
108
+ * - Construct with joints, bind data, meshes and submesh bounding info.
109
+ * - Call `apply()` each frame to update joint texture, bind to meshes, and update bounds.
110
+ * - Call `reset()` to clear skinning on meshes.
111
+ *
112
+ * @public
30
113
  */ class Skeleton extends Disposable {
31
114
  /** @internal Global weak registry keyed by persistentId for serialization/lookup. */ static _registry = new Map();
32
115
  /** @internal */ _id;
33
116
  /** @internal */ _joints;
34
117
  /** @internal */ _inverseBindMatrices;
35
- /** @internal */ _bindPoseModelSpace;
36
118
  /** @internal */ _bindPose;
37
119
  /** @internal */ _jointMatrices;
38
120
  /** @internal */ _jointOffsets;
@@ -41,12 +123,14 @@ const tmpV3 = new Vector3();
41
123
  /** @internal */ _playing;
42
124
  /** @internal */ _modifiers;
43
125
  /** @internal */ _lastUpdateTime;
44
- /**
45
- * Create a skeleton instance.
46
- *
47
- * @param joints - Joint scene nodes (one per joint), ordered to match skin data.
48
- * @param inverseBindMatrices - Inverse bind matrices for each joint.
49
- * @param bindPoseMatrices - Bind pose matrices for each joint (model-space).
126
+ /** @internal */ _humanoidJointMapping;
127
+ /** @internal */ _humanoidRootRotation;
128
+ /**
129
+ * Create a skeleton instance.
130
+ *
131
+ * @param joints - Joint scene nodes (one per joint), ordered to match skin data.
132
+ * @param inverseBindMatrices - Inverse bind matrices for each joint.
133
+ * @param bindPoseMatrices - Bind pose matrices for each joint (model-space).
50
134
  */ constructor(joints, inverseBindMatrices, bindPose){
51
135
  super();
52
136
  this._id = randomUUID();
@@ -57,29 +141,29 @@ const tmpV3 = new Vector3();
57
141
  this._playing = false;
58
142
  this._modifiers = [];
59
143
  this._lastUpdateTime = 0;
60
- this._bindPoseModelSpace = [];
61
144
  this.computeBindPose();
62
145
  this.updateJointMatrices(0);
63
146
  let skeletonRoot = this.findRootJoint(this._joints);
64
147
  if (!skeletonRoot || !this._joints.includes(skeletonRoot)) {
65
148
  throw new Error('Skeleton root must be included in the joint list');
66
149
  }
67
- while(skeletonRoot.parent && this._joints.includes(skeletonRoot.parent)){
68
- skeletonRoot = skeletonRoot.parent;
69
- }
70
- const im = skeletonRoot.parent.invWorldMatrix;
71
- for (const joint of this._joints){
72
- const m = Matrix4x4.multiplyAffine(im, joint.worldMatrix);
73
- this._bindPoseModelSpace.push(m);
150
+ this._humanoidJointMapping = Skeleton.tryExtractHumanoidJoints(skeletonRoot);
151
+ this._humanoidRootRotation = Quaternion.identity();
152
+ if (this._humanoidJointMapping) {
153
+ let p = this._humanoidJointMapping.body["Hips"];
154
+ while(this._joints.includes(p.parent)){
155
+ Quaternion.multiply(p.parent.rotation, this._humanoidRootRotation, this._humanoidRootRotation);
156
+ p = p.parent;
157
+ }
74
158
  }
75
159
  Skeleton._registry.set(this._id, new DWeakRef(this));
76
160
  }
77
- /**
78
- * Lookup a skeleton from the global registry by persistent id.
79
- *
80
- * @param id - The persistent UUID to search for.
81
- * @returns The skeleton if alive, otherwise `null`.
82
- * @internal
161
+ /**
162
+ * Lookup a skeleton from the global registry by persistent id.
163
+ *
164
+ * @param id - The persistent UUID to search for.
165
+ * @returns The skeleton if alive, otherwise `null`.
166
+ * @internal
83
167
  */ static findSkeletonById(id) {
84
168
  const m = this._registry.get(id);
85
169
  if (m && !m.get()) {
@@ -88,18 +172,21 @@ const tmpV3 = new Vector3();
88
172
  }
89
173
  return m ? m.get() : null;
90
174
  }
91
- /** @internal */ get joints() {
175
+ /** Gets joint nodes */ get joints() {
92
176
  return this._joints;
93
177
  }
178
+ /** Gets the humanoid joint mapping */ get humanoidJointMapping() {
179
+ return this._humanoidJointMapping;
180
+ }
181
+ /** Root rotation of humanoid hips bone */ get humanoidRootRotation() {
182
+ return this._humanoidRootRotation;
183
+ }
94
184
  /** @internal */ get inverseBindMatrices() {
95
185
  return this._inverseBindMatrices;
96
186
  }
97
187
  /** @internal */ get bindPose() {
98
188
  return this._bindPose;
99
189
  }
100
- get bindPoseModelSpace() {
101
- return this._bindPoseModelSpace;
102
- }
103
190
  /** @internal */ get playing() {
104
191
  return this._playing;
105
192
  }
@@ -120,42 +207,42 @@ const tmpV3 = new Vector3();
120
207
  Skeleton._registry.set(this._id, m);
121
208
  }
122
209
  }
123
- /**
124
- * Texture containing joint matrices for GPU skinning.
125
- *
126
- * Each matrix is stored in 4 texels (one row per texel, RGBA = 4 floats).
210
+ /**
211
+ * Texture containing joint matrices for GPU skinning.
212
+ *
213
+ * Each matrix is stored in 4 texels (one row per texel, RGBA = 4 floats).
127
214
  */ get jointTexture() {
128
215
  return this._jointTexture.get();
129
216
  }
130
- /**
131
- * Get joint index by joint node
132
- * @param joint - joint node
133
- * @returns The index of the joint
217
+ /**
218
+ * Get joint index by joint node
219
+ * @param joint - joint node
220
+ * @returns The index of the joint
134
221
  */ getJointIndex(joint) {
135
222
  return this._joints.indexOf(joint);
136
223
  }
137
- /**
138
- * Get joint index by joint name
139
- * @param jointName - joint name
140
- * @returns The index of the joint
224
+ /**
225
+ * Get joint index by joint name
226
+ * @param jointName - joint name
227
+ * @returns The index of the joint
141
228
  */ getJointIndexByName(jointName) {
142
229
  return this._joints.findIndex((joint)=>joint.name === jointName);
143
230
  }
144
- /**
145
- * Update joint matrices from either provided transforms or the joints' world matrices.
146
- *
147
- * - Lazily creates the joint texture and its backing arrays on first call.
148
- * - Advances the ring buffer offset in `_jointOffsets` to write a new "current" set.
149
- * - For each joint:
150
- * - Optionally premultiplies by `worldMatrix` (to transform into model space).
151
- * - Computes skinning matrix: ( M_\{skin\} = M_\{joint\} \\times M_\{inverseBind\} ).
152
- *
153
- * Note: This method only writes into the CPU-side array; callers like `computeJoints()`
154
- * update the GPU texture.
155
- *
156
- * @param jointTransforms - Optional per-joint transforms to use instead of node world matrices.
157
- * @param worldMatrix - Optional world-to-model transform applied before inverse bind.
158
- * @internal
231
+ /**
232
+ * Update joint matrices from either provided transforms or the joints' world matrices.
233
+ *
234
+ * - Lazily creates the joint texture and its backing arrays on first call.
235
+ * - Advances the ring buffer offset in `_jointOffsets` to write a new "current" set.
236
+ * - For each joint:
237
+ * - Optionally premultiplies by `worldMatrix` (to transform into model space).
238
+ * - Computes skinning matrix: ( M_\{skin\} = M_\{joint\} \\times M_\{inverseBind\} ).
239
+ *
240
+ * Note: This method only writes into the CPU-side array; callers like `computeJoints()`
241
+ * update the GPU texture.
242
+ *
243
+ * @param jointTransforms - Optional per-joint transforms to use instead of node world matrices.
244
+ * @param worldMatrix - Optional world-to-model transform applied before inverse bind.
245
+ * @internal
159
246
  */ updateJointMatrices(deltaTime) {
160
247
  this.applyModifiers(deltaTime);
161
248
  if (!this._jointTexture.get()) {
@@ -172,67 +259,68 @@ const tmpV3 = new Vector3();
172
259
  Matrix4x4.multiply(this._joints[i].worldMatrix, this._inverseBindMatrices[i], this._jointMatrices[i + this._jointOffsets[0] - 1]);
173
260
  }
174
261
  }
175
- /**
176
- * Reset skeleton to bind pose
177
- *
178
- * @internal
262
+ /**
263
+ * Reset skeleton to bind pose
264
+ *
265
+ * @internal
179
266
  */ computeBindPose() {
180
267
  for(let i = 0; i < this._joints.length; i++){
181
268
  const joint = this._joints[i];
182
269
  const bindpose = this._bindPose[i];
183
270
  joint.position.set(bindpose.position);
184
271
  joint.rotation.set(bindpose.rotation);
272
+ //joint.rotation.identity();
185
273
  joint.scale.set(bindpose.scale);
186
274
  }
187
275
  }
188
- /**
189
- * Compute current joint matrices from the nodes and upload them to the joint texture.
190
- *
191
- * @internal
276
+ /**
277
+ * Compute current joint matrices from the nodes and upload them to the joint texture.
278
+ *
279
+ * @internal
192
280
  */ apply(deltaTime) {
193
281
  this.updateJointMatrices(deltaTime);
194
282
  const tex = this.jointTexture;
195
283
  tex.update(this._jointMatrixArray, 0, 0, tex.width, tex.height);
196
284
  }
197
- /**
198
- * Apply all enabled modifiers.
199
- *
200
- * Modifiers are applied after the base animation/bind pose layer,
201
- * allowing procedural modifications like IK, spring physics, or manual overrides.
202
- *
203
- * @param deltaTime - Time elapsed since last frame (in seconds)
204
- * @internal
285
+ /**
286
+ * Apply all enabled modifiers.
287
+ *
288
+ * Modifiers are applied after the base animation/bind pose layer,
289
+ * allowing procedural modifications like IK, spring physics, or manual overrides.
290
+ *
291
+ * @param deltaTime - Time elapsed since last frame (in seconds)
292
+ * @internal
205
293
  */ applyModifiers(deltaTime) {
206
294
  for (const modifier of this._modifiers){
207
295
  modifier.apply(this, deltaTime);
208
296
  }
209
297
  }
210
- /**
211
- * Get all modifiers attached to this skeleton.
212
- *
213
- * @public
298
+ /**
299
+ * Get all modifiers attached to this skeleton.
300
+ *
301
+ * @public
214
302
  */ get modifiers() {
215
303
  return this._modifiers;
216
304
  }
217
- /**
218
- * Reset all meshes to an unskinned state and clear animated bounds.
219
- *
220
- * @internal
305
+ /**
306
+ * Reset all meshes to an unskinned state and clear animated bounds.
307
+ *
308
+ * @internal
221
309
  */ reset() {
222
310
  //this.updateJointMatrices(this._bindPoseMatrices);
223
311
  this._playing = false;
224
312
  }
225
- /**
226
- * Compute the animated bounding box for a single mesh using its representative vertices.
227
- *
228
- * For each representative vertex:
229
- * - Blends the vertex by up to 4 joint matrices using provided weights.
230
- * - Transforms to the mesh's local space using `invWorldMatrix`.
231
- * - Expands the bounding box.
232
- *
233
- * @param info - Precomputed bounding data (representative vertices, indices, weights).
234
- * @param invWorldMatrix - Mesh inverse world matrix to convert to model/local space.
235
- * @internal
313
+ /**
314
+ * Compute the animated bounding box for a single mesh using its representative vertices.
315
+ *
316
+ * For each representative vertex:
317
+ * - Blends the vertex by up to 4 joint matrices using provided weights.
318
+ * - Transforms to the mesh's local space using `invWorldMatrix`.
319
+ * - Expands the bounding box.
320
+ *
321
+ * @param info - Precomputed bounding data (representative vertices, indices, weights).
322
+ * @param invWorldMatrix - Mesh inverse world matrix to convert to model/local space.
323
+ * @internal
236
324
  */ computeBoundingBox(info, invWorldMatrix) {
237
325
  info.boundingBox.beginExtend();
238
326
  for(let i = 0; i < info.boundingVertices.length; i++){
@@ -245,11 +333,11 @@ const tmpV3 = new Vector3();
245
333
  info.boundingBox.extend(tmpV0);
246
334
  }
247
335
  }
248
- /**
249
- * Dispose GPU resources and references held by the skeleton.
250
- *
251
- * - Disposes the joint texture.
252
- * - Clears matrix arrays and joint references.
336
+ /**
337
+ * Dispose GPU resources and references held by the skeleton.
338
+ *
339
+ * - Disposes the joint texture.
340
+ * - Clears matrix arrays and joint references.
253
341
  */ onDispose() {
254
342
  super.onDispose();
255
343
  this._jointTexture.dispose();
@@ -259,17 +347,17 @@ const tmpV3 = new Vector3();
259
347
  m.dispose();
260
348
  }
261
349
  }
262
- /**
263
- * Initialize joint texture and CPU-side matrix storage.
264
- *
265
- * Layout details:
266
- * - Texture size is the next power-of-two able to contain all matrices plus two offset texels.
267
- * - `_jointMatrixArray` holds:
268
- * - First 2 vec4s: ring buffer offsets `[current, previous, 0, 0]`.
269
- * - Followed by 2×N matrices (current and previous), each as 16 floats.
270
- * - `_jointMatrices` is a view into `_jointMatrixArray` providing Matrix4x4 objects per slot.
271
- *
272
- * @internal
350
+ /**
351
+ * Initialize joint texture and CPU-side matrix storage.
352
+ *
353
+ * Layout details:
354
+ * - Texture size is the next power-of-two able to contain all matrices plus two offset texels.
355
+ * - `_jointMatrixArray` holds:
356
+ * - First 2 vec4s: ring buffer offsets `[current, previous, 0, 0]`.
357
+ * - Followed by 2×N matrices (current and previous), each as 16 floats.
358
+ * - `_jointMatrices` is a view into `_jointMatrixArray` providing Matrix4x4 objects per slot.
359
+ *
360
+ * @internal
273
361
  */ _createJointTexture() {
274
362
  const textureWidth = nextPowerOf2(Math.max(4, Math.ceil(Math.sqrt((this._joints.length * 2 + 1) * 4))));
275
363
  const device = getDevice();
@@ -289,19 +377,19 @@ const tmpV3 = new Vector3();
289
377
  length: this._joints.length * 2
290
378
  }).map((val, index)=>new Matrix4x4(buffer, (index + 1) * 16 * Float32Array.BYTES_PER_ELEMENT));
291
379
  }
292
- /**
293
- * Build representative skinned bounding data for a submesh.
294
- *
295
- * Strategy:
296
- * - For all vertices, compute their skinned position (using current ring buffer slot).
297
- * - Track the indices of the min/max extents along x, y, z (6 indices total).
298
- * - Store:
299
- * - The 6 representative positions in object space.
300
- * - Their 4 joint indices and weights (flattened).
301
- * - An empty BoundingBox to be filled during animation.
302
- *
303
- * @param meshData - Raw submesh attributes (positions, blend indices, weights).
304
- * @returns Skinned bounding box info used during per-frame updates.
380
+ /**
381
+ * Build representative skinned bounding data for a submesh.
382
+ *
383
+ * Strategy:
384
+ * - For all vertices, compute their skinned position (using current ring buffer slot).
385
+ * - Track the indices of the min/max extents along x, y, z (6 indices total).
386
+ * - Store:
387
+ * - The 6 representative positions in object space.
388
+ * - Their 4 joint indices and weights (flattened).
389
+ * - An empty BoundingBox to be filled during animation.
390
+ *
391
+ * @param meshData - Raw submesh attributes (positions, blend indices, weights).
392
+ * @returns Skinned bounding box info used during per-frame updates.
305
393
  */ getBoundingInfo(data) {
306
394
  const indices = [
307
395
  0,
@@ -370,6 +458,57 @@ const tmpV3 = new Vector3();
370
458
  };
371
459
  return info;
372
460
  }
461
+ /**
462
+ * Skin all vertices into mesh-local space for the current skeleton pose.
463
+ *
464
+ * This is used by systems like cloth that need the animated mesh pose on CPU,
465
+ * but render with GPU skinning disabled to avoid double deformation.
466
+ *
467
+ * @param positions - Bind-pose/object-space input positions, xyz packed.
468
+ * @param blendIndices - 4 joint indices per vertex.
469
+ * @param weights - 4 joint weights per vertex.
470
+ * @param invWorldMatrix - Mesh inverse world matrix to convert skinned world positions back to mesh-local space.
471
+ * @param out - Optional output array to reuse.
472
+ * @returns The skinned mesh-local positions.
473
+ * @internal
474
+ */ skinPositionsToLocal(positions, blendIndices, weights, invWorldMatrix, out) {
475
+ const result = out && out.length === positions.length ? out : new Float32Array(positions.length);
476
+ const matrixOffset = this._jointOffsets[0] - 1;
477
+ for(let i = 0; i + 2 < positions.length; i += 3){
478
+ const vertexIndex = i / 3 >> 0;
479
+ const x = positions[i];
480
+ const y = positions[i + 1];
481
+ const z = positions[i + 2];
482
+ let skinnedX = 0;
483
+ let skinnedY = 0;
484
+ let skinnedZ = 0;
485
+ let weightSum = 0;
486
+ for(let j = 0; j < 4; j++){
487
+ const weight = Number(weights[vertexIndex * 4 + j]) || 0;
488
+ if (weight <= 0) {
489
+ continue;
490
+ }
491
+ const jointIndex = (Number(blendIndices[vertexIndex * 4 + j]) || 0) + matrixOffset;
492
+ const matrix = this._jointMatrices[jointIndex];
493
+ if (!matrix) {
494
+ continue;
495
+ }
496
+ skinnedX += (matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12]) * weight;
497
+ skinnedY += (matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13]) * weight;
498
+ skinnedZ += (matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14]) * weight;
499
+ weightSum += weight;
500
+ }
501
+ if (weightSum <= 1e-6) {
502
+ skinnedX = x;
503
+ skinnedY = y;
504
+ skinnedZ = z;
505
+ }
506
+ result[i] = invWorldMatrix[0] * skinnedX + invWorldMatrix[4] * skinnedY + invWorldMatrix[8] * skinnedZ + invWorldMatrix[12];
507
+ result[i + 1] = invWorldMatrix[1] * skinnedX + invWorldMatrix[5] * skinnedY + invWorldMatrix[9] * skinnedZ + invWorldMatrix[13];
508
+ result[i + 2] = invWorldMatrix[2] * skinnedX + invWorldMatrix[6] * skinnedY + invWorldMatrix[10] * skinnedZ + invWorldMatrix[14];
509
+ }
510
+ return result;
511
+ }
373
512
  findRootJoint(joints) {
374
513
  let root = null;
375
514
  for (const joint of joints){
@@ -385,7 +524,1740 @@ const tmpV3 = new Vector3();
385
524
  }
386
525
  return root;
387
526
  }
527
+ static normalizeHumanoidJointName(name) {
528
+ return name.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2').replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/([a-zA-Z])(\d+)/g, '$1 $2').replace(/(\d+)([a-zA-Z])/g, '$1 $2').replace(/[_:\/\\.-]+/g, ' ').replace(/[^a-zA-Z0-9]+/g, ' ').toLowerCase().replace(/\bforearm\b/g, 'fore arm').replace(/\bupperarm\b/g, 'upper arm').replace(/\blowerarm\b/g, 'lower arm').replace(/\bupleg\b/g, 'up leg').replace(/\bupperleg\b/g, 'upper leg').replace(/\blowerleg\b/g, 'lower leg').replace(/\btoebase\b/g, 'toe base').replace(/\bupperchest\b/g, 'upper chest').replace(/\btoes\b/g, 'toe').replace(/\blittle\b/g, 'pinky').replace(/\bcollar\b/g, 'clavicle').replace(/\s+/g, ' ').trim();
529
+ }
530
+ static getHumanoidJointNodeDepth(root, node) {
531
+ let depth = 0;
532
+ let current = node;
533
+ while(current && current !== root){
534
+ depth++;
535
+ current = current.parent;
536
+ }
537
+ return depth;
538
+ }
539
+ static collectHumanoidJointNodeInfos(root) {
540
+ const nodes = [];
541
+ this.iterateNode(root, (node)=>{
542
+ const normalizedName = this.normalizeHumanoidJointName(node.name || '');
543
+ if (!normalizedName) {
544
+ return false;
545
+ }
546
+ const tokens = normalizedName.split(' ').filter(Boolean);
547
+ if (!tokens.length) {
548
+ return false;
549
+ }
550
+ nodes.push({
551
+ node,
552
+ depth: this.getHumanoidJointNodeDepth(root, node),
553
+ tokens,
554
+ tokenSet: new Set(tokens)
555
+ });
556
+ return false;
557
+ });
558
+ return nodes;
559
+ }
560
+ static matchesHumanoidJointPattern(info, pattern) {
561
+ if (pattern.all?.some((token)=>!info.tokenSet.has(token))) {
562
+ return false;
563
+ }
564
+ if (pattern.any?.length && !pattern.any.some((token)=>info.tokenSet.has(token))) {
565
+ return false;
566
+ }
567
+ if (pattern.none?.some((token)=>info.tokenSet.has(token))) {
568
+ return false;
569
+ }
570
+ return true;
571
+ }
572
+ static scoreHumanoidJointPattern(info, pattern, priority) {
573
+ const matchedTokens = new Set(pattern.all ?? []);
574
+ for (const token of pattern.any ?? []){
575
+ if (info.tokenSet.has(token)) {
576
+ matchedTokens.add(token);
577
+ }
578
+ }
579
+ let extraTokenCount = 0;
580
+ for (const token of info.tokens){
581
+ if (!matchedTokens.has(token)) {
582
+ extraTokenCount++;
583
+ }
584
+ }
585
+ return priority * 100000 + matchedTokens.size * 100 - extraTokenCount * 5 - info.depth;
586
+ }
587
+ static collectHumanoidJointCandidates(nodes, patterns, used) {
588
+ const candidates = new Map();
589
+ for(let index = 0; index < patterns.length; index++){
590
+ const pattern = patterns[index];
591
+ const priority = patterns.length - index;
592
+ for (const info of nodes){
593
+ if (used.has(info.node) || !this.matchesHumanoidJointPattern(info, pattern)) {
594
+ continue;
595
+ }
596
+ const score = this.scoreHumanoidJointPattern(info, pattern, priority);
597
+ const current = candidates.get(info.node);
598
+ if (current === undefined || score > current) {
599
+ candidates.set(info.node, score);
600
+ }
601
+ }
602
+ }
603
+ return Array.from(candidates, ([node, score])=>({
604
+ node,
605
+ score
606
+ })).sort((a, b)=>b.score - a.score);
607
+ }
608
+ static findBestHumanoidJoint(nodes, patterns, used) {
609
+ return this.collectHumanoidJointCandidates(nodes, patterns, used)[0]?.node ?? null;
610
+ }
611
+ static matchHumanoidJointProfile(nodes, profile, used, optional) {
612
+ const result = {};
613
+ const reserved = new Set();
614
+ for (const joint of Object.keys(profile)){
615
+ const node = this.findBestHumanoidJoint(nodes, profile[joint], new Set([
616
+ ...used,
617
+ ...reserved
618
+ ]));
619
+ if (!node) {
620
+ if (optional?.has(joint)) {
621
+ continue;
622
+ }
623
+ return null;
624
+ }
625
+ result[joint] = node;
626
+ reserved.add(node);
627
+ }
628
+ for (const node of reserved){
629
+ used.add(node);
630
+ }
631
+ return result;
632
+ }
633
+ static validateHumanoidBodyPartial(body) {
634
+ const validateAncestor = (ancestor, descendant)=>!body[ancestor] || !body[descendant] || this.isSameOrAncestor(body[ancestor], body[descendant]);
635
+ return validateAncestor("Hips", "Spine") && validateAncestor("Hips", "Neck") && validateAncestor("Hips", "LeftUpperLeg") && validateAncestor("Hips", "RightUpperLeg") && validateAncestor("Spine", "Chest") && validateAncestor("Spine", "UpperChest") && validateAncestor("Spine", "Neck") && validateAncestor("Spine", "Head") && validateAncestor("Spine", "LeftShoulder") && validateAncestor("Spine", "RightShoulder") && validateAncestor("Chest", "UpperChest") && validateAncestor("Chest", "Neck") && validateAncestor("Chest", "Head") && validateAncestor("Chest", "LeftShoulder") && validateAncestor("Chest", "RightShoulder") && validateAncestor("UpperChest", "Neck") && validateAncestor("UpperChest", "Head") && validateAncestor("UpperChest", "LeftShoulder") && validateAncestor("UpperChest", "RightShoulder") && validateAncestor("Neck", "Head") && validateAncestor("LeftShoulder", "LeftUpperArm") && validateAncestor("LeftUpperArm", "LeftLowerArm") && validateAncestor("LeftLowerArm", "LeftHand") && validateAncestor("RightShoulder", "RightUpperArm") && validateAncestor("RightUpperArm", "RightLowerArm") && validateAncestor("RightLowerArm", "RightHand") && validateAncestor("LeftUpperLeg", "LeftLowerLeg") && validateAncestor("LeftLowerLeg", "LeftFoot") && validateAncestor("LeftFoot", "LeftToes") && validateAncestor("RightUpperLeg", "RightLowerLeg") && validateAncestor("RightLowerLeg", "RightFoot") && validateAncestor("RightFoot", "RightToes");
636
+ }
637
+ static matchHumanoidBodyProfile(nodes, profile, used, optional) {
638
+ const joints = Object.keys(profile);
639
+ const candidates = new Map();
640
+ for (const joint of joints){
641
+ candidates.set(joint, this.collectHumanoidJointCandidates(nodes, profile[joint], used));
642
+ }
643
+ const current = {};
644
+ const reserved = new Set();
645
+ let bestScore = Number.NEGATIVE_INFINITY;
646
+ let bestResult = null;
647
+ const search = (index, score)=>{
648
+ if (index >= joints.length) {
649
+ if (score > bestScore) {
650
+ bestScore = score;
651
+ bestResult = {
652
+ ...current
653
+ };
654
+ }
655
+ return;
656
+ }
657
+ const joint = joints[index];
658
+ let matched = false;
659
+ for (const candidate of candidates.get(joint) ?? []){
660
+ if (reserved.has(candidate.node)) {
661
+ continue;
662
+ }
663
+ current[joint] = candidate.node;
664
+ reserved.add(candidate.node);
665
+ if (this.validateHumanoidBodyPartial(current)) {
666
+ matched = true;
667
+ search(index + 1, score + candidate.score);
668
+ }
669
+ reserved.delete(candidate.node);
670
+ delete current[joint];
671
+ }
672
+ if (optional?.has(joint)) {
673
+ search(index + 1, score);
674
+ } else if (!matched) {
675
+ return;
676
+ }
677
+ };
678
+ search(0, 0);
679
+ if (!bestResult) {
680
+ return null;
681
+ }
682
+ for (const node of Object.values(bestResult)){
683
+ used.add(node);
684
+ }
685
+ return bestResult;
686
+ }
687
+ static getHumanoidIntermediateChain(ancestor, descendant) {
688
+ if (!this.isSameOrAncestor(ancestor, descendant)) {
689
+ return null;
690
+ }
691
+ const chain = [];
692
+ let current = descendant.parent;
693
+ while(current && current !== ancestor){
694
+ chain.unshift(current);
695
+ current = current.parent;
696
+ }
697
+ return chain;
698
+ }
699
+ static completeHumanoidBody(body) {
700
+ const requiredJoints = [
701
+ "Hips",
702
+ "Spine",
703
+ "Neck",
704
+ "Head",
705
+ "LeftShoulder",
706
+ "LeftUpperArm",
707
+ "LeftLowerArm",
708
+ "LeftHand",
709
+ "RightShoulder",
710
+ "RightUpperArm",
711
+ "RightLowerArm",
712
+ "RightHand",
713
+ "LeftUpperLeg",
714
+ "LeftLowerLeg",
715
+ "LeftFoot",
716
+ "LeftToes",
717
+ "RightUpperLeg",
718
+ "RightLowerLeg",
719
+ "RightFoot",
720
+ "RightToes"
721
+ ];
722
+ for (const joint of requiredJoints){
723
+ if (!body[joint]) {
724
+ return null;
725
+ }
726
+ }
727
+ const spine = body["Spine"];
728
+ const neck = body["Neck"];
729
+ let chest = body["Chest"];
730
+ let upperChest = body["UpperChest"];
731
+ if (!chest || !upperChest) {
732
+ const spineChain = this.getHumanoidIntermediateChain(spine, neck);
733
+ if (!spineChain || spineChain.length === 0) {
734
+ chest ||= spine;
735
+ upperChest ||= chest;
736
+ } else if (spineChain.length === 1) {
737
+ chest ||= spineChain[0];
738
+ upperChest ||= chest;
739
+ } else {
740
+ chest ||= spineChain[0];
741
+ upperChest ||= spineChain[spineChain.length - 1];
742
+ }
743
+ }
744
+ if (!chest || !upperChest) {
745
+ return null;
746
+ }
747
+ return {
748
+ ["Hips"]: body["Hips"],
749
+ ["Spine"]: spine,
750
+ ["Chest"]: chest,
751
+ ["UpperChest"]: upperChest,
752
+ ["Neck"]: neck,
753
+ ["Head"]: body["Head"],
754
+ ["LeftShoulder"]: body["LeftShoulder"],
755
+ ["LeftUpperArm"]: body["LeftUpperArm"],
756
+ ["LeftLowerArm"]: body["LeftLowerArm"],
757
+ ["LeftHand"]: body["LeftHand"],
758
+ ["RightShoulder"]: body["RightShoulder"],
759
+ ["RightUpperArm"]: body["RightUpperArm"],
760
+ ["RightLowerArm"]: body["RightLowerArm"],
761
+ ["RightHand"]: body["RightHand"],
762
+ ["LeftUpperLeg"]: body["LeftUpperLeg"],
763
+ ["LeftLowerLeg"]: body["LeftLowerLeg"],
764
+ ["LeftFoot"]: body["LeftFoot"],
765
+ ["LeftToes"]: body["LeftToes"],
766
+ ["RightUpperLeg"]: body["RightUpperLeg"],
767
+ ["RightLowerLeg"]: body["RightLowerLeg"],
768
+ ["RightFoot"]: body["RightFoot"],
769
+ ["RightToes"]: body["RightToes"]
770
+ };
771
+ }
772
+ static completeHumanoidHand(hand) {
773
+ for (const joint of Object.values(HumanoidHandRig)){
774
+ if (!hand[joint]) {
775
+ return null;
776
+ }
777
+ }
778
+ return {
779
+ ["ThumbProximal"]: hand["ThumbProximal"],
780
+ ["ThumbIntermediate"]: hand["ThumbIntermediate"],
781
+ ["ThumbDistal"]: hand["ThumbDistal"],
782
+ ["IndexProximal"]: hand["IndexProximal"],
783
+ ["IndexIntermediate"]: hand["IndexIntermediate"],
784
+ ["IndexDistal"]: hand["IndexDistal"],
785
+ ["MiddleProximal"]: hand["MiddleProximal"],
786
+ ["MiddleIntermediate"]: hand["MiddleIntermediate"],
787
+ ["MiddleDistal"]: hand["MiddleDistal"],
788
+ ["RingProximal"]: hand["RingProximal"],
789
+ ["RingIntermediate"]: hand["RingIntermediate"],
790
+ ["RingDistal"]: hand["RingDistal"],
791
+ ["PinkyProximal"]: hand["PinkyProximal"],
792
+ ["PinkyIntermediate"]: hand["PinkyIntermediate"],
793
+ ["PinkyDistal"]: hand["PinkyDistal"]
794
+ };
795
+ }
796
+ static tryExtractOptionalHumanoidHand(nodes, side, used) {
797
+ const handCandidates = this.matchHumanoidJointProfile(nodes, this.createCommonHumanoidHandProfile(side), used);
798
+ if (!handCandidates) {
799
+ return undefined;
800
+ }
801
+ return this.completeHumanoidHand(handCandidates) ?? undefined;
802
+ }
803
+ static isSameOrAncestor(parent, child) {
804
+ return parent === child || this.isParentOf(parent, child);
805
+ }
806
+ static validateHumanoidHandHierarchy(hand, joints) {
807
+ const chains = [
808
+ [
809
+ "ThumbProximal",
810
+ "ThumbIntermediate",
811
+ "ThumbDistal"
812
+ ],
813
+ [
814
+ "IndexProximal",
815
+ "IndexIntermediate",
816
+ "IndexDistal"
817
+ ],
818
+ [
819
+ "MiddleProximal",
820
+ "MiddleIntermediate",
821
+ "MiddleDistal"
822
+ ],
823
+ [
824
+ "RingProximal",
825
+ "RingIntermediate",
826
+ "RingDistal"
827
+ ],
828
+ [
829
+ "PinkyProximal",
830
+ "PinkyIntermediate",
831
+ "PinkyDistal"
832
+ ]
833
+ ];
834
+ return chains.every(([proximal, intermediate, distal])=>this.isSameOrAncestor(hand, joints[proximal]) && this.isSameOrAncestor(joints[proximal], joints[intermediate]) && this.isSameOrAncestor(joints[intermediate], joints[distal]));
835
+ }
836
+ static validateHumanoidJointExtraction(result) {
837
+ const body = result.body;
838
+ return this.isSameOrAncestor(body["Hips"], body["Spine"]) && this.isSameOrAncestor(body["Spine"], body["Chest"]) && this.isSameOrAncestor(body["Chest"], body["UpperChest"]) && this.isSameOrAncestor(body["UpperChest"], body["Neck"]) && this.isSameOrAncestor(body["Neck"], body["Head"]) && this.isSameOrAncestor(body["UpperChest"], body["LeftShoulder"]) && this.isSameOrAncestor(body["LeftShoulder"], body["LeftUpperArm"]) && this.isSameOrAncestor(body["LeftUpperArm"], body["LeftLowerArm"]) && this.isSameOrAncestor(body["LeftLowerArm"], body["LeftHand"]) && this.isSameOrAncestor(body["UpperChest"], body["RightShoulder"]) && this.isSameOrAncestor(body["RightShoulder"], body["RightUpperArm"]) && this.isSameOrAncestor(body["RightUpperArm"], body["RightLowerArm"]) && this.isSameOrAncestor(body["RightLowerArm"], body["RightHand"]) && this.isSameOrAncestor(body["Hips"], body["LeftUpperLeg"]) && this.isSameOrAncestor(body["LeftUpperLeg"], body["LeftLowerLeg"]) && this.isSameOrAncestor(body["LeftLowerLeg"], body["LeftFoot"]) && this.isSameOrAncestor(body["LeftFoot"], body["LeftToes"]) && this.isSameOrAncestor(body["Hips"], body["RightUpperLeg"]) && this.isSameOrAncestor(body["RightUpperLeg"], body["RightLowerLeg"]) && this.isSameOrAncestor(body["RightLowerLeg"], body["RightFoot"]) && this.isSameOrAncestor(body["RightFoot"], body["RightToes"]) && (!result.leftHand || this.validateHumanoidHandHierarchy(body["LeftHand"], result.leftHand)) && (!result.rightHand || this.validateHumanoidHandHierarchy(body["RightHand"], result.rightHand));
839
+ }
840
+ static createCommonHumanoidHandProfile(side) {
841
+ return {
842
+ ["ThumbProximal"]: sideJointPatterns(side, [
843
+ {
844
+ all: [
845
+ 'thumb',
846
+ 'proximal'
847
+ ]
848
+ },
849
+ {
850
+ all: [
851
+ 'thumb',
852
+ '1'
853
+ ]
854
+ },
855
+ {
856
+ all: [
857
+ 'finger',
858
+ '0'
859
+ ]
860
+ }
861
+ ]),
862
+ ["ThumbIntermediate"]: sideJointPatterns(side, [
863
+ {
864
+ all: [
865
+ 'thumb',
866
+ 'intermediate'
867
+ ]
868
+ },
869
+ {
870
+ all: [
871
+ 'thumb',
872
+ '2'
873
+ ]
874
+ },
875
+ {
876
+ all: [
877
+ 'finger',
878
+ '01'
879
+ ]
880
+ }
881
+ ]),
882
+ ["ThumbDistal"]: sideJointPatterns(side, [
883
+ {
884
+ all: [
885
+ 'thumb',
886
+ 'distal'
887
+ ]
888
+ },
889
+ {
890
+ all: [
891
+ 'thumb',
892
+ '3'
893
+ ]
894
+ },
895
+ {
896
+ all: [
897
+ 'finger',
898
+ '02'
899
+ ]
900
+ }
901
+ ]),
902
+ ["IndexProximal"]: sideJointPatterns(side, [
903
+ {
904
+ all: [
905
+ 'index',
906
+ 'proximal'
907
+ ]
908
+ },
909
+ {
910
+ all: [
911
+ 'index',
912
+ '1'
913
+ ]
914
+ },
915
+ {
916
+ all: [
917
+ 'finger',
918
+ '1'
919
+ ]
920
+ }
921
+ ]),
922
+ ["IndexIntermediate"]: sideJointPatterns(side, [
923
+ {
924
+ all: [
925
+ 'index',
926
+ 'intermediate'
927
+ ]
928
+ },
929
+ {
930
+ all: [
931
+ 'index',
932
+ '2'
933
+ ]
934
+ },
935
+ {
936
+ all: [
937
+ 'finger',
938
+ '11'
939
+ ]
940
+ }
941
+ ]),
942
+ ["IndexDistal"]: sideJointPatterns(side, [
943
+ {
944
+ all: [
945
+ 'index',
946
+ 'distal'
947
+ ]
948
+ },
949
+ {
950
+ all: [
951
+ 'index',
952
+ '3'
953
+ ]
954
+ },
955
+ {
956
+ all: [
957
+ 'finger',
958
+ '12'
959
+ ]
960
+ }
961
+ ]),
962
+ ["MiddleProximal"]: sideJointPatterns(side, [
963
+ {
964
+ all: [
965
+ 'middle',
966
+ 'proximal'
967
+ ]
968
+ },
969
+ {
970
+ all: [
971
+ 'middle',
972
+ '1'
973
+ ]
974
+ },
975
+ {
976
+ all: [
977
+ 'finger',
978
+ '2'
979
+ ]
980
+ }
981
+ ]),
982
+ ["MiddleIntermediate"]: sideJointPatterns(side, [
983
+ {
984
+ all: [
985
+ 'middle',
986
+ 'intermediate'
987
+ ]
988
+ },
989
+ {
990
+ all: [
991
+ 'middle',
992
+ '2'
993
+ ]
994
+ },
995
+ {
996
+ all: [
997
+ 'finger',
998
+ '21'
999
+ ]
1000
+ }
1001
+ ]),
1002
+ ["MiddleDistal"]: sideJointPatterns(side, [
1003
+ {
1004
+ all: [
1005
+ 'middle',
1006
+ 'distal'
1007
+ ]
1008
+ },
1009
+ {
1010
+ all: [
1011
+ 'middle',
1012
+ '3'
1013
+ ]
1014
+ },
1015
+ {
1016
+ all: [
1017
+ 'finger',
1018
+ '22'
1019
+ ]
1020
+ }
1021
+ ]),
1022
+ ["RingProximal"]: sideJointPatterns(side, [
1023
+ {
1024
+ all: [
1025
+ 'ring',
1026
+ 'proximal'
1027
+ ]
1028
+ },
1029
+ {
1030
+ all: [
1031
+ 'ring',
1032
+ '1'
1033
+ ]
1034
+ },
1035
+ {
1036
+ all: [
1037
+ 'finger',
1038
+ '3'
1039
+ ]
1040
+ }
1041
+ ]),
1042
+ ["RingIntermediate"]: sideJointPatterns(side, [
1043
+ {
1044
+ all: [
1045
+ 'ring',
1046
+ 'intermediate'
1047
+ ]
1048
+ },
1049
+ {
1050
+ all: [
1051
+ 'ring',
1052
+ '2'
1053
+ ]
1054
+ },
1055
+ {
1056
+ all: [
1057
+ 'finger',
1058
+ '31'
1059
+ ]
1060
+ }
1061
+ ]),
1062
+ ["RingDistal"]: sideJointPatterns(side, [
1063
+ {
1064
+ all: [
1065
+ 'ring',
1066
+ 'distal'
1067
+ ]
1068
+ },
1069
+ {
1070
+ all: [
1071
+ 'ring',
1072
+ '3'
1073
+ ]
1074
+ },
1075
+ {
1076
+ all: [
1077
+ 'finger',
1078
+ '32'
1079
+ ]
1080
+ }
1081
+ ]),
1082
+ ["PinkyProximal"]: sideJointPatterns(side, [
1083
+ {
1084
+ all: [
1085
+ 'pinky',
1086
+ 'proximal'
1087
+ ]
1088
+ },
1089
+ {
1090
+ all: [
1091
+ 'pinky',
1092
+ '1'
1093
+ ]
1094
+ },
1095
+ {
1096
+ all: [
1097
+ 'finger',
1098
+ '4'
1099
+ ]
1100
+ }
1101
+ ]),
1102
+ ["PinkyIntermediate"]: sideJointPatterns(side, [
1103
+ {
1104
+ all: [
1105
+ 'pinky',
1106
+ 'intermediate'
1107
+ ]
1108
+ },
1109
+ {
1110
+ all: [
1111
+ 'pinky',
1112
+ '2'
1113
+ ]
1114
+ },
1115
+ {
1116
+ all: [
1117
+ 'finger',
1118
+ '41'
1119
+ ]
1120
+ }
1121
+ ]),
1122
+ ["PinkyDistal"]: sideJointPatterns(side, [
1123
+ {
1124
+ all: [
1125
+ 'pinky',
1126
+ 'distal'
1127
+ ]
1128
+ },
1129
+ {
1130
+ all: [
1131
+ 'pinky',
1132
+ '3'
1133
+ ]
1134
+ },
1135
+ {
1136
+ all: [
1137
+ 'finger',
1138
+ '42'
1139
+ ]
1140
+ }
1141
+ ])
1142
+ };
1143
+ }
1144
+ static createStandardHumanoidBodyProfile() {
1145
+ return {
1146
+ ["Hips"]: [
1147
+ jointPattern([
1148
+ 'hips'
1149
+ ]),
1150
+ jointPattern([
1151
+ 'pelvis'
1152
+ ])
1153
+ ],
1154
+ ["Spine"]: [
1155
+ jointPattern([
1156
+ 'spine'
1157
+ ], [
1158
+ '1',
1159
+ '2',
1160
+ '3',
1161
+ 'chest',
1162
+ 'upper'
1163
+ ])
1164
+ ],
1165
+ ["Chest"]: [
1166
+ jointPattern([
1167
+ 'chest'
1168
+ ], [
1169
+ 'upper'
1170
+ ]),
1171
+ jointPattern([
1172
+ 'spine',
1173
+ '1'
1174
+ ])
1175
+ ],
1176
+ ["UpperChest"]: [
1177
+ jointPattern([
1178
+ 'upper',
1179
+ 'chest'
1180
+ ]),
1181
+ jointPattern([
1182
+ 'spine',
1183
+ '2'
1184
+ ]),
1185
+ jointPattern([
1186
+ 'spine',
1187
+ '3'
1188
+ ])
1189
+ ],
1190
+ ["Neck"]: [
1191
+ jointPattern([
1192
+ 'neck'
1193
+ ], [
1194
+ '1',
1195
+ '2'
1196
+ ])
1197
+ ],
1198
+ ["Head"]: [
1199
+ jointPattern([
1200
+ 'head'
1201
+ ], [
1202
+ 'top',
1203
+ 'end',
1204
+ 'nub'
1205
+ ])
1206
+ ],
1207
+ ["LeftShoulder"]: sideJointPatterns('left', [
1208
+ {
1209
+ all: [
1210
+ 'shoulder'
1211
+ ]
1212
+ },
1213
+ {
1214
+ all: [
1215
+ 'clavicle'
1216
+ ]
1217
+ }
1218
+ ]),
1219
+ ["LeftUpperArm"]: sideJointPatterns('left', [
1220
+ {
1221
+ all: [
1222
+ 'upper',
1223
+ 'arm'
1224
+ ],
1225
+ none: [
1226
+ 'twist'
1227
+ ]
1228
+ },
1229
+ {
1230
+ all: [
1231
+ 'arm'
1232
+ ],
1233
+ none: [
1234
+ 'fore',
1235
+ 'lower',
1236
+ 'hand',
1237
+ 'twist',
1238
+ 'shoulder'
1239
+ ]
1240
+ }
1241
+ ]),
1242
+ ["LeftLowerArm"]: sideJointPatterns('left', [
1243
+ {
1244
+ all: [
1245
+ 'lower',
1246
+ 'arm'
1247
+ ],
1248
+ none: [
1249
+ 'twist'
1250
+ ]
1251
+ },
1252
+ {
1253
+ all: [
1254
+ 'fore',
1255
+ 'arm'
1256
+ ],
1257
+ none: [
1258
+ 'twist'
1259
+ ]
1260
+ }
1261
+ ]),
1262
+ ["LeftHand"]: sideJointPatterns('left', [
1263
+ {
1264
+ all: [
1265
+ 'hand'
1266
+ ],
1267
+ none: [
1268
+ 'thumb',
1269
+ 'index',
1270
+ 'middle',
1271
+ 'ring',
1272
+ 'pinky'
1273
+ ]
1274
+ }
1275
+ ]),
1276
+ ["RightShoulder"]: sideJointPatterns('right', [
1277
+ {
1278
+ all: [
1279
+ 'shoulder'
1280
+ ]
1281
+ },
1282
+ {
1283
+ all: [
1284
+ 'clavicle'
1285
+ ]
1286
+ }
1287
+ ]),
1288
+ ["RightUpperArm"]: sideJointPatterns('right', [
1289
+ {
1290
+ all: [
1291
+ 'upper',
1292
+ 'arm'
1293
+ ],
1294
+ none: [
1295
+ 'twist'
1296
+ ]
1297
+ },
1298
+ {
1299
+ all: [
1300
+ 'arm'
1301
+ ],
1302
+ none: [
1303
+ 'fore',
1304
+ 'lower',
1305
+ 'hand',
1306
+ 'twist',
1307
+ 'shoulder'
1308
+ ]
1309
+ }
1310
+ ]),
1311
+ ["RightLowerArm"]: sideJointPatterns('right', [
1312
+ {
1313
+ all: [
1314
+ 'lower',
1315
+ 'arm'
1316
+ ],
1317
+ none: [
1318
+ 'twist'
1319
+ ]
1320
+ },
1321
+ {
1322
+ all: [
1323
+ 'fore',
1324
+ 'arm'
1325
+ ],
1326
+ none: [
1327
+ 'twist'
1328
+ ]
1329
+ }
1330
+ ]),
1331
+ ["RightHand"]: sideJointPatterns('right', [
1332
+ {
1333
+ all: [
1334
+ 'hand'
1335
+ ],
1336
+ none: [
1337
+ 'thumb',
1338
+ 'index',
1339
+ 'middle',
1340
+ 'ring',
1341
+ 'pinky'
1342
+ ]
1343
+ }
1344
+ ]),
1345
+ ["LeftUpperLeg"]: sideJointPatterns('left', [
1346
+ {
1347
+ all: [
1348
+ 'upper',
1349
+ 'leg'
1350
+ ]
1351
+ },
1352
+ {
1353
+ all: [
1354
+ 'up',
1355
+ 'leg'
1356
+ ]
1357
+ },
1358
+ {
1359
+ all: [
1360
+ 'thigh'
1361
+ ]
1362
+ }
1363
+ ]),
1364
+ ["LeftLowerLeg"]: sideJointPatterns('left', [
1365
+ {
1366
+ all: [
1367
+ 'lower',
1368
+ 'leg'
1369
+ ]
1370
+ },
1371
+ {
1372
+ all: [
1373
+ 'leg'
1374
+ ],
1375
+ none: [
1376
+ 'upper',
1377
+ 'up',
1378
+ 'thigh',
1379
+ 'foot',
1380
+ 'toe'
1381
+ ]
1382
+ },
1383
+ {
1384
+ all: [
1385
+ 'calf'
1386
+ ]
1387
+ },
1388
+ {
1389
+ all: [
1390
+ 'shin'
1391
+ ]
1392
+ }
1393
+ ]),
1394
+ ["LeftFoot"]: sideJointPatterns('left', [
1395
+ {
1396
+ all: [
1397
+ 'foot'
1398
+ ],
1399
+ none: [
1400
+ 'toe'
1401
+ ]
1402
+ }
1403
+ ]),
1404
+ ["LeftToes"]: sideJointPatterns('left', [
1405
+ {
1406
+ all: [
1407
+ 'toe',
1408
+ 'base'
1409
+ ]
1410
+ },
1411
+ {
1412
+ all: [
1413
+ 'toe'
1414
+ ],
1415
+ none: [
1416
+ 'end'
1417
+ ]
1418
+ }
1419
+ ]),
1420
+ ["RightUpperLeg"]: sideJointPatterns('right', [
1421
+ {
1422
+ all: [
1423
+ 'upper',
1424
+ 'leg'
1425
+ ]
1426
+ },
1427
+ {
1428
+ all: [
1429
+ 'up',
1430
+ 'leg'
1431
+ ]
1432
+ },
1433
+ {
1434
+ all: [
1435
+ 'thigh'
1436
+ ]
1437
+ }
1438
+ ]),
1439
+ ["RightLowerLeg"]: sideJointPatterns('right', [
1440
+ {
1441
+ all: [
1442
+ 'lower',
1443
+ 'leg'
1444
+ ]
1445
+ },
1446
+ {
1447
+ all: [
1448
+ 'leg'
1449
+ ],
1450
+ none: [
1451
+ 'upper',
1452
+ 'up',
1453
+ 'thigh',
1454
+ 'foot',
1455
+ 'toe'
1456
+ ]
1457
+ },
1458
+ {
1459
+ all: [
1460
+ 'calf'
1461
+ ]
1462
+ },
1463
+ {
1464
+ all: [
1465
+ 'shin'
1466
+ ]
1467
+ }
1468
+ ]),
1469
+ ["RightFoot"]: sideJointPatterns('right', [
1470
+ {
1471
+ all: [
1472
+ 'foot'
1473
+ ],
1474
+ none: [
1475
+ 'toe'
1476
+ ]
1477
+ }
1478
+ ]),
1479
+ ["RightToes"]: sideJointPatterns('right', [
1480
+ {
1481
+ all: [
1482
+ 'toe',
1483
+ 'base'
1484
+ ]
1485
+ },
1486
+ {
1487
+ all: [
1488
+ 'toe'
1489
+ ],
1490
+ none: [
1491
+ 'end'
1492
+ ]
1493
+ }
1494
+ ])
1495
+ };
1496
+ }
1497
+ static createMixamoHumanoidBodyProfile() {
1498
+ return {
1499
+ ["Hips"]: [
1500
+ jointPattern([
1501
+ 'hips'
1502
+ ])
1503
+ ],
1504
+ ["Spine"]: [
1505
+ jointPattern([
1506
+ 'spine'
1507
+ ], [
1508
+ '1',
1509
+ '2',
1510
+ '3'
1511
+ ])
1512
+ ],
1513
+ ["Chest"]: [
1514
+ jointPattern([
1515
+ 'spine',
1516
+ '1'
1517
+ ]),
1518
+ jointPattern([
1519
+ 'chest'
1520
+ ], [
1521
+ 'upper'
1522
+ ])
1523
+ ],
1524
+ ["UpperChest"]: [
1525
+ jointPattern([
1526
+ 'spine',
1527
+ '2'
1528
+ ]),
1529
+ jointPattern([
1530
+ 'spine',
1531
+ '3'
1532
+ ]),
1533
+ jointPattern([
1534
+ 'upper',
1535
+ 'chest'
1536
+ ])
1537
+ ],
1538
+ ["Neck"]: [
1539
+ jointPattern([
1540
+ 'neck'
1541
+ ], [
1542
+ '1',
1543
+ '2'
1544
+ ])
1545
+ ],
1546
+ ["Head"]: [
1547
+ jointPattern([
1548
+ 'head'
1549
+ ], [
1550
+ 'top',
1551
+ 'end',
1552
+ 'nub'
1553
+ ])
1554
+ ],
1555
+ ["LeftShoulder"]: sideJointPatterns('left', [
1556
+ {
1557
+ all: [
1558
+ 'shoulder'
1559
+ ]
1560
+ },
1561
+ {
1562
+ all: [
1563
+ 'clavicle'
1564
+ ]
1565
+ }
1566
+ ]),
1567
+ ["LeftUpperArm"]: sideJointPatterns('left', [
1568
+ {
1569
+ all: [
1570
+ 'arm'
1571
+ ],
1572
+ none: [
1573
+ 'upper',
1574
+ 'fore',
1575
+ 'lower',
1576
+ 'hand',
1577
+ 'twist',
1578
+ 'shoulder'
1579
+ ]
1580
+ },
1581
+ {
1582
+ all: [
1583
+ 'upper',
1584
+ 'arm'
1585
+ ],
1586
+ none: [
1587
+ 'twist'
1588
+ ]
1589
+ }
1590
+ ]),
1591
+ ["LeftLowerArm"]: sideJointPatterns('left', [
1592
+ {
1593
+ all: [
1594
+ 'fore',
1595
+ 'arm'
1596
+ ],
1597
+ none: [
1598
+ 'twist'
1599
+ ]
1600
+ },
1601
+ {
1602
+ all: [
1603
+ 'lower',
1604
+ 'arm'
1605
+ ],
1606
+ none: [
1607
+ 'twist'
1608
+ ]
1609
+ }
1610
+ ]),
1611
+ ["LeftHand"]: sideJointPatterns('left', [
1612
+ {
1613
+ all: [
1614
+ 'hand'
1615
+ ],
1616
+ none: [
1617
+ 'thumb',
1618
+ 'index',
1619
+ 'middle',
1620
+ 'ring',
1621
+ 'pinky'
1622
+ ]
1623
+ }
1624
+ ]),
1625
+ ["RightShoulder"]: sideJointPatterns('right', [
1626
+ {
1627
+ all: [
1628
+ 'shoulder'
1629
+ ]
1630
+ },
1631
+ {
1632
+ all: [
1633
+ 'clavicle'
1634
+ ]
1635
+ }
1636
+ ]),
1637
+ ["RightUpperArm"]: sideJointPatterns('right', [
1638
+ {
1639
+ all: [
1640
+ 'arm'
1641
+ ],
1642
+ none: [
1643
+ 'upper',
1644
+ 'fore',
1645
+ 'lower',
1646
+ 'hand',
1647
+ 'twist',
1648
+ 'shoulder'
1649
+ ]
1650
+ },
1651
+ {
1652
+ all: [
1653
+ 'upper',
1654
+ 'arm'
1655
+ ],
1656
+ none: [
1657
+ 'twist'
1658
+ ]
1659
+ }
1660
+ ]),
1661
+ ["RightLowerArm"]: sideJointPatterns('right', [
1662
+ {
1663
+ all: [
1664
+ 'fore',
1665
+ 'arm'
1666
+ ],
1667
+ none: [
1668
+ 'twist'
1669
+ ]
1670
+ },
1671
+ {
1672
+ all: [
1673
+ 'lower',
1674
+ 'arm'
1675
+ ],
1676
+ none: [
1677
+ 'twist'
1678
+ ]
1679
+ }
1680
+ ]),
1681
+ ["RightHand"]: sideJointPatterns('right', [
1682
+ {
1683
+ all: [
1684
+ 'hand'
1685
+ ],
1686
+ none: [
1687
+ 'thumb',
1688
+ 'index',
1689
+ 'middle',
1690
+ 'ring',
1691
+ 'pinky'
1692
+ ]
1693
+ }
1694
+ ]),
1695
+ ["LeftUpperLeg"]: sideJointPatterns('left', [
1696
+ {
1697
+ all: [
1698
+ 'up',
1699
+ 'leg'
1700
+ ]
1701
+ },
1702
+ {
1703
+ all: [
1704
+ 'upper',
1705
+ 'leg'
1706
+ ]
1707
+ },
1708
+ {
1709
+ all: [
1710
+ 'thigh'
1711
+ ]
1712
+ }
1713
+ ]),
1714
+ ["LeftLowerLeg"]: sideJointPatterns('left', [
1715
+ {
1716
+ all: [
1717
+ 'leg'
1718
+ ],
1719
+ none: [
1720
+ 'upper',
1721
+ 'up',
1722
+ 'thigh',
1723
+ 'foot',
1724
+ 'toe'
1725
+ ]
1726
+ },
1727
+ {
1728
+ all: [
1729
+ 'lower',
1730
+ 'leg'
1731
+ ]
1732
+ },
1733
+ {
1734
+ all: [
1735
+ 'calf'
1736
+ ]
1737
+ },
1738
+ {
1739
+ all: [
1740
+ 'shin'
1741
+ ]
1742
+ }
1743
+ ]),
1744
+ ["LeftFoot"]: sideJointPatterns('left', [
1745
+ {
1746
+ all: [
1747
+ 'foot'
1748
+ ],
1749
+ none: [
1750
+ 'toe'
1751
+ ]
1752
+ }
1753
+ ]),
1754
+ ["LeftToes"]: sideJointPatterns('left', [
1755
+ {
1756
+ all: [
1757
+ 'toe',
1758
+ 'base'
1759
+ ]
1760
+ },
1761
+ {
1762
+ all: [
1763
+ 'toe'
1764
+ ],
1765
+ none: [
1766
+ 'end'
1767
+ ]
1768
+ }
1769
+ ]),
1770
+ ["RightUpperLeg"]: sideJointPatterns('right', [
1771
+ {
1772
+ all: [
1773
+ 'up',
1774
+ 'leg'
1775
+ ]
1776
+ },
1777
+ {
1778
+ all: [
1779
+ 'upper',
1780
+ 'leg'
1781
+ ]
1782
+ },
1783
+ {
1784
+ all: [
1785
+ 'thigh'
1786
+ ]
1787
+ }
1788
+ ]),
1789
+ ["RightLowerLeg"]: sideJointPatterns('right', [
1790
+ {
1791
+ all: [
1792
+ 'leg'
1793
+ ],
1794
+ none: [
1795
+ 'upper',
1796
+ 'up',
1797
+ 'thigh',
1798
+ 'foot',
1799
+ 'toe'
1800
+ ]
1801
+ },
1802
+ {
1803
+ all: [
1804
+ 'lower',
1805
+ 'leg'
1806
+ ]
1807
+ },
1808
+ {
1809
+ all: [
1810
+ 'calf'
1811
+ ]
1812
+ },
1813
+ {
1814
+ all: [
1815
+ 'shin'
1816
+ ]
1817
+ }
1818
+ ]),
1819
+ ["RightFoot"]: sideJointPatterns('right', [
1820
+ {
1821
+ all: [
1822
+ 'foot'
1823
+ ],
1824
+ none: [
1825
+ 'toe'
1826
+ ]
1827
+ }
1828
+ ]),
1829
+ ["RightToes"]: sideJointPatterns('right', [
1830
+ {
1831
+ all: [
1832
+ 'toe',
1833
+ 'base'
1834
+ ]
1835
+ },
1836
+ {
1837
+ all: [
1838
+ 'toe'
1839
+ ],
1840
+ none: [
1841
+ 'end'
1842
+ ]
1843
+ }
1844
+ ])
1845
+ };
1846
+ }
1847
+ static createBipedHumanoidBodyProfile() {
1848
+ return {
1849
+ ["Hips"]: [
1850
+ jointPattern([
1851
+ 'pelvis'
1852
+ ]),
1853
+ jointPattern([
1854
+ 'hips'
1855
+ ])
1856
+ ],
1857
+ ["Spine"]: [
1858
+ jointPattern([
1859
+ 'spine'
1860
+ ], [
1861
+ '1',
1862
+ '2',
1863
+ '3'
1864
+ ])
1865
+ ],
1866
+ ["Chest"]: [
1867
+ jointPattern([
1868
+ 'spine',
1869
+ '1'
1870
+ ]),
1871
+ jointPattern([
1872
+ 'chest'
1873
+ ], [
1874
+ 'upper'
1875
+ ])
1876
+ ],
1877
+ ["UpperChest"]: [
1878
+ jointPattern([
1879
+ 'spine',
1880
+ '2'
1881
+ ]),
1882
+ jointPattern([
1883
+ 'spine',
1884
+ '3'
1885
+ ]),
1886
+ jointPattern([
1887
+ 'upper',
1888
+ 'chest'
1889
+ ])
1890
+ ],
1891
+ ["Neck"]: [
1892
+ jointPattern([
1893
+ 'neck'
1894
+ ], [
1895
+ '1',
1896
+ '2'
1897
+ ])
1898
+ ],
1899
+ ["Head"]: [
1900
+ jointPattern([
1901
+ 'head'
1902
+ ], [
1903
+ 'top',
1904
+ 'end',
1905
+ 'nub'
1906
+ ])
1907
+ ],
1908
+ ["LeftShoulder"]: sideJointPatterns('left', [
1909
+ {
1910
+ all: [
1911
+ 'clavicle'
1912
+ ]
1913
+ },
1914
+ {
1915
+ all: [
1916
+ 'shoulder'
1917
+ ]
1918
+ }
1919
+ ]),
1920
+ ["LeftUpperArm"]: sideJointPatterns('left', [
1921
+ {
1922
+ all: [
1923
+ 'upper',
1924
+ 'arm'
1925
+ ],
1926
+ none: [
1927
+ 'twist'
1928
+ ]
1929
+ },
1930
+ {
1931
+ all: [
1932
+ 'arm'
1933
+ ],
1934
+ none: [
1935
+ 'fore',
1936
+ 'lower',
1937
+ 'hand',
1938
+ 'twist',
1939
+ 'shoulder',
1940
+ 'clavicle'
1941
+ ]
1942
+ }
1943
+ ]),
1944
+ ["LeftLowerArm"]: sideJointPatterns('left', [
1945
+ {
1946
+ all: [
1947
+ 'fore',
1948
+ 'arm'
1949
+ ],
1950
+ none: [
1951
+ 'twist'
1952
+ ]
1953
+ },
1954
+ {
1955
+ all: [
1956
+ 'lower',
1957
+ 'arm'
1958
+ ],
1959
+ none: [
1960
+ 'twist'
1961
+ ]
1962
+ }
1963
+ ]),
1964
+ ["LeftHand"]: sideJointPatterns('left', [
1965
+ {
1966
+ all: [
1967
+ 'hand'
1968
+ ],
1969
+ none: [
1970
+ 'finger',
1971
+ 'thumb',
1972
+ 'index',
1973
+ 'middle',
1974
+ 'ring',
1975
+ 'pinky'
1976
+ ]
1977
+ }
1978
+ ]),
1979
+ ["RightShoulder"]: sideJointPatterns('right', [
1980
+ {
1981
+ all: [
1982
+ 'clavicle'
1983
+ ]
1984
+ },
1985
+ {
1986
+ all: [
1987
+ 'shoulder'
1988
+ ]
1989
+ }
1990
+ ]),
1991
+ ["RightUpperArm"]: sideJointPatterns('right', [
1992
+ {
1993
+ all: [
1994
+ 'upper',
1995
+ 'arm'
1996
+ ],
1997
+ none: [
1998
+ 'twist'
1999
+ ]
2000
+ },
2001
+ {
2002
+ all: [
2003
+ 'arm'
2004
+ ],
2005
+ none: [
2006
+ 'fore',
2007
+ 'lower',
2008
+ 'hand',
2009
+ 'twist',
2010
+ 'shoulder',
2011
+ 'clavicle'
2012
+ ]
2013
+ }
2014
+ ]),
2015
+ ["RightLowerArm"]: sideJointPatterns('right', [
2016
+ {
2017
+ all: [
2018
+ 'fore',
2019
+ 'arm'
2020
+ ],
2021
+ none: [
2022
+ 'twist'
2023
+ ]
2024
+ },
2025
+ {
2026
+ all: [
2027
+ 'lower',
2028
+ 'arm'
2029
+ ],
2030
+ none: [
2031
+ 'twist'
2032
+ ]
2033
+ }
2034
+ ]),
2035
+ ["RightHand"]: sideJointPatterns('right', [
2036
+ {
2037
+ all: [
2038
+ 'hand'
2039
+ ],
2040
+ none: [
2041
+ 'finger',
2042
+ 'thumb',
2043
+ 'index',
2044
+ 'middle',
2045
+ 'ring',
2046
+ 'pinky'
2047
+ ]
2048
+ }
2049
+ ]),
2050
+ ["LeftUpperLeg"]: sideJointPatterns('left', [
2051
+ {
2052
+ all: [
2053
+ 'thigh'
2054
+ ]
2055
+ },
2056
+ {
2057
+ all: [
2058
+ 'upper',
2059
+ 'leg'
2060
+ ]
2061
+ },
2062
+ {
2063
+ all: [
2064
+ 'up',
2065
+ 'leg'
2066
+ ]
2067
+ }
2068
+ ]),
2069
+ ["LeftLowerLeg"]: sideJointPatterns('left', [
2070
+ {
2071
+ all: [
2072
+ 'calf'
2073
+ ]
2074
+ },
2075
+ {
2076
+ all: [
2077
+ 'lower',
2078
+ 'leg'
2079
+ ]
2080
+ },
2081
+ {
2082
+ all: [
2083
+ 'leg'
2084
+ ],
2085
+ none: [
2086
+ 'upper',
2087
+ 'up',
2088
+ 'thigh',
2089
+ 'foot',
2090
+ 'toe'
2091
+ ]
2092
+ }
2093
+ ]),
2094
+ ["LeftFoot"]: sideJointPatterns('left', [
2095
+ {
2096
+ all: [
2097
+ 'foot'
2098
+ ],
2099
+ none: [
2100
+ 'toe'
2101
+ ]
2102
+ }
2103
+ ]),
2104
+ ["LeftToes"]: sideJointPatterns('left', [
2105
+ {
2106
+ all: [
2107
+ 'toe',
2108
+ '0'
2109
+ ]
2110
+ },
2111
+ {
2112
+ all: [
2113
+ 'toe'
2114
+ ],
2115
+ none: [
2116
+ 'end'
2117
+ ]
2118
+ }
2119
+ ]),
2120
+ ["RightUpperLeg"]: sideJointPatterns('right', [
2121
+ {
2122
+ all: [
2123
+ 'thigh'
2124
+ ]
2125
+ },
2126
+ {
2127
+ all: [
2128
+ 'upper',
2129
+ 'leg'
2130
+ ]
2131
+ },
2132
+ {
2133
+ all: [
2134
+ 'up',
2135
+ 'leg'
2136
+ ]
2137
+ }
2138
+ ]),
2139
+ ["RightLowerLeg"]: sideJointPatterns('right', [
2140
+ {
2141
+ all: [
2142
+ 'calf'
2143
+ ]
2144
+ },
2145
+ {
2146
+ all: [
2147
+ 'lower',
2148
+ 'leg'
2149
+ ]
2150
+ },
2151
+ {
2152
+ all: [
2153
+ 'leg'
2154
+ ],
2155
+ none: [
2156
+ 'upper',
2157
+ 'up',
2158
+ 'thigh',
2159
+ 'foot',
2160
+ 'toe'
2161
+ ]
2162
+ }
2163
+ ]),
2164
+ ["RightFoot"]: sideJointPatterns('right', [
2165
+ {
2166
+ all: [
2167
+ 'foot'
2168
+ ],
2169
+ none: [
2170
+ 'toe'
2171
+ ]
2172
+ }
2173
+ ]),
2174
+ ["RightToes"]: sideJointPatterns('right', [
2175
+ {
2176
+ all: [
2177
+ 'toe',
2178
+ '0'
2179
+ ]
2180
+ },
2181
+ {
2182
+ all: [
2183
+ 'toe'
2184
+ ],
2185
+ none: [
2186
+ 'end'
2187
+ ]
2188
+ }
2189
+ ])
2190
+ };
2191
+ }
2192
+ static isParentOf(parent, child) {
2193
+ while(child && child !== parent){
2194
+ child = child.parent;
2195
+ }
2196
+ return child === parent;
2197
+ }
2198
+ static iterateNode(root, callback) {
2199
+ if (callback(root)) {
2200
+ return true;
2201
+ }
2202
+ for (const child of root.children){
2203
+ if (this.iterateNode(child, callback)) {
2204
+ return true;
2205
+ }
2206
+ }
2207
+ return false;
2208
+ }
2209
+ static tryExtractHumanoidJointsByBodyProfile(root, bodyProfile) {
2210
+ const nodes = this.collectHumanoidJointNodeInfos(root);
2211
+ const used = new Set();
2212
+ const bodyCandidates = this.matchHumanoidBodyProfile(nodes, bodyProfile, used, new Set([
2213
+ "Chest",
2214
+ "UpperChest"
2215
+ ]));
2216
+ if (!bodyCandidates) {
2217
+ return null;
2218
+ }
2219
+ const body = this.completeHumanoidBody(bodyCandidates);
2220
+ if (!body) {
2221
+ return null;
2222
+ }
2223
+ const leftHand = this.tryExtractOptionalHumanoidHand(nodes, 'left', used);
2224
+ const rightHand = this.tryExtractOptionalHumanoidHand(nodes, 'right', used);
2225
+ const result = {
2226
+ body,
2227
+ leftHand,
2228
+ rightHand
2229
+ };
2230
+ return this.validateHumanoidJointExtraction(result) ? result : null;
2231
+ }
2232
+ /**
2233
+ * Attempt to extract humanoid joint mappings from the skeleton's joints based on their names.
2234
+ *
2235
+ * This method looks for joints with names matching the standardized `HumanoidBodyRig` and `HumanoidHandRig` enums.
2236
+ * If a complete mapping is found, it returns an object containing the mapped joints for the body and hands.
2237
+ * If any required joint is missing, it returns `null`.
2238
+ *
2239
+ * This method tries to find the best match for humanoid rigs, designed to work with Mixamo, VRoid, Unity Humanoid,
2240
+ * Biped, and similar skeletons. It is not guaranteed to work with all models, and may require manual adjustments or
2241
+ * custom modifiers for non-standard rigs.
2242
+ *
2243
+ * @param root - The root scene node to search for humanoid joints.
2244
+ * @returns An object containing the mapped body and hand joints if a complete humanoid rig is detected, otherwise `null`.
2245
+ */ static tryExtractHumanoidJoints(root) {
2246
+ return this.tryExtractHumanoidJointsMixamo(root) ?? this.tryExtractHumanoidJointsVRM(root) ?? this.tryExtractHumanoidJointsUnityHumanoid(root) ?? this.tryExtractHumanoidJointsBiped(root);
2247
+ }
2248
+ static tryExtractHumanoidJointsMixamo(root) {
2249
+ return this.tryExtractHumanoidJointsByBodyProfile(root, this.createMixamoHumanoidBodyProfile());
2250
+ }
2251
+ static tryExtractHumanoidJointsVRM(root) {
2252
+ return this.tryExtractHumanoidJointsByBodyProfile(root, this.createStandardHumanoidBodyProfile());
2253
+ }
2254
+ static tryExtractHumanoidJointsUnityHumanoid(root) {
2255
+ return this.tryExtractHumanoidJointsByBodyProfile(root, this.createStandardHumanoidBodyProfile());
2256
+ }
2257
+ static tryExtractHumanoidJointsBiped(root) {
2258
+ return this.tryExtractHumanoidJointsByBodyProfile(root, this.createBipedHumanoidBodyProfile());
2259
+ }
388
2260
  }
389
2261
 
390
- export { Skeleton };
2262
+ export { HumanoidBodyRig, HumanoidHandRig, Skeleton };
391
2263
  //# sourceMappingURL=skeleton.js.map