@woosh/meep-engine 2.131.28 → 2.131.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "description": "Pure JavaScript game engine. Fully featured and production ready.",
6
6
  "type": "module",
7
7
  "author": "Alexander Goldring",
8
- "version": "2.131.28",
8
+ "version": "2.131.29",
9
9
  "main": "build/meep.module.js",
10
10
  "module": "build/meep.module.js",
11
11
  "exports": {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Implementation of FABRIK algorithm, "Forward And Backward Reaching Inverse Kinematics"
3
- * This implementation deals with single chain at a time, without support for multiple effectors
3
+ * This implementation deals with a single chain at a time, without support for multiple effectors
4
4
  * see "FABRIK: a fast, iterative solver for inverse kinematics"
5
5
  * @param {number} size number of points in the chain
6
6
  * @param {Float32Array|number[]} positions
@@ -1 +1 @@
1
- {"version":3,"file":"fabrik3d_solve_primitive.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/inverse_kinematics/fabrik/fabrik3d_solve_primitive.js"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;GAeG;AACH,+CAZW,MAAM,aACN,YAAY,GAAC,MAAM,EAAE,WACrB,YAAY,GAAC,MAAM,EAAE,YACrB,MAAM,YACN,MAAM,YACN,MAAM,YACN,MAAM,YACN,MAAM,YACN,MAAM,mBACN,MAAM,uBACN,MAAM,QAsDhB"}
1
+ {"version":3,"file":"fabrik3d_solve_primitive.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/inverse_kinematics/fabrik/fabrik3d_solve_primitive.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,+CAZW,MAAM,aACN,YAAY,GAAC,MAAM,EAAE,WACrB,YAAY,GAAC,MAAM,EAAE,YACrB,MAAM,YACN,MAAM,YACN,MAAM,YACN,MAAM,YACN,MAAM,YACN,MAAM,mBACN,MAAM,uBACN,MAAM,QA8FhB"}
@@ -1,14 +1,6 @@
1
- import { v3_array_normalize } from "../../../../core/geom/vec3/v3_array_normalize.js";
2
- import { v3_displace_in_direction_array } from "../../../../core/geom/vec3/v3_displace_in_direction_array.js";
3
- import { v3_distance } from "../../../../core/geom/vec3/v3_distance.js";
4
- import { v3_distance_sqr } from "../../../../core/geom/vec3/v3_distance_sqr.js";
5
- import { v3_length_sqr } from "../../../../core/geom/vec3/v3_length_sqr.js";
6
-
7
- const scratch_direction = new Float32Array(3);
8
-
9
1
  /**
10
2
  * Implementation of FABRIK algorithm, "Forward And Backward Reaching Inverse Kinematics"
11
- * This implementation deals with single chain at a time, without support for multiple effectors
3
+ * This implementation deals with a single chain at a time, without support for multiple effectors
12
4
  * see "FABRIK: a fast, iterative solver for inverse kinematics"
13
5
  * @param {number} size number of points in the chain
14
6
  * @param {Float32Array|number[]} positions
@@ -37,241 +29,183 @@ export function fabrik3d_solve_primitive(
37
29
  return;
38
30
  }
39
31
 
40
- if (
41
- !is_reachable(
42
- size, lengths,
43
- origin_x, origin_y, origin_z,
44
- target_x, target_y, target_z)
45
- ) {
46
- // target is not reachable, so stretch out the chain towards it instead
47
- reach_out(
48
- size,
49
- positions,
50
- lengths,
51
- origin_x, origin_y, origin_z,
52
- target_x, target_y, target_z
53
- );
32
+ // 1. Check Reachability
33
+ // Calculate distance to target squared first to avoid sqrt if not needed
34
+ const dx = target_x - origin_x;
35
+ const dy = target_y - origin_y;
36
+ const dz = target_z - origin_z;
37
+ const dist_sq = dx * dx + dy * dy + dz * dz;
38
+
39
+ // Calculate total length if not provided (fallback)
40
+ let total_chain_length = 0;
41
+ for (let i = 0; i < size - 1; i++) {
42
+ total_chain_length += lengths[i];
43
+ }
54
44
 
45
+ // Unreachable: The target is further than the total length
46
+ // (Compare squares to save a SQRT on the distance check)
47
+ if (dist_sq > total_chain_length * total_chain_length) {
48
+ stretch_chain(size, positions, lengths, origin_x, origin_y, origin_z, dx, dy, dz, Math.sqrt(dist_sq));
55
49
  return;
56
50
  }
57
51
 
52
+ // 2. Iteration Loop
53
+ const last_index = size - 1;
54
+ const last_offset = last_index * 3;
58
55
 
59
56
  for (let i = 0; i < max_iterations; i++) {
60
57
 
61
- solve_backward(size, positions, lengths, target_x, target_y, target_z);
62
- solve_forward(size, positions, lengths, origin_x, origin_y, origin_z);
58
+ // --- Backward Pass (Target -> Origin) ---
59
+ // Snap the last point to target
60
+ positions[last_offset] = target_x;
61
+ positions[last_offset + 1] = target_y;
62
+ positions[last_offset + 2] = target_z;
63
63
 
64
- const last_join_address = (size - 1) * 3;
64
+ for (let j = last_index - 1; j >= 0; j--) {
65
+ const curr = j * 3;
66
+ const next = (j + 1) * 3;
65
67
 
66
- const last_joint_x = positions[last_join_address];
67
- const last_joint_y = positions[last_join_address + 1];
68
- const last_joint_z = positions[last_join_address + 2];
68
+ // Vector from Next (fixed) to Current (to be moved)
69
+ // We want to move Current towards Next until distance is lengths[j]
70
+ const bone_len = lengths[j];
69
71
 
70
- if (v3_distance_sqr(last_joint_x, last_joint_y, last_joint_z, target_x, target_y, target_z) <= distance_tolerance) {
71
- // close enough
72
- break;
72
+ solve_joint(positions, curr, next, bone_len);
73
73
  }
74
74
 
75
- }
75
+ // --- Forward Pass (Origin -> Target) ---
76
+ // Snap first point to origin
77
+ positions[0] = origin_x;
78
+ positions[1] = origin_y;
79
+ positions[2] = origin_z;
76
80
 
77
- }
81
+ for (let j = 0; j < last_index; j++) {
82
+ const curr = (j + 1) * 3; // The point we are moving
83
+ const prev = j * 3; // The fixed point
78
84
 
79
- /**
80
- * @param {number} size number of points in the chain
81
- * @param {Float32Array|number[]} positions
82
- * @param {Float32Array|number[]} lengths
83
- * @param {number} origin_x
84
- * @param {number} origin_y
85
- * @param {number} origin_z
86
- * @param {number} target_x
87
- * @param {number} target_y
88
- * @param {number} target_z
89
- */
90
- function reach_out(
91
- size,
92
- positions,
93
- lengths,
94
- origin_x, origin_y, origin_z,
95
- target_x, target_y, target_z,
96
- ) {
97
-
98
- positions[0] = origin_x;
99
- positions[1] = origin_y;
100
- positions[2] = origin_z;
101
-
102
- scratch_direction[0] = target_x - origin_x;
103
- scratch_direction[1] = target_y - origin_y;
104
- scratch_direction[2] = target_z - origin_z;
105
-
106
- v3_array_normalize(scratch_direction, 0, scratch_direction, 0);
107
-
108
- for (let i = 1; i < size; i++) {
109
- const current_offset = i * 3;
110
-
111
- const previous_offset = current_offset - 3;
112
-
113
- const previous_x = positions[previous_offset];
114
- const previous_y = positions[previous_offset + 1];
115
- const previous_z = positions[previous_offset + 2];
116
-
117
- const length = lengths[i - 1];
118
-
119
- positions[current_offset] = previous_x + scratch_direction[0] * length;
120
- positions[current_offset + 1] = previous_y + scratch_direction[1] * length;
121
- positions[current_offset + 2] = previous_z + scratch_direction[2] * length;
122
- }
123
-
124
- }
85
+ const bone_len = lengths[j];
125
86
 
126
- /**
127
- *
128
- * @param {number} size
129
- * @param {number[]} lengths
130
- * @param {number} origin_x
131
- * @param {number} origin_y
132
- * @param {number} origin_z
133
- * @param {number} target_x
134
- * @param {number} target_y
135
- * @param {number} target_z
136
- */
137
- function is_reachable(
138
- size, lengths,
139
- origin_x, origin_y, origin_z,
140
- target_x, target_y, target_z
141
- ) {
142
-
143
- let total_length = 0;
144
-
145
- const limit = size - 1;
146
-
147
- for (let i = 0; i < limit; i++) {
148
- const x = lengths[i];
149
-
150
- total_length += x;
151
- }
152
-
153
- const distance_to_target = v3_distance(origin_x, origin_y, origin_z, target_x, target_y, target_z);
154
-
155
- return distance_to_target <= total_length;
156
- }
157
-
158
- /**
159
- * Produced vector normalize(B-A)
160
- * @param result
161
- * @param result_offset
162
- * @param positions
163
- * @param offsetA
164
- * @param offsetB
165
- */
166
- function compute_direction(
167
- result, result_offset,
168
- positions, offsetA, offsetB
169
- ) {
170
-
171
- const ax = positions[offsetA];
172
- const ay = positions[offsetA + 1];
173
- const az = positions[offsetA + 2];
174
-
175
- const bx = positions[offsetB];
176
- const by = positions[offsetB + 1];
177
- const bz = positions[offsetB + 2];
178
-
179
- const dx = bx - ax;
180
- const dy = by - ay;
181
- const dz = bz - az;
87
+ solve_joint(positions, curr, prev, bone_len);
88
+ }
182
89
 
183
- const length_sqr = v3_length_sqr(dx, dy, dz);
90
+ // --- Check Tolerance ---
91
+ const tip_x = positions[last_offset];
92
+ const tip_y = positions[last_offset + 1];
93
+ const tip_z = positions[last_offset + 2];
184
94
 
185
- if (length_sqr === 0) {
186
- // TODO use random point on a sphere to prevent pathology
95
+ const delta_x = tip_x - target_x;
96
+ const delta_y = tip_y - target_y;
97
+ const delta_z = tip_z - target_z;
187
98
 
188
- // points are on top of each other, set arbitrary direction
189
- result[result_offset + 0] = 0;
190
- result[result_offset + 1] = 0;
191
- result[result_offset + 2] = 1;
99
+ const dist_to_target_sq =
100
+ delta_x * delta_x
101
+ + delta_y * delta_y
102
+ + delta_z * delta_z;
192
103
 
193
- return;
104
+ if (dist_to_target_sq <= distance_tolerance) {
105
+ break;
106
+ }
194
107
  }
195
108
 
196
- const m = 1 / Math.sqrt(length_sqr);
197
-
198
- result[result_offset + 0] = dx * m;
199
- result[result_offset + 1] = dy * m;
200
- result[result_offset + 2] = dz * m;
201
109
  }
202
110
 
203
-
204
111
  /**
205
- *
206
- * @param {number} size
112
+ * Inline-friendly helper to project point 'curr' onto the line starting at 'anchor'
113
+ * such that the distance between them becomes 'length'.
207
114
  * @param {Float32Array} positions
208
- * @param {Float32Array|number[]} lengths
209
- * @param {number} origin_x
210
- * @param {number} origin_y
211
- * @param {number} origin_z
115
+ * @param {number} curr_ptr
116
+ * @param {number} anchor_ptr
117
+ * @param {number} length
212
118
  */
213
- function solve_forward(size, positions, lengths, origin_x, origin_y, origin_z) {
214
-
215
- // move first point to origin
216
- positions[0] = origin_x;
217
- positions[1] = origin_y;
218
- positions[2] = origin_z;
219
-
220
- for (let i = 1; i < size; i++) {
221
- const current_address = i * 3;
222
- const previous_address = current_address - 3;
223
-
224
- compute_direction(
225
- scratch_direction, 0,
226
- positions, previous_address, current_address
227
- );
228
-
229
- const length = lengths[i - 1];
230
-
231
- v3_displace_in_direction_array(
232
- positions, current_address,
233
- positions, previous_address,
234
- scratch_direction, 0,
235
- length
236
- );
119
+ function solve_joint(
120
+ positions,
121
+ curr_ptr,
122
+ anchor_ptr,
123
+ length
124
+ ) {
125
+ const ax = positions[anchor_ptr];
126
+ const ay = positions[anchor_ptr + 1];
127
+ const az = positions[anchor_ptr + 2];
128
+
129
+ const cx = positions[curr_ptr];
130
+ const cy = positions[curr_ptr + 1];
131
+ const cz = positions[curr_ptr + 2];
132
+
133
+ // Vector from Anchor -> Current
134
+ const dx = cx - ax;
135
+ const dy = cy - ay;
136
+ const dz = cz - az;
137
+
138
+ const len_sq = dx * dx + dy * dy + dz * dz;
139
+
140
+ if (len_sq < 1e-15) {
141
+ // Prevent division by zero / singularity
142
+ // If points overlap perfectly, pick arbitrary direction (Z-axis)
143
+ positions[curr_ptr] = ax;
144
+ positions[curr_ptr + 1] = ay;
145
+ positions[curr_ptr + 2] = az + length;
146
+ }else {
147
+
148
+ // Math: NewPos = Anchor + (Direction * Length)
149
+ // Direction = Vector / CurrentLength
150
+ // NewPos = Anchor + Vector * (Length / CurrentLength)
151
+ // We use inverse sqrt for speed if available, or just standard math
152
+
153
+ const scale = length / Math.sqrt(len_sq);
154
+
155
+ positions[curr_ptr] = ax + dx * scale;
156
+ positions[curr_ptr + 1] = ay + dy * scale;
157
+ positions[curr_ptr + 2] = az + dz * scale;
237
158
  }
238
-
239
159
  }
240
160
 
241
161
  /**
242
- *
162
+ * Handle unreachable target by stretching chain in a straight line
243
163
  * @param {number} size
244
164
  * @param {Float32Array} positions
245
- * @param {Float32Array|number[]} lengths
246
- * @param {number} target_x
247
- * @param {number} target_y
248
- * @param {number} target_z
165
+ * @param {Float32Array} lengths
166
+ * @param {number} ox
167
+ * @param {number} oy
168
+ * @param {number} oz
169
+ * @param {number} dx
170
+ * @param {number} dy
171
+ * @param {number} dz
172
+ * @param {number} current_dist
249
173
  */
250
- function solve_backward(size, positions, lengths, target_x, target_y, target_z) {
251
-
252
- const last_index = size - 1;
253
-
254
- // move last point to target
255
- const last_address = last_index * 3;
256
-
257
- positions[last_address] = target_x;
258
- positions[last_address + 1] = target_y;
259
- positions[last_address + 2] = target_z;
260
-
261
- for (let i = last_index - 1; i >= 0; i--) {
262
- const current_address = i * 3;
263
- const next_address = current_address + 3;
264
-
265
- compute_direction(
266
- scratch_direction, 0,
267
- positions, next_address, current_address
268
- );
174
+ function stretch_chain(
175
+ size,
176
+ positions,
177
+ lengths,
178
+ ox, oy, oz,
179
+ dx, dy, dz,
180
+ current_dist
181
+ ) {
269
182
 
270
- v3_displace_in_direction_array(
271
- positions, current_address,
272
- positions, next_address,
273
- scratch_direction, 0,
274
- lengths[i]
275
- )
183
+ // Normalize direction
184
+ const inv_dist = 1.0 / current_dist;
185
+ const nx = dx * inv_dist;
186
+ const ny = dy * inv_dist;
187
+ const nz = dz * inv_dist;
188
+
189
+ // Reset origin
190
+ positions[0] = ox;
191
+ positions[1] = oy;
192
+ positions[2] = oz;
193
+
194
+ let prev_x = ox;
195
+ let prev_y = oy;
196
+ let prev_z = oz;
197
+
198
+ for (let i = 0; i < size - 1; i++) {
199
+ const len = lengths[i];
200
+ const next_offset = (i + 1) * 3;
201
+
202
+ // Move point along the line
203
+ prev_x += nx * len;
204
+ prev_y += ny * len;
205
+ prev_z += nz * len;
206
+
207
+ positions[next_offset] = prev_x;
208
+ positions[next_offset + 1] = prev_y;
209
+ positions[next_offset + 2] = prev_z;
276
210
  }
277
211
  }
@@ -2,7 +2,7 @@
2
2
  * Note that bones are defined as a combination of Joint ({@link Transform}) and bone length. Joint defines the origin of the bone.
3
3
  * Last joint's position is expected to be bone_length distance from the target if target is reachable and be oriented towards that target ({@link Transform.lookAt})
4
4
  * @param {Transform[]} joints Will be updated as a result of the solve
5
- * @param {number[]} lengths distance to next bone
5
+ * @param {number[]} lengths distance to next bone, bone lengths.
6
6
  * @param {Vector3} origin where should the first joint be placed at
7
7
  * @param {Vector3} target where should the last joint be placed at
8
8
  * @param {number} [max_iterations] More steps will lead to higher accuracy, but at the cost of computation. Generally solution will be reached in just a few iteration so this is just a ceiling
@@ -13,7 +13,7 @@ const scratch_quat = new Quaternion();
13
13
  * Note that bones are defined as a combination of Joint ({@link Transform}) and bone length. Joint defines the origin of the bone.
14
14
  * Last joint's position is expected to be bone_length distance from the target if target is reachable and be oriented towards that target ({@link Transform.lookAt})
15
15
  * @param {Transform[]} joints Will be updated as a result of the solve
16
- * @param {number[]} lengths distance to next bone
16
+ * @param {number[]} lengths distance to next bone, bone lengths.
17
17
  * @param {Vector3} origin where should the first joint be placed at
18
18
  * @param {Vector3} target where should the last joint be placed at
19
19
  * @param {number} [max_iterations] More steps will lead to higher accuracy, but at the cost of computation. Generally solution will be reached in just a few iteration so this is just a ceiling