@woosh/meep-engine 3.40.0 → 3.41.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 (203) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/quaternion/swing_twist_clamp.d.ts +24 -0
  3. package/src/core/geom/3d/quaternion/swing_twist_clamp.d.ts.map +1 -0
  4. package/src/core/geom/3d/quaternion/swing_twist_clamp.js +62 -0
  5. package/src/core/geom/3d/quaternion/swing_twist_compose.d.ts +15 -0
  6. package/src/core/geom/3d/quaternion/swing_twist_compose.d.ts.map +1 -0
  7. package/src/core/geom/3d/quaternion/swing_twist_compose.js +36 -0
  8. package/src/core/geom/3d/quaternion/swing_twist_error.d.ts +39 -0
  9. package/src/core/geom/3d/quaternion/swing_twist_error.d.ts.map +1 -0
  10. package/src/core/geom/3d/quaternion/swing_twist_error.js +85 -0
  11. package/src/core/geom/vec3/v3_rigid_align_paired_unit_vectors.d.ts +11 -6
  12. package/src/core/geom/vec3/v3_rigid_align_paired_unit_vectors.d.ts.map +1 -1
  13. package/src/core/geom/vec3/v3_rigid_align_paired_unit_vectors.js +27 -13
  14. package/src/core/math/linalg/eigen/matrix_symmetric_4x4_eigen.d.ts +25 -0
  15. package/src/core/math/linalg/eigen/matrix_symmetric_4x4_eigen.d.ts.map +1 -0
  16. package/src/core/math/linalg/eigen/matrix_symmetric_4x4_eigen.js +166 -0
  17. package/src/core/math/linalg/eigen/matrix_top_eigenvector_power_iteration.d.ts +5 -3
  18. package/src/core/math/linalg/eigen/matrix_top_eigenvector_power_iteration.d.ts.map +1 -1
  19. package/src/core/math/linalg/eigen/matrix_top_eigenvector_power_iteration.js +5 -3
  20. package/src/engine/animation/curve/AnimationCurve.d.ts +3 -0
  21. package/src/engine/animation/curve/AnimationCurve.d.ts.map +1 -1
  22. package/src/engine/animation/curve/AnimationCurve.js +6 -1
  23. package/src/engine/ecs/EntityManager.d.ts +9 -1
  24. package/src/engine/ecs/EntityManager.d.ts.map +1 -1
  25. package/src/engine/ecs/EntityManager.js +26 -27
  26. package/src/engine/ecs/gpu/GPUStateAuthority.d.ts +20 -0
  27. package/src/engine/ecs/gpu/GPUStateAuthority.d.ts.map +1 -1
  28. package/src/engine/ecs/gpu/GPUStateAuthority.js +21 -0
  29. package/src/engine/ecs/gpu/GPU_AUTHORITY_EVENT_HOLD_CHANGE.d.ts +13 -0
  30. package/src/engine/ecs/gpu/GPU_AUTHORITY_EVENT_HOLD_CHANGE.d.ts.map +1 -0
  31. package/src/engine/ecs/gpu/GPU_AUTHORITY_EVENT_HOLD_CHANGE.js +12 -0
  32. package/src/engine/ecs/gpu/gpu_authority_grant.d.ts +6 -0
  33. package/src/engine/ecs/gpu/gpu_authority_grant.d.ts.map +1 -1
  34. package/src/engine/ecs/gpu/gpu_authority_grant.js +7 -1
  35. package/src/engine/ecs/gpu/gpu_authority_held.d.ts +14 -0
  36. package/src/engine/ecs/gpu/gpu_authority_held.d.ts.map +1 -0
  37. package/src/engine/ecs/gpu/gpu_authority_held.js +23 -0
  38. package/src/engine/ecs/gpu/gpu_authority_hold.d.ts +23 -0
  39. package/src/engine/ecs/gpu/gpu_authority_hold.d.ts.map +1 -0
  40. package/src/engine/ecs/gpu/gpu_authority_hold.js +41 -0
  41. package/src/engine/ecs/gpu/gpu_authority_unhold.d.ts +14 -0
  42. package/src/engine/ecs/gpu/gpu_authority_unhold.d.ts.map +1 -0
  43. package/src/engine/ecs/gpu/gpu_authority_unhold.js +32 -0
  44. package/src/engine/ecs/ik/IKGoal.d.ts +59 -0
  45. package/src/engine/ecs/ik/IKGoal.d.ts.map +1 -0
  46. package/src/engine/ecs/ik/IKGoal.js +83 -0
  47. package/src/engine/ecs/ik/IKGoalSystem.d.ts +75 -0
  48. package/src/engine/ecs/ik/IKGoalSystem.d.ts.map +1 -0
  49. package/src/engine/ecs/ik/IKGoalSystem.js +1456 -0
  50. package/src/engine/ecs/ik/PLAN.md +179 -0
  51. package/src/engine/ecs/speaker/VoiceSystem.d.ts +2 -2
  52. package/src/engine/ecs/speaker/VoiceSystem.d.ts.map +1 -1
  53. package/src/engine/ecs/storage/BinaryBufferSerializer.d.ts.map +1 -1
  54. package/src/engine/ecs/storage/BinaryBufferSerializer.js +4 -5
  55. package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +7 -3
  56. package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
  57. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +1 -2
  58. package/src/engine/ecs/system/orderSystemsByDeclaredAccess.d.ts +33 -0
  59. package/src/engine/ecs/system/orderSystemsByDeclaredAccess.d.ts.map +1 -0
  60. package/src/engine/ecs/system/orderSystemsByDeclaredAccess.js +282 -0
  61. package/src/engine/ecs/transform/T64WorldCache.js +139 -139
  62. package/src/engine/ecs/transform/t64_evaluate_local.d.ts +25 -0
  63. package/src/engine/ecs/transform/t64_evaluate_local.d.ts.map +1 -0
  64. package/src/engine/ecs/transform/t64_evaluate_local.js +35 -0
  65. package/src/engine/ecs/transform/t64_evaluate_world.d.ts +5 -3
  66. package/src/engine/ecs/transform/t64_evaluate_world.d.ts.map +1 -1
  67. package/src/engine/ecs/transform/t64_evaluate_world.js +128 -126
  68. package/src/engine/graphics3/AnimationGraphSystem.d.ts +2 -0
  69. package/src/engine/graphics3/AnimationGraphSystem.d.ts.map +1 -1
  70. package/src/engine/graphics3/AnimationGraphSystem.js +337 -329
  71. package/src/engine/graphics3/AnimationSystem.d.ts +2 -0
  72. package/src/engine/graphics3/AnimationSystem.d.ts.map +1 -1
  73. package/src/engine/graphics3/AnimationSystem.js +360 -352
  74. package/src/engine/physics/cloth/MEASUREMENTS.md +130 -0
  75. package/src/engine/physics/cloth/PLAN.md +16 -1
  76. package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts +5 -6
  77. package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts.map +1 -1
  78. package/src/engine/physics/cloth/ecs/ClothColliderSystem.js +5 -6
  79. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +4 -7
  80. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
  81. package/src/engine/physics/cloth/ecs/ClothInstance.js +4 -7
  82. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +4 -3
  83. package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
  84. package/src/engine/physics/cloth/ecs/ClothSystem.js +4 -3
  85. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
  86. package/src/engine/physics/cloth/ecs/ClothWorld.js +10 -0
  87. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts +24 -24
  88. package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts.map +1 -1
  89. package/src/engine/physics/cloth/ecs/cloth_seed_rig.js +46 -43
  90. package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts +1 -1
  91. package/src/engine/physics/cloth/ecs/cloth_write_back.js +1 -1
  92. package/src/engine/physics/constraint/solve_constraints.d.ts +0 -35
  93. package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
  94. package/src/engine/physics/constraint/solve_constraints.js +1 -83
  95. package/src/engine/physics/ecs/ContactProbe.d.ts +73 -0
  96. package/src/engine/physics/ecs/ContactProbe.d.ts.map +1 -0
  97. package/src/engine/physics/ecs/ContactProbe.js +103 -0
  98. package/src/engine/physics/ecs/ContactProbeSystem.d.ts +44 -0
  99. package/src/engine/physics/ecs/ContactProbeSystem.d.ts.map +1 -0
  100. package/src/engine/physics/ecs/ContactProbeSystem.js +397 -0
  101. package/src/engine/physics/fluid/ecs/FluidComponent.d.ts +1 -1
  102. package/src/engine/physics/fluid/ecs/FluidComponent.js +1 -1
  103. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  104. package/src/engine/physics/inverse_kinematics/frame_fabrik/FrameFabrikProblem.d.ts +205 -0
  105. package/src/engine/physics/inverse_kinematics/frame_fabrik/FrameFabrikProblem.d.ts.map +1 -0
  106. package/src/engine/physics/inverse_kinematics/frame_fabrik/FrameFabrikProblem.js +245 -0
  107. package/src/engine/physics/inverse_kinematics/frame_fabrik/frame_fabrik_limit_from_joint.d.ts +21 -0
  108. package/src/engine/physics/inverse_kinematics/frame_fabrik/frame_fabrik_limit_from_joint.d.ts.map +1 -0
  109. package/src/engine/physics/inverse_kinematics/frame_fabrik/frame_fabrik_limit_from_joint.js +63 -0
  110. package/src/engine/physics/inverse_kinematics/frame_fabrik/frame_fabrik_solve.d.ts +33 -0
  111. package/src/engine/physics/inverse_kinematics/frame_fabrik/frame_fabrik_solve.d.ts.map +1 -0
  112. package/src/engine/physics/inverse_kinematics/frame_fabrik/frame_fabrik_solve.js +874 -0
  113. package/src/engine/physics/inverse_kinematics/joint_hinge_mid_axis.d.ts +27 -0
  114. package/src/engine/physics/inverse_kinematics/joint_hinge_mid_axis.d.ts.map +1 -0
  115. package/src/engine/physics/inverse_kinematics/joint_hinge_mid_axis.js +77 -0
  116. package/src/engine/physics/inverse_kinematics/two_bone_path_solve.d.ts +36 -0
  117. package/src/engine/physics/inverse_kinematics/two_bone_path_solve.d.ts.map +1 -0
  118. package/src/engine/physics/inverse_kinematics/two_bone_path_solve.js +97 -0
  119. package/src/engine/physics/inverse_kinematics/two_joint_ik.d.ts +33 -11
  120. package/src/engine/physics/inverse_kinematics/two_joint_ik.d.ts.map +1 -1
  121. package/src/engine/physics/inverse_kinematics/two_joint_ik.js +158 -67
  122. package/src/shade/renderer/animation/ANIMATION_TRACK_BINDING_STRUCT.d.ts.map +1 -1
  123. package/src/shade/renderer/animation/ANIMATION_TRACK_BINDING_STRUCT.js +10 -1
  124. package/src/shade/renderer/animation/GPUAnimationManager.d.ts +10 -4
  125. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  126. package/src/shade/renderer/animation/GPUAnimationManager.js +251 -23
  127. package/src/shade/renderer/animation/POSE_ACCUMULATOR_LAYOUT.d.ts +12 -4
  128. package/src/shade/renderer/animation/POSE_ACCUMULATOR_LAYOUT.d.ts.map +1 -1
  129. package/src/shade/renderer/animation/POSE_ACCUMULATOR_LAYOUT.js +12 -4
  130. package/src/shade/renderer/animation/chunk_pose_accumulator.d.ts.map +1 -1
  131. package/src/shade/renderer/animation/chunk_pose_accumulator.js +14 -1
  132. package/src/shade/renderer/animation/pose/PoseChannelIndex.d.ts +78 -0
  133. package/src/shade/renderer/animation/pose/PoseChannelIndex.d.ts.map +1 -0
  134. package/src/shade/renderer/animation/pose/PoseChannelIndex.js +274 -0
  135. package/src/shade/renderer/animation/pose/PosePlayback.d.ts +40 -3
  136. package/src/shade/renderer/animation/pose/PosePlayback.d.ts.map +1 -1
  137. package/src/shade/renderer/animation/pose/PosePlayback.js +77 -3
  138. package/src/shade/renderer/animation/pose/pose_evaluate_local.d.ts +21 -3
  139. package/src/shade/renderer/animation/pose/pose_evaluate_local.d.ts.map +1 -1
  140. package/src/shade/renderer/animation/pose/pose_evaluate_local.js +79 -11
  141. package/src/shade/renderer/animation/shader_animation_apply.js +1 -1
  142. package/src/shade/renderer/animation/shader_animation_pose_resolve.d.ts +3 -2
  143. package/src/shade/renderer/animation/shader_animation_pose_resolve.d.ts.map +1 -1
  144. package/src/shade/renderer/animation/shader_animation_pose_resolve.js +187 -176
  145. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  146. package/src/shade/renderer/object_property/chunk_write_object_property.d.ts +3 -1
  147. package/src/shade/renderer/object_property/chunk_write_object_property.d.ts.map +1 -1
  148. package/src/shade/renderer/object_property/chunk_write_object_property.js +5 -2
  149. package/src/shade/renderer/object_property/chunk_write_object_property_node3d.d.ts +4 -0
  150. package/src/shade/renderer/object_property/chunk_write_object_property_node3d.d.ts.map +1 -1
  151. package/src/shade/renderer/object_property/chunk_write_object_property_node3d.js +6 -1
  152. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -1
  153. package/src/shade/renderer/scene/optimization/geometry/geometry_deduplicate.d.ts +1 -1
  154. package/src/shade/renderer/scene/optimization/geometry/geometry_deduplicate.d.ts.map +1 -1
  155. package/src/shade/renderer/scene/optimization/geometry/geometry_deduplicate.js +135 -73
  156. package/src/shade/renderer/scene/optimization/geometry/internal/cover_greedy.d.ts +7 -0
  157. package/src/shade/renderer/scene/optimization/geometry/internal/cover_greedy.d.ts.map +1 -1
  158. package/src/shade/renderer/scene/optimization/geometry/internal/cover_greedy.js +29 -1
  159. package/src/shade/renderer/scene/optimization/geometry/internal/face_neighbourhood_hash.d.ts +19 -0
  160. package/src/shade/renderer/scene/optimization/geometry/internal/face_neighbourhood_hash.d.ts.map +1 -0
  161. package/src/shade/renderer/scene/optimization/geometry/internal/face_neighbourhood_hash.js +167 -0
  162. package/src/shade/renderer/scene/optimization/geometry/internal/neighbourhood_anchor_index.d.ts +25 -0
  163. package/src/shade/renderer/scene/optimization/geometry/internal/neighbourhood_anchor_index.d.ts.map +1 -0
  164. package/src/shade/renderer/scene/optimization/geometry/internal/neighbourhood_anchor_index.js +124 -0
  165. package/src/shade/renderer/scene/optimization/geometry/internal/seed_correspondence_from_face_pair.d.ts +19 -0
  166. package/src/shade/renderer/scene/optimization/geometry/internal/seed_correspondence_from_face_pair.d.ts.map +1 -0
  167. package/src/shade/renderer/scene/optimization/geometry/internal/seed_correspondence_from_face_pair.js +48 -0
  168. package/src/shade/renderer/scene/rows/GPUSceneRows.d.ts +17 -1
  169. package/src/shade/renderer/scene/rows/GPUSceneRows.d.ts.map +1 -1
  170. package/src/shade/renderer/scene/rows/GPUSceneRows.js +29 -3
  171. package/src/engine/ecs/ik/IKConstraint.d.ts +0 -52
  172. package/src/engine/ecs/ik/IKConstraint.d.ts.map +0 -1
  173. package/src/engine/ecs/ik/IKConstraint.js +0 -95
  174. package/src/engine/ecs/ik/IKProblem.d.ts +0 -45
  175. package/src/engine/ecs/ik/IKProblem.d.ts.map +0 -1
  176. package/src/engine/ecs/ik/IKProblem.js +0 -58
  177. package/src/engine/ecs/ik/IKSolver.d.ts +0 -8
  178. package/src/engine/ecs/ik/IKSolver.d.ts.map +0 -1
  179. package/src/engine/ecs/ik/IKSolver.js +0 -9
  180. package/src/engine/ecs/ik/InverseKinematics.d.ts +0 -48
  181. package/src/engine/ecs/ik/InverseKinematics.d.ts.map +0 -1
  182. package/src/engine/ecs/ik/InverseKinematics.js +0 -119
  183. package/src/engine/ecs/ik/InverseKinematicsSerializationAdapter.d.ts +0 -19
  184. package/src/engine/ecs/ik/InverseKinematicsSerializationAdapter.d.ts.map +0 -1
  185. package/src/engine/ecs/ik/InverseKinematicsSerializationAdapter.js +0 -72
  186. package/src/engine/ecs/ik/InverseKinematicsSystem.d.ts +0 -54
  187. package/src/engine/ecs/ik/InverseKinematicsSystem.d.ts.map +0 -1
  188. package/src/engine/ecs/ik/InverseKinematicsSystem.js +0 -212
  189. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.d.ts +0 -8
  190. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.d.ts.map +0 -1
  191. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +0 -153
  192. package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.d.ts +0 -5
  193. package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.d.ts.map +0 -1
  194. package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.js +0 -120
  195. package/src/engine/ecs/ik/ik_bone_chain.d.ts +0 -64
  196. package/src/engine/ecs/ik/ik_bone_chain.d.ts.map +0 -1
  197. package/src/engine/ecs/ik/ik_bone_chain.js +0 -142
  198. package/src/engine/ecs/ik/ik_probe_terrain_contact.d.ts +0 -42
  199. package/src/engine/ecs/ik/ik_probe_terrain_contact.d.ts.map +0 -1
  200. package/src/engine/ecs/ik/ik_probe_terrain_contact.js +0 -123
  201. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts +0 -18
  202. package/src/engine/ecs/ik/ik_write_local_rotation.d.ts.map +0 -1
  203. package/src/engine/ecs/ik/ik_write_local_rotation.js +0 -32
@@ -0,0 +1,874 @@
1
+ import { assert } from "../../../../core/assert.js";
2
+ import { quat3_multiply_array } from "../../../../core/geom/3d/quaternion/quat3_multiply.js";
3
+ import { quat3_shortest_arc } from "../../../../core/geom/3d/quaternion/quat3_shortest_arc.js";
4
+ import { swing_twist_clamp_array } from "../../../../core/geom/3d/quaternion/swing_twist_clamp.js";
5
+ import { v3_quaternion_apply_array } from "../../../../core/geom/vec3/v3_quaternion_apply.js";
6
+
7
+ /**
8
+ * Power-iteration steps a branch joint's fit takes from its carried rotation, per pass.
9
+ *
10
+ * The steps are part of the mechanism, not a precision setting. A branch whose children lie nearly
11
+ * along one line (a chest, its shoulders either side) barely determines its turn about that line,
12
+ * and a fit started from the carried rotation that takes a few steps leaves that turn where the
13
+ * carried rotation had it. An exact fit (Jacobi) answers it from rounding: at convergence it bent
14
+ * the chest joint 40-175 degrees over the probe cases this one bends 0-54.
15
+ *
16
+ * @type {number}
17
+ */
18
+ const FIT_STEPS = 8;
19
+
20
+ /**
21
+ * Below this fraction of its bone's length, a direction to fit a joint toward is no direction: the
22
+ * child sits on the joint, and a turn toward it divides 0/0.
23
+ *
24
+ * @type {number}
25
+ */
26
+ const DEGENERATE_DIRECTION = 1e-12;
27
+
28
+ /**
29
+ * The rotation a joint would have if the solve left it alone relative to its parent.
30
+ * @type {Float64Array}
31
+ */
32
+ const carried = new Float64Array(4);
33
+
34
+ /**
35
+ * `[x, y, z]` scratch.
36
+ * @type {Float64Array}
37
+ */
38
+ const v3 = new Float64Array(3);
39
+
40
+ /**
41
+ * Turn and position scratch for one fit.
42
+ * @type {Float64Array}
43
+ */
44
+ const turn = new Float64Array(4);
45
+
46
+ /**
47
+ * The frame above a limited joint, and the joint's turn in it.
48
+ * @type {Float64Array}
49
+ */
50
+ const above = new Float64Array(4);
51
+
52
+ /**
53
+ * @type {Float64Array}
54
+ */
55
+ const relative = new Float64Array(4);
56
+
57
+ /**
58
+ * Fit the world rotation of joint `i` so its weighted children's offsets point where the children
59
+ * are, starting from `carried`, and write it to `rotation[i]`.
60
+ *
61
+ * - No weighted child: the carried rotation.
62
+ * - One: the shortest arc from where the carried rotation points the child onto where it is, so the
63
+ * joint's turn about the bone stays the carried one. This is the paper's pull.
64
+ * - More: Horn's quaternion fit of the offsets onto the directions, weighted by link and by both
65
+ * lengths, by {@link FIT_STEPS} steps of power iteration from the carried rotation.
66
+ *
67
+ * A child sitting on the joint has no direction and contributes nothing; with none left, the carried
68
+ * rotation stands.
69
+ *
70
+ * @param {FrameFabrikProblem} problem
71
+ * @param {number} i
72
+ * @returns {boolean} whether the joint was fitted, rather than left at the carried rotation
73
+ */
74
+ function fit_rotation(problem, i) {
75
+ const position = problem.solved_position;
76
+ const rotation = problem.solved_rotation;
77
+ const offset = problem._offset;
78
+ const link_weight = problem._link_weight;
79
+ const child = problem._child;
80
+
81
+ const child_end = problem._child_start[i + 1];
82
+
83
+ const i3 = i * 3;
84
+ const i4 = i * 4;
85
+
86
+ const px = position[i3], py = position[i3 + 1], pz = position[i3 + 2];
87
+
88
+ let weighted = 0;
89
+ let single = -1;
90
+
91
+ for (let c = problem._child_start[i]; c < child_end; c++) {
92
+ const k = child[c];
93
+
94
+ if (link_weight[k] > 0) {
95
+ weighted++;
96
+ single = k;
97
+ }
98
+ }
99
+
100
+ if (weighted === 1) {
101
+ const k3 = single * 3;
102
+
103
+ const ox = offset[k3], oy = offset[k3 + 1], oz = offset[k3 + 2];
104
+
105
+ const dx = position[k3] - px, dy = position[k3 + 1] - py, dz = position[k3 + 2] - pz;
106
+
107
+ const o_sqr = ox * ox + oy * oy + oz * oz;
108
+ const d_sqr = dx * dx + dy * dy + dz * dz;
109
+
110
+ if (d_sqr > DEGENERATE_DIRECTION * DEGENERATE_DIRECTION * o_sqr) {
111
+ // where the carried rotation points the child
112
+ v3_quaternion_apply_array(v3, 0, offset, k3, carried, 0);
113
+
114
+ const u_inverse = 1 / Math.sqrt(o_sqr);
115
+ const d_inverse = 1 / Math.sqrt(d_sqr);
116
+
117
+ quat3_shortest_arc(
118
+ turn, 0,
119
+ v3[0] * u_inverse, v3[1] * u_inverse, v3[2] * u_inverse,
120
+ dx * d_inverse, dy * d_inverse, dz * d_inverse
121
+ );
122
+
123
+ quat3_multiply_array(rotation, i4, turn, 0, carried, 0);
124
+
125
+ return true;
126
+ }
127
+
128
+ weighted = 0;
129
+ }
130
+
131
+ if (weighted === 0) {
132
+ rotation[i4] = carried[0];
133
+ rotation[i4 + 1] = carried[1];
134
+ rotation[i4 + 2] = carried[2];
135
+ rotation[i4 + 3] = carried[3];
136
+
137
+ return false;
138
+ }
139
+
140
+ // Horn (1987): S = sum w b a^T, with a the direction to the child and b its offset
141
+ let Sxx = 0, Sxy = 0, Sxz = 0;
142
+ let Syx = 0, Syy = 0, Syz = 0;
143
+ let Szx = 0, Szy = 0, Szz = 0;
144
+
145
+ // bounds N's spectrum, |lambda| <= s, so N + sI has the fit as its dominant eigenvector
146
+ let s = 0;
147
+
148
+ for (let c = problem._child_start[i]; c < child_end; c++) {
149
+ const k = child[c];
150
+
151
+ const w = link_weight[k];
152
+
153
+ if (w <= 0) {
154
+ continue;
155
+ }
156
+
157
+ const k3 = k * 3;
158
+
159
+ const bx = offset[k3], by = offset[k3 + 1], bz = offset[k3 + 2];
160
+
161
+ const ax = position[k3] - px, ay = position[k3 + 1] - py, az = position[k3 + 2] - pz;
162
+
163
+ Sxx += w * bx * ax;
164
+ Sxy += w * bx * ay;
165
+ Sxz += w * bx * az;
166
+ Syx += w * by * ax;
167
+ Syy += w * by * ay;
168
+ Syz += w * by * az;
169
+ Szx += w * bz * ax;
170
+ Szy += w * bz * ay;
171
+ Szz += w * bz * az;
172
+
173
+ s += w * Math.sqrt((ax * ax + ay * ay + az * az) * (bx * bx + by * by + bz * bz));
174
+ }
175
+
176
+ // scalar-last (x, y, z, w), as v3_rigid_align_paired_unit_vectors orders it, shifted by s
177
+ const Nxx = Sxx - Syy - Szz + s;
178
+ const Nyy = -Sxx + Syy - Szz + s;
179
+ const Nzz = -Sxx - Syy + Szz + s;
180
+ const Nww = Sxx + Syy + Szz + s;
181
+ const Nxy = Sxy + Syx;
182
+ const Nxz = Szx + Sxz;
183
+ const Nxw = Syz - Szy;
184
+ const Nyz = Syz + Szy;
185
+ const Nyw = Szx - Sxz;
186
+ const Nzw = Sxy - Syx;
187
+
188
+ let x = carried[0], y = carried[1], z = carried[2], w = carried[3];
189
+
190
+ for (let step = 0; step < FIT_STEPS; step++) {
191
+ const rx = Nxx * x + Nxy * y + Nxz * z + Nxw * w;
192
+ const ry = Nxy * x + Nyy * y + Nyz * z + Nyw * w;
193
+ const rz = Nxz * x + Nyz * y + Nzz * z + Nzw * w;
194
+ const rw = Nxw * x + Nyw * y + Nzw * z + Nww * w;
195
+
196
+ const length = Math.sqrt(rx * rx + ry * ry + rz * rz + rw * rw);
197
+
198
+ if (!(length > 0)) {
199
+ // every child sits on the joint: nothing to fit
200
+ break;
201
+ }
202
+
203
+ const inverse = 1 / length;
204
+
205
+ x = rx * inverse;
206
+ y = ry * inverse;
207
+ z = rz * inverse;
208
+ w = rw * inverse;
209
+ }
210
+
211
+ rotation[i4] = x;
212
+ rotation[i4 + 1] = y;
213
+ rotation[i4 + 2] = z;
214
+ rotation[i4 + 3] = w;
215
+
216
+ return true;
217
+ }
218
+
219
+ /**
220
+ * Project joint `i`'s fitted rotation into its limits: its turn relative to its parent, taken
221
+ * between the limit's frames as a physics `Joint` measures it (`conj(basis_a) * local * basis_b`),
222
+ * clamped into the box (`swing_twist_clamp_array`). The parent's frame is its current one; a joint
223
+ * whose parent is not posed is limited against that parent's fixed frame.
224
+ *
225
+ * @param {FrameFabrikProblem} problem
226
+ * @param {number} i
227
+ */
228
+ function limit_rotation(problem, i) {
229
+ const rotation = problem.solved_rotation;
230
+ const basis_a = problem.limit_basis_a;
231
+ const basis_b = problem.limit_basis_b;
232
+
233
+ const i4 = i * 4;
234
+ const parent = problem.joint_parent[i];
235
+
236
+ if (parent < 0) {
237
+ // the parent's fixed frame: R_i = above * L_i, as animated
238
+ const local = problem.base_local_rotation;
239
+
240
+ turn[0] = -local[i4];
241
+ turn[1] = -local[i4 + 1];
242
+ turn[2] = -local[i4 + 2];
243
+ turn[3] = local[i4 + 3];
244
+
245
+ quat3_multiply_array(above, 0, problem.base_rotation, i4, turn, 0);
246
+ } else {
247
+ const p4 = parent * 4;
248
+
249
+ above[0] = rotation[p4];
250
+ above[1] = rotation[p4 + 1];
251
+ above[2] = rotation[p4 + 2];
252
+ above[3] = rotation[p4 + 3];
253
+ }
254
+
255
+ // relative = conj(basis_a) * conj(above) * R_i * basis_b
256
+ turn[0] = -above[0];
257
+ turn[1] = -above[1];
258
+ turn[2] = -above[2];
259
+ turn[3] = above[3];
260
+
261
+ quat3_multiply_array(relative, 0, turn, 0, rotation, i4);
262
+ quat3_multiply_array(relative, 0, relative, 0, basis_b, i4);
263
+
264
+ turn[0] = -basis_a[i4];
265
+ turn[1] = -basis_a[i4 + 1];
266
+ turn[2] = -basis_a[i4 + 2];
267
+ turn[3] = basis_a[i4 + 3];
268
+
269
+ quat3_multiply_array(relative, 0, turn, 0, relative, 0);
270
+
271
+ if (!swing_twist_clamp_array(relative, 0, relative, 0, problem.limit_lower, problem.limit_upper, i * 3)) {
272
+ return;
273
+ }
274
+
275
+ // R_i = above * basis_a * relative * conj(basis_b)
276
+ quat3_multiply_array(relative, 0, basis_a, i4, relative, 0);
277
+
278
+ turn[0] = -basis_b[i4];
279
+ turn[1] = -basis_b[i4 + 1];
280
+ turn[2] = -basis_b[i4 + 2];
281
+ turn[3] = basis_b[i4 + 3];
282
+
283
+ quat3_multiply_array(relative, 0, relative, 0, turn, 0);
284
+
285
+ quat3_multiply_array(rotation, i4, above, 0, relative, 0);
286
+ }
287
+
288
+ /**
289
+ * The rotation joint `i` carries from its parent's current frame: the parent's rotation composed with
290
+ * the joint's own as animated. A joint whose parent is not posed carries its animated rotation.
291
+ *
292
+ * @param {FrameFabrikProblem} problem
293
+ * @param {number} i
294
+ */
295
+ function load_carried(problem, i) {
296
+ const parent = problem.joint_parent[i];
297
+
298
+ if (parent < 0) {
299
+ const r = problem.base_rotation;
300
+ const i4 = i * 4;
301
+
302
+ carried[0] = r[i4];
303
+ carried[1] = r[i4 + 1];
304
+ carried[2] = r[i4 + 2];
305
+ carried[3] = r[i4 + 3];
306
+ } else {
307
+ quat3_multiply_array(carried, 0, problem.solved_rotation, parent * 4, problem.base_local_rotation, i * 4);
308
+ }
309
+ }
310
+
311
+ /**
312
+ * What a solve derives once from the tree, the goals and the animated pose: offsets, children, link
313
+ * weights, which joints only turn, each goal's anchor.
314
+ *
315
+ * @param {FrameFabrikProblem} problem
316
+ */
317
+ function prepare(problem) {
318
+ const n = problem.joint_count;
319
+ const goal_count = problem.goal_count;
320
+
321
+ const parent = problem.joint_parent;
322
+ const base_position = problem.base_position;
323
+ const base_rotation = problem.base_rotation;
324
+
325
+ const offset = problem._offset;
326
+ const child_start = problem._child_start;
327
+ const child = problem._child;
328
+ const link_weight = problem._link_weight;
329
+ const path_count = problem._path_count;
330
+ const interior = problem._interior;
331
+ const root_only = problem._root_only;
332
+ const effector_goal = problem._effector_goal;
333
+
334
+ // offsets, and the solve starts from the animated pose
335
+ for (let i = 0; i < n; i++) {
336
+ const i3 = i * 3;
337
+ const i4 = i * 4;
338
+
339
+ problem.solved_position[i3] = base_position[i3];
340
+ problem.solved_position[i3 + 1] = base_position[i3 + 1];
341
+ problem.solved_position[i3 + 2] = base_position[i3 + 2];
342
+
343
+ problem.solved_rotation[i4] = base_rotation[i4];
344
+ problem.solved_rotation[i4 + 1] = base_rotation[i4 + 1];
345
+ problem.solved_rotation[i4 + 2] = base_rotation[i4 + 2];
346
+ problem.solved_rotation[i4 + 3] = base_rotation[i4 + 3];
347
+
348
+ child_start[i] = 0;
349
+ link_weight[i] = 0;
350
+ path_count[i] = 0;
351
+ interior[i] = 0;
352
+ root_only[i] = 0;
353
+ effector_goal[i] = -1;
354
+
355
+ const p = parent[i];
356
+
357
+ if (p < 0) {
358
+ continue;
359
+ }
360
+
361
+ const p3 = p * 3;
362
+ const p4 = p * 4;
363
+
364
+ // conj(parent) applied to the world offset
365
+ v3[0] = base_position[i3] - base_position[p3];
366
+ v3[1] = base_position[i3 + 1] - base_position[p3 + 1];
367
+ v3[2] = base_position[i3 + 2] - base_position[p3 + 2];
368
+
369
+ turn[0] = -base_rotation[p4];
370
+ turn[1] = -base_rotation[p4 + 1];
371
+ turn[2] = -base_rotation[p4 + 2];
372
+ turn[3] = base_rotation[p4 + 3];
373
+
374
+ v3_quaternion_apply_array(offset, i3, v3, 0, turn, 0);
375
+ }
376
+
377
+ // children, compressed: counted one slot up, summed into starts, filled with the start as a
378
+ // cursor (which leaves each start where the next one began), then shifted back down
379
+ child_start[n] = 0;
380
+
381
+ for (let i = 0; i < n; i++) {
382
+ const p = parent[i];
383
+
384
+ if (p >= 0) {
385
+ child_start[p + 1]++;
386
+ }
387
+ }
388
+
389
+ for (let i = 1; i <= n; i++) {
390
+ child_start[i] += child_start[i - 1];
391
+ }
392
+
393
+ for (let i = 0; i < n; i++) {
394
+ const p = parent[i];
395
+
396
+ if (p >= 0) {
397
+ child[child_start[p]++] = i;
398
+ }
399
+ }
400
+
401
+ for (let i = n; i > 0; i--) {
402
+ child_start[i] = child_start[i - 1];
403
+ }
404
+
405
+ child_start[0] = 0;
406
+
407
+ // paths: link weights, how many pulling goals hold each joint, which joints are interior
408
+ for (let g = 0; g < goal_count; g++) {
409
+ const effector = problem.goal_effector[g];
410
+ const root = problem.goal_root[g];
411
+ const weight = problem.goal_weight[g];
412
+
413
+ effector_goal[effector] = g;
414
+
415
+ for (let j = effector; ; j = parent[j]) {
416
+ if (weight > 0) {
417
+ path_count[j]++;
418
+ }
419
+
420
+ if (j === root) {
421
+ break;
422
+ }
423
+
424
+ interior[j] = 1;
425
+
426
+ if (weight > link_weight[j]) {
427
+ link_weight[j] = weight;
428
+ }
429
+ }
430
+ }
431
+
432
+ for (let g = 0; g < goal_count; g++) {
433
+ const root = problem.goal_root[g];
434
+
435
+ if (interior[root] === 0) {
436
+ root_only[root] = 1;
437
+ }
438
+ }
439
+
440
+ for (let i = 0; i < n; i++) {
441
+ if (parent[i] < 0) {
442
+ // its parent's frame is fixed, so nothing the solve does moves it
443
+ root_only[i] = 1;
444
+ }
445
+ }
446
+
447
+ // anchors, and each target in its joint's frame
448
+ for (let g = 0; g < goal_count; g++) {
449
+ const effector = problem.goal_effector[g];
450
+ const root = problem.goal_root[g];
451
+ const own = problem.goal_weight[g] > 0 ? 1 : 0;
452
+
453
+ let anchor = parent[root];
454
+
455
+ for (let j = effector; ; j = parent[j]) {
456
+ if (path_count[j] - own > 0) {
457
+ anchor = j;
458
+ break;
459
+ }
460
+
461
+ if (j === root) {
462
+ break;
463
+ }
464
+ }
465
+
466
+ problem._goal_anchor[g] = anchor;
467
+
468
+ if (anchor >= 0) {
469
+ to_frame(problem._goal_anchor_offset, g * 3, base_position, effector * 3, base_position, base_rotation, anchor);
470
+ }
471
+
472
+ const target_joint = problem.goal_target_joint[g];
473
+
474
+ if (target_joint >= 0) {
475
+ to_frame(problem._goal_target_local, g * 3, problem.goal_target, g * 3, base_position, base_rotation, target_joint);
476
+ }
477
+ }
478
+ }
479
+
480
+ /**
481
+ * `out = conj(R_j) (point - P_j)` with the frame of joint `j` as animated.
482
+ *
483
+ * @param {Float64Array} out
484
+ * @param {number} out_offset
485
+ * @param {Float64Array} point
486
+ * @param {number} point_offset
487
+ * @param {Float64Array} base_position
488
+ * @param {Float64Array} base_rotation
489
+ * @param {number} j
490
+ */
491
+ function to_frame(out, out_offset, point, point_offset, base_position, base_rotation, j) {
492
+ const j3 = j * 3;
493
+ const j4 = j * 4;
494
+
495
+ v3[0] = point[point_offset] - base_position[j3];
496
+ v3[1] = point[point_offset + 1] - base_position[j3 + 1];
497
+ v3[2] = point[point_offset + 2] - base_position[j3 + 2];
498
+
499
+ turn[0] = -base_rotation[j4];
500
+ turn[1] = -base_rotation[j4 + 1];
501
+ turn[2] = -base_rotation[j4 + 2];
502
+ turn[3] = base_rotation[j4 + 3];
503
+
504
+ v3_quaternion_apply_array(out, out_offset, v3, 0, turn, 0);
505
+ }
506
+
507
+ /**
508
+ * `out = P_j + R_j local` with the frame of joint `j` as solved so far.
509
+ *
510
+ * @param {Float64Array} out
511
+ * @param {number} out_offset
512
+ * @param {FrameFabrikProblem} problem
513
+ * @param {Float64Array} local
514
+ * @param {number} local_offset
515
+ * @param {number} j
516
+ */
517
+ function from_frame(out, out_offset, problem, local, local_offset, j) {
518
+ const j3 = j * 3;
519
+
520
+ v3_quaternion_apply_array(v3, 0, local, local_offset, problem.solved_rotation, j * 4);
521
+
522
+ out[out_offset] = problem.solved_position[j3] + v3[0];
523
+ out[out_offset + 1] = problem.solved_position[j3 + 1] + v3[1];
524
+ out[out_offset + 2] = problem.solved_position[j3 + 2] + v3[2];
525
+ }
526
+
527
+ /**
528
+ * This iteration's target for every effector: `lerp(carried, goal, w)`.
529
+ *
530
+ * Carried is where the effector would be if the goal did not pull, in the frame of its anchor (see
531
+ * {@link FrameFabrikProblem#_goal_anchor}), so a goal of weight 0 asks for exactly where the other
532
+ * goals take it, and raising its weight moves it continuously from there.
533
+ *
534
+ * @param {FrameFabrikProblem} problem
535
+ */
536
+ function load_targets(problem) {
537
+ const goal_count = problem.goal_count;
538
+ const target = problem._target;
539
+
540
+ for (let g = 0; g < goal_count; g++) {
541
+ const g3 = g * 3;
542
+
543
+ const anchor = problem._goal_anchor[g];
544
+
545
+ let cx, cy, cz;
546
+
547
+ if (anchor < 0) {
548
+ const e3 = problem.goal_effector[g] * 3;
549
+
550
+ cx = problem.base_position[e3];
551
+ cy = problem.base_position[e3 + 1];
552
+ cz = problem.base_position[e3 + 2];
553
+ } else {
554
+ from_frame(target, g3, problem, problem._goal_anchor_offset, g3, anchor);
555
+
556
+ cx = target[g3];
557
+ cy = target[g3 + 1];
558
+ cz = target[g3 + 2];
559
+ }
560
+
561
+ const target_joint = problem.goal_target_joint[g];
562
+
563
+ let gx, gy, gz;
564
+
565
+ if (target_joint < 0) {
566
+ gx = problem.goal_target[g3];
567
+ gy = problem.goal_target[g3 + 1];
568
+ gz = problem.goal_target[g3 + 2];
569
+ } else {
570
+ from_frame(target, g3, problem, problem._goal_target_local, g3, target_joint);
571
+
572
+ gx = target[g3];
573
+ gy = target[g3 + 1];
574
+ gz = target[g3 + 2];
575
+ }
576
+
577
+ const w = problem.goal_weight[g];
578
+
579
+ target[g3] = cx + (gx - cx) * w;
580
+ target[g3 + 1] = cy + (gy - cy) * w;
581
+ target[g3 + 2] = cz + (gz - cz) * w;
582
+ }
583
+ }
584
+
585
+ /**
586
+ * Inward: from the leaves to the roots, each effector onto its target, and each joint with pulling
587
+ * children turned toward them from where it was, then moved to the weighted mean of where each child
588
+ * puts it rigidly. An effector with pulling children of its own, inside another goal's path, takes
589
+ * its target into that mean as one more proposal, weighted by its goal's weight. A joint that only
590
+ * turns is left for the outward pass.
591
+ *
592
+ * @param {FrameFabrikProblem} problem
593
+ */
594
+ function pass_inward(problem) {
595
+ const n = problem.joint_count;
596
+
597
+ const position = problem.solved_position;
598
+ const rotation = problem.solved_rotation;
599
+ const offset = problem._offset;
600
+ const link_weight = problem._link_weight;
601
+ const child = problem._child;
602
+ const child_start = problem._child_start;
603
+ const root_only = problem._root_only;
604
+ const effector_goal = problem._effector_goal;
605
+ const target = problem._target;
606
+
607
+ for (let i = n - 1; i >= 0; i--) {
608
+ if (root_only[i] !== 0) {
609
+ continue;
610
+ }
611
+
612
+ const i3 = i * 3;
613
+
614
+ const goal = effector_goal[i];
615
+
616
+ const child_end = child_start[i + 1];
617
+
618
+ let pulled = false;
619
+
620
+ for (let c = child_start[i]; c < child_end; c++) {
621
+ if (link_weight[child[c]] > 0) {
622
+ pulled = true;
623
+ break;
624
+ }
625
+ }
626
+
627
+ if (!pulled) {
628
+ if (goal >= 0) {
629
+ // an effector at the end of its paths goes onto its target
630
+ const g3 = goal * 3;
631
+
632
+ position[i3] = target[g3];
633
+ position[i3 + 1] = target[g3 + 1];
634
+ position[i3 + 2] = target[g3 + 2];
635
+ }
636
+
637
+ continue;
638
+ }
639
+
640
+ load_carried(problem, i);
641
+
642
+ if (fit_rotation(problem, i) && problem.joint_limited[i] !== 0) {
643
+ limit_rotation(problem, i);
644
+ }
645
+
646
+ let x = 0, y = 0, z = 0, total = 0;
647
+
648
+ for (let c = child_start[i]; c < child_end; c++) {
649
+ const k = child[c];
650
+
651
+ const w = link_weight[k];
652
+
653
+ if (w <= 0) {
654
+ continue;
655
+ }
656
+
657
+ const k3 = k * 3;
658
+
659
+ v3_quaternion_apply_array(v3, 0, offset, k3, rotation, i * 4);
660
+
661
+ x += w * (position[k3] - v3[0]);
662
+ y += w * (position[k3 + 1] - v3[1]);
663
+ z += w * (position[k3 + 2] - v3[2]);
664
+
665
+ total += w;
666
+ }
667
+
668
+ if (goal >= 0) {
669
+ // an effector inside another goal's path: its target is one more proposal, as strong as
670
+ // its goal, so weight 0 leaves the joint to its children and the pose is continuous
671
+ const w = problem.goal_weight[goal];
672
+ const g3 = goal * 3;
673
+
674
+ x += w * target[g3];
675
+ y += w * target[g3 + 1];
676
+ z += w * target[g3 + 2];
677
+
678
+ total += w;
679
+ }
680
+
681
+ const inverse = 1 / total;
682
+
683
+ position[i3] = x * inverse;
684
+ position[i3 + 1] = y * inverse;
685
+ position[i3 + 2] = z * inverse;
686
+ }
687
+ }
688
+
689
+ /**
690
+ * Outward: from the roots to the leaves, each joint turned toward where its pulling children went on
691
+ * the way in, from where its parent has now put it, and every child placed rigidly.
692
+ *
693
+ * @param {FrameFabrikProblem} problem
694
+ */
695
+ function pass_outward(problem) {
696
+ const n = problem.joint_count;
697
+
698
+ const position = problem.solved_position;
699
+ const rotation = problem.solved_rotation;
700
+ const offset = problem._offset;
701
+ const child = problem._child;
702
+ const child_start = problem._child_start;
703
+
704
+ for (let i = 0; i < n; i++) {
705
+ load_carried(problem, i);
706
+
707
+ if (fit_rotation(problem, i) && problem.joint_limited[i] !== 0) {
708
+ limit_rotation(problem, i);
709
+ }
710
+
711
+ const i3 = i * 3;
712
+ const child_end = child_start[i + 1];
713
+
714
+ for (let c = child_start[i]; c < child_end; c++) {
715
+ const k3 = child[c] * 3;
716
+
717
+ v3_quaternion_apply_array(v3, 0, offset, k3, rotation, i * 4);
718
+
719
+ position[k3] = position[i3] + v3[0];
720
+ position[k3 + 1] = position[i3 + 1] + v3[1];
721
+ position[k3 + 2] = position[i3 + 2] + v3[2];
722
+ }
723
+ }
724
+ }
725
+
726
+ /**
727
+ * Pose a tree of joints so its effectors reach their goals: frame FABRIK.
728
+ *
729
+ * Every posed joint is a frame, a position and a rotation, with its children at rigid offsets in it,
730
+ * so a branch keeps every child's offset and the solved pose is one the hierarchy composes exactly.
731
+ * Each iteration runs:
732
+ * - **inward**, leaves to roots: each effector onto its target; every other joint with a pulling child
733
+ * fitted toward its pulling children from its old position, then moved to the weighted mean of
734
+ * the positions its children put it at rigidly;
735
+ * - **outward**, roots to leaves: each joint refitted from where its parent now puts it, and its
736
+ * children placed rigidly.
737
+ *
738
+ * A limited joint's fitted turn is projected into its limits in both passes, so the solved pose keeps
739
+ * them (Aristidou, Chrysanthou and Lasenby 2016); a joint the solve only carries keeps its animated
740
+ * turn, inside its limits or not, as it would with no goal.
741
+ *
742
+ * With one pulling child that is the paper's pull (Aristidou and Lasenby 2011). The fits start from
743
+ * the rotation the joint carries from its parent, never from its own last answer, so nothing drifts
744
+ * with the iteration count. A goal's weight scales the links of its path, and its effector's target
745
+ * is `lerp(carried, goal, weight)`: weight 0 gives the pose the solve gives without the goal, and the
746
+ * pose is continuous in the weight. A goal's root turns and never moves.
747
+ *
748
+ * The solve stops when no joint moved by `tolerance` in an iteration, or after `max_iterations`. Not
749
+ * on the distance to the targets: an unreachable target never comes within any distance, and a
750
+ * reachable one comes close while the joints above it are still settling.
751
+ *
752
+ * @param {FrameFabrikProblem} problem filled; the solved pose lands in its `solved_position` and `solved_rotation`
753
+ * @param {number} max_iterations
754
+ * @param {number} tolerance in world units; the solve stops once an iteration moves no joint this far
755
+ * @returns {number} how many iterations ran
756
+ */
757
+ export function frame_fabrik_solve(problem, max_iterations, tolerance) {
758
+ assert.defined(problem, 'problem');
759
+ assert.isNonNegativeInteger(max_iterations, 'max_iterations');
760
+ assert.isNumber(tolerance, 'tolerance');
761
+ assert.greaterThanOrEqual(tolerance, 0, 'tolerance');
762
+
763
+ const n = problem.joint_count;
764
+ const goal_count = problem.goal_count;
765
+
766
+ assert.isNonNegativeInteger(n, 'joint_count');
767
+ assert.lessThanOrEqual(n, problem.joint_capacity, 'joint_count');
768
+ assert.isNonNegativeInteger(goal_count, 'goal_count');
769
+ assert.lessThanOrEqual(goal_count, problem.goal_capacity, 'goal_count');
770
+
771
+ validate(problem);
772
+
773
+ prepare(problem);
774
+
775
+ const position = problem.solved_position;
776
+ const previous = problem._previous;
777
+
778
+ let iteration = 0;
779
+
780
+ while (iteration < max_iterations) {
781
+ for (let i = 0; i < n * 3; i++) {
782
+ previous[i] = position[i];
783
+ }
784
+
785
+ load_targets(problem);
786
+
787
+ pass_inward(problem);
788
+
789
+ pass_outward(problem);
790
+
791
+ iteration++;
792
+
793
+ let moved = 0;
794
+
795
+ for (let i = 0; i < n; i++) {
796
+ const i3 = i * 3;
797
+
798
+ const dx = position[i3] - previous[i3];
799
+ const dy = position[i3 + 1] - previous[i3 + 1];
800
+ const dz = position[i3 + 2] - previous[i3 + 2];
801
+
802
+ const d = dx * dx + dy * dy + dz * dz;
803
+
804
+ if (d > moved) {
805
+ moved = d;
806
+ }
807
+ }
808
+
809
+ if (moved < tolerance * tolerance) {
810
+ break;
811
+ }
812
+ }
813
+
814
+ return iteration;
815
+ }
816
+
817
+ /**
818
+ * The problem's shape, asserted: parents first, one goal per effector, every root on its effector's
819
+ * way up, weights in 0..1.
820
+ *
821
+ * @param {FrameFabrikProblem} problem
822
+ */
823
+ function validate(problem) {
824
+ const n = problem.joint_count;
825
+ const parent = problem.joint_parent;
826
+
827
+ for (let i = 0; i < n; i++) {
828
+ assert.ok(parent[i] >= -1 && parent[i] < i, 'every parent comes before its children');
829
+
830
+ if (problem.joint_limited[i] !== 0) {
831
+ for (let k = 0; k < 3; k++) {
832
+ assert.ok(problem.limit_lower[i * 3 + k] <= problem.limit_upper[i * 3 + k], 'limit_lower is no greater than limit_upper');
833
+ }
834
+ }
835
+
836
+ const p = parent[i];
837
+
838
+ if (p < 0) {
839
+ continue;
840
+ }
841
+
842
+ // base_rotation[i] = base_rotation[p] * base_local_rotation[i]
843
+ quat3_multiply_array(above, 0, problem.base_rotation, p * 4, problem.base_local_rotation, i * 4);
844
+
845
+ const i4 = i * 4;
846
+ const dot = above[0] * problem.base_rotation[i4] + above[1] * problem.base_rotation[i4 + 1]
847
+ + above[2] * problem.base_rotation[i4 + 2] + above[3] * problem.base_rotation[i4 + 3];
848
+
849
+ assert.ok(Math.abs(dot) > 1 - 1e-9, 'base_local_rotation composes with the parent\'s base_rotation to the joint\'s');
850
+ }
851
+
852
+ for (let g = 0; g < problem.goal_count; g++) {
853
+ const effector = problem.goal_effector[g];
854
+ const root = problem.goal_root[g];
855
+ const weight = problem.goal_weight[g];
856
+ const target_joint = problem.goal_target_joint[g];
857
+
858
+ assert.ok(effector >= 0 && effector < n, 'goal_effector is a joint of the problem');
859
+ assert.ok(weight >= 0 && weight <= 1, 'goal_weight is within 0..1');
860
+ assert.ok(target_joint >= -1 && target_joint < n, 'goal_target_joint is a joint of the problem, or -1');
861
+
862
+ let j = effector;
863
+
864
+ while (j !== root && j >= 0) {
865
+ j = parent[j];
866
+ }
867
+
868
+ assert.equal(j, root, 'goal_root is the effector or one of its ancestors');
869
+
870
+ for (let h = 0; h < g; h++) {
871
+ assert.notEqual(problem.goal_effector[h], effector, 'one goal per effector');
872
+ }
873
+ }
874
+ }