reze-engine 0.3.5 → 0.3.7

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/src/ik-solver.ts CHANGED
@@ -117,11 +117,7 @@ export class IKSolverSystem {
117
117
  }
118
118
  }
119
119
 
120
- // Get IK bone and target positions
121
- const ikPosition = this.getWorldTranslation(ikBoneIndex, worldMatrices)
122
- const targetPosition = this.getWorldTranslation(targetBoneIndex, worldMatrices)
123
-
124
- if (ikPosition.subtract(targetPosition).length() < this.EPSILON) return
120
+ if (this.getDistance(ikBoneIndex, targetBoneIndex, worldMatrices) < this.EPSILON) return
125
121
 
126
122
  // Build IK chains
127
123
  const chains: IKChain[] = []
@@ -135,11 +131,7 @@ export class IKSolverSystem {
135
131
  }
136
132
  this.updateWorldMatrix(targetBoneIndex, bones, localRotations, localTranslations, worldMatrices, undefined)
137
133
 
138
- // Re-read positions after initial update
139
- const updatedIkPosition = this.getWorldTranslation(ikBoneIndex, worldMatrices)
140
- const updatedTargetPosition = this.getWorldTranslation(targetBoneIndex, worldMatrices)
141
-
142
- if (updatedIkPosition.subtract(updatedTargetPosition).length() < this.EPSILON) return
134
+ if (this.getDistance(ikBoneIndex, targetBoneIndex, worldMatrices) < this.EPSILON) return
143
135
 
144
136
  // Solve iteratively
145
137
  const iteration = Math.min(solver.iterationCount, 256)
@@ -165,29 +157,17 @@ export class IKSolverSystem {
165
157
  }
166
158
  }
167
159
 
168
- // Re-read positions after this iteration
169
- const currentIkPosition = this.getWorldTranslation(ikBoneIndex, worldMatrices)
170
- const currentTargetPosition = this.getWorldTranslation(targetBoneIndex, worldMatrices)
171
- const distance = currentIkPosition.subtract(currentTargetPosition).length()
172
- if (distance < this.EPSILON) break
160
+ if (this.getDistance(ikBoneIndex, targetBoneIndex, worldMatrices) < this.EPSILON) break
173
161
  }
174
162
 
175
163
  // Apply IK rotations to local rotations
176
164
  for (const link of solver.links) {
177
165
  const chainInfo = ikChainInfo[link.boneIndex]
178
- if (chainInfo && chainInfo.ikRotation) {
166
+ if (chainInfo?.ikRotation) {
179
167
  const qi = link.boneIndex * 4
180
- const localRot = new Quat(
181
- localRotations[qi],
182
- localRotations[qi + 1],
183
- localRotations[qi + 2],
184
- localRotations[qi + 3]
185
- )
168
+ const localRot = this.getQuatFromArray(localRotations, qi)
186
169
  const finalRot = chainInfo.ikRotation.multiply(localRot).normalize()
187
- localRotations[qi] = finalRot.x
188
- localRotations[qi + 1] = finalRot.y
189
- localRotations[qi + 2] = finalRot.z
190
- localRotations[qi + 3] = finalRot.w
170
+ this.setQuatToArray(localRotations, qi, finalRot)
191
171
  }
192
172
  }
193
173
  }
@@ -227,25 +207,20 @@ export class IKSolverSystem {
227
207
  finalRotationAxis = this.transformNormal(chainRotationAxis, invParentRot).normalize()
228
208
  break
229
209
  }
230
- case InternalSolveAxis.X: {
231
- const m = parentWorldRotMatrix.values
232
- const axisX = new Vec3(m[0], m[1], m[2])
233
- const dot = chainRotationAxis.dot(axisX)
234
- finalRotationAxis = new Vec3(dot >= 0 ? 1 : -1, 0, 0)
235
- break
236
- }
237
- case InternalSolveAxis.Y: {
238
- const m = parentWorldRotMatrix.values
239
- const axisY = new Vec3(m[4], m[5], m[6])
240
- const dot = chainRotationAxis.dot(axisY)
241
- finalRotationAxis = new Vec3(0, dot >= 0 ? 1 : -1, 0)
242
- break
243
- }
210
+ case InternalSolveAxis.X:
211
+ case InternalSolveAxis.Y:
244
212
  case InternalSolveAxis.Z: {
245
213
  const m = parentWorldRotMatrix.values
246
- const axisZ = new Vec3(m[8], m[9], m[10])
247
- const dot = chainRotationAxis.dot(axisZ)
248
- finalRotationAxis = new Vec3(0, 0, dot >= 0 ? 1 : -1)
214
+ const axisOffset = (chain.solveAxis - InternalSolveAxis.X) * 4
215
+ const axis = new Vec3(m[axisOffset], m[axisOffset + 1], m[axisOffset + 2])
216
+ const dot = chainRotationAxis.dot(axis)
217
+ const sign = dot >= 0 ? 1 : -1
218
+ finalRotationAxis =
219
+ chain.solveAxis === InternalSolveAxis.X
220
+ ? new Vec3(sign, 0, 0)
221
+ : chain.solveAxis === InternalSolveAxis.Y
222
+ ? new Vec3(0, sign, 0)
223
+ : new Vec3(0, 0, sign)
249
224
  break
250
225
  }
251
226
  default:
@@ -267,84 +242,16 @@ export class IKSolverSystem {
267
242
  chainInfo.ikRotation = ikRotation.multiply(chainInfo.ikRotation)
268
243
 
269
244
  // Apply angle constraints if present
270
- if (chain.minimumAngle !== null && chain.maximumAngle !== null) {
245
+ if (chain.minimumAngle && chain.maximumAngle) {
271
246
  const qi = chainBoneIndex * 4
272
- const localRot = new Quat(
273
- localRotations[qi],
274
- localRotations[qi + 1],
275
- localRotations[qi + 2],
276
- localRotations[qi + 3]
277
- )
247
+ const localRot = this.getQuatFromArray(localRotations, qi)
278
248
  chainInfo.localRotation = localRot.clone()
279
249
 
280
250
  const combinedRot = chainInfo.ikRotation.multiply(localRot)
281
- const rotMatrix = Mat4.fromQuat(combinedRot.x, combinedRot.y, combinedRot.z, combinedRot.w)
282
- const m = rotMatrix.values
283
-
284
- let rX: number, rY: number, rZ: number
285
-
286
- switch (chain.rotationOrder) {
287
- case InternalEulerRotationOrder.YXZ: {
288
- rX = Math.asin(-m[9]) // m32
289
- if (Math.abs(rX) > this.THRESHOLD) {
290
- rX = rX < 0 ? -this.THRESHOLD : this.THRESHOLD
291
- }
292
- let cosX = Math.cos(rX)
293
- if (cosX !== 0) cosX = 1 / cosX
294
- rY = Math.atan2(m[8] * cosX, m[10] * cosX) // m31, m33
295
- rZ = Math.atan2(m[1] * cosX, m[5] * cosX) // m12, m22
296
-
297
- rX = this.limitAngle(rX, chain.minimumAngle.x, chain.maximumAngle.x, useAxis)
298
- rY = this.limitAngle(rY, chain.minimumAngle.y, chain.maximumAngle.y, useAxis)
299
- rZ = this.limitAngle(rZ, chain.minimumAngle.z, chain.maximumAngle.z, useAxis)
300
-
301
- chainInfo.ikRotation = Quat.fromAxisAngle(new Vec3(0, 1, 0), rY)
302
- chainInfo.ikRotation = chainInfo.ikRotation.multiply(Quat.fromAxisAngle(new Vec3(1, 0, 0), rX))
303
- chainInfo.ikRotation = chainInfo.ikRotation.multiply(Quat.fromAxisAngle(new Vec3(0, 0, 1), rZ))
304
- break
305
- }
306
- case InternalEulerRotationOrder.ZYX: {
307
- rY = Math.asin(-m[2]) // m13
308
- if (Math.abs(rY) > this.THRESHOLD) {
309
- rY = rY < 0 ? -this.THRESHOLD : this.THRESHOLD
310
- }
311
- let cosY = Math.cos(rY)
312
- if (cosY !== 0) cosY = 1 / cosY
313
- rX = Math.atan2(m[6] * cosY, m[10] * cosY) // m23, m33
314
- rZ = Math.atan2(m[1] * cosY, m[0] * cosY) // m12, m11
315
-
316
- rX = this.limitAngle(rX, chain.minimumAngle.x, chain.maximumAngle.x, useAxis)
317
- rY = this.limitAngle(rY, chain.minimumAngle.y, chain.maximumAngle.y, useAxis)
318
- rZ = this.limitAngle(rZ, chain.minimumAngle.z, chain.maximumAngle.z, useAxis)
319
-
320
- chainInfo.ikRotation = Quat.fromAxisAngle(new Vec3(0, 0, 1), rZ)
321
- chainInfo.ikRotation = chainInfo.ikRotation.multiply(Quat.fromAxisAngle(new Vec3(0, 1, 0), rY))
322
- chainInfo.ikRotation = chainInfo.ikRotation.multiply(Quat.fromAxisAngle(new Vec3(1, 0, 0), rX))
323
- break
324
- }
325
- case InternalEulerRotationOrder.XZY: {
326
- rZ = Math.asin(-m[4]) // m21
327
- if (Math.abs(rZ) > this.THRESHOLD) {
328
- rZ = rZ < 0 ? -this.THRESHOLD : this.THRESHOLD
329
- }
330
- let cosZ = Math.cos(rZ)
331
- if (cosZ !== 0) cosZ = 1 / cosZ
332
- rX = Math.atan2(m[6] * cosZ, m[5] * cosZ) // m23, m22
333
- rY = Math.atan2(m[8] * cosZ, m[0] * cosZ) // m31, m11
334
-
335
- rX = this.limitAngle(rX, chain.minimumAngle.x, chain.maximumAngle.x, useAxis)
336
- rY = this.limitAngle(rY, chain.minimumAngle.y, chain.maximumAngle.y, useAxis)
337
- rZ = this.limitAngle(rZ, chain.minimumAngle.z, chain.maximumAngle.z, useAxis)
338
-
339
- chainInfo.ikRotation = Quat.fromAxisAngle(new Vec3(1, 0, 0), rX)
340
- chainInfo.ikRotation = chainInfo.ikRotation.multiply(Quat.fromAxisAngle(new Vec3(0, 0, 1), rZ))
341
- chainInfo.ikRotation = chainInfo.ikRotation.multiply(Quat.fromAxisAngle(new Vec3(0, 1, 0), rY))
342
- break
343
- }
344
- }
345
-
346
- const invertedLocalRotation = localRot.conjugate().normalize()
347
- chainInfo.ikRotation = chainInfo.ikRotation.multiply(invertedLocalRotation)
251
+ const euler = this.extractEulerAngles(combinedRot, chain.rotationOrder)
252
+ const limited = this.limitEulerAngles(euler, chain.minimumAngle, chain.maximumAngle, useAxis)
253
+ chainInfo.ikRotation = this.reconstructQuatFromEuler(limited, chain.rotationOrder)
254
+ chainInfo.ikRotation = chainInfo.ikRotation.multiply(localRot.conjugate().normalize())
348
255
  }
349
256
  }
350
257
 
@@ -368,11 +275,92 @@ export class IKSolverSystem {
368
275
  }
369
276
  }
370
277
 
278
+ private static getDistance(boneIndex1: number, boneIndex2: number, worldMatrices: Float32Array): number {
279
+ const pos1 = this.getWorldTranslation(boneIndex1, worldMatrices)
280
+ const pos2 = this.getWorldTranslation(boneIndex2, worldMatrices)
281
+ return pos1.subtract(pos2).length()
282
+ }
283
+
371
284
  private static getWorldTranslation(boneIndex: number, worldMatrices: Float32Array): Vec3 {
372
285
  const offset = boneIndex * 16
373
286
  return new Vec3(worldMatrices[offset + 12], worldMatrices[offset + 13], worldMatrices[offset + 14])
374
287
  }
375
288
 
289
+ private static getQuatFromArray(array: Float32Array, offset: number): Quat {
290
+ return new Quat(array[offset], array[offset + 1], array[offset + 2], array[offset + 3])
291
+ }
292
+
293
+ private static setQuatToArray(array: Float32Array, offset: number, quat: Quat): void {
294
+ array[offset] = quat.x
295
+ array[offset + 1] = quat.y
296
+ array[offset + 2] = quat.z
297
+ array[offset + 3] = quat.w
298
+ }
299
+
300
+ private static extractEulerAngles(quat: Quat, order: InternalEulerRotationOrder): Vec3 {
301
+ const rotMatrix = Mat4.fromQuat(quat.x, quat.y, quat.z, quat.w)
302
+ const m = rotMatrix.values
303
+
304
+ switch (order) {
305
+ case InternalEulerRotationOrder.YXZ: {
306
+ let rX = Math.asin(-m[9])
307
+ if (Math.abs(rX) > this.THRESHOLD) rX = rX < 0 ? -this.THRESHOLD : this.THRESHOLD
308
+ let cosX = Math.cos(rX)
309
+ if (cosX !== 0) cosX = 1 / cosX
310
+ const rY = Math.atan2(m[8] * cosX, m[10] * cosX)
311
+ const rZ = Math.atan2(m[1] * cosX, m[5] * cosX)
312
+ return new Vec3(rX, rY, rZ)
313
+ }
314
+ case InternalEulerRotationOrder.ZYX: {
315
+ let rY = Math.asin(-m[2])
316
+ if (Math.abs(rY) > this.THRESHOLD) rY = rY < 0 ? -this.THRESHOLD : this.THRESHOLD
317
+ let cosY = Math.cos(rY)
318
+ if (cosY !== 0) cosY = 1 / cosY
319
+ const rX = Math.atan2(m[6] * cosY, m[10] * cosY)
320
+ const rZ = Math.atan2(m[1] * cosY, m[0] * cosY)
321
+ return new Vec3(rX, rY, rZ)
322
+ }
323
+ case InternalEulerRotationOrder.XZY: {
324
+ let rZ = Math.asin(-m[4])
325
+ if (Math.abs(rZ) > this.THRESHOLD) rZ = rZ < 0 ? -this.THRESHOLD : this.THRESHOLD
326
+ let cosZ = Math.cos(rZ)
327
+ if (cosZ !== 0) cosZ = 1 / cosZ
328
+ const rX = Math.atan2(m[6] * cosZ, m[5] * cosZ)
329
+ const rY = Math.atan2(m[8] * cosZ, m[0] * cosZ)
330
+ return new Vec3(rX, rY, rZ)
331
+ }
332
+ }
333
+ }
334
+
335
+ private static limitEulerAngles(euler: Vec3, min: Vec3, max: Vec3, useAxis: boolean): Vec3 {
336
+ return new Vec3(
337
+ this.limitAngle(euler.x, min.x, max.x, useAxis),
338
+ this.limitAngle(euler.y, min.y, max.y, useAxis),
339
+ this.limitAngle(euler.z, min.z, max.z, useAxis)
340
+ )
341
+ }
342
+
343
+ private static reconstructQuatFromEuler(euler: Vec3, order: InternalEulerRotationOrder): Quat {
344
+ const axes = [
345
+ [new Vec3(1, 0, 0), new Vec3(0, 1, 0), new Vec3(0, 0, 1)],
346
+ [new Vec3(0, 0, 1), new Vec3(0, 1, 0), new Vec3(1, 0, 0)],
347
+ [new Vec3(0, 1, 0), new Vec3(1, 0, 0), new Vec3(0, 0, 1)],
348
+ ]
349
+
350
+ const [axis1, axis2, axis3] = axes[order]
351
+ const [angle1, angle2, angle3] =
352
+ order === InternalEulerRotationOrder.YXZ
353
+ ? [euler.y, euler.x, euler.z]
354
+ : order === InternalEulerRotationOrder.ZYX
355
+ ? [euler.z, euler.y, euler.x]
356
+ : [euler.x, euler.z, euler.y]
357
+
358
+ let result = Quat.fromAxisAngle(axis1, angle1)
359
+ result = result.multiply(Quat.fromAxisAngle(axis2, angle2))
360
+ result = result.multiply(Quat.fromAxisAngle(axis3, angle3))
361
+ return result
362
+ }
363
+
376
364
  private static getParentWorldRotationMatrix(boneIndex: number, bones: Bone[], worldMatrices: Float32Array): Mat4 {
377
365
  const bone = bones[boneIndex]
378
366
  if (bone.parentIndex >= 0) {
@@ -409,13 +397,7 @@ export class IKSolverSystem {
409
397
  const qi = boneIndex * 4
410
398
  const ti = boneIndex * 3
411
399
 
412
- // Get local rotation
413
- const localRot = new Quat(
414
- localRotations[qi],
415
- localRotations[qi + 1],
416
- localRotations[qi + 2],
417
- localRotations[qi + 3]
418
- )
400
+ const localRot = this.getQuatFromArray(localRotations, qi)
419
401
 
420
402
  // Apply IK rotation if available
421
403
  let finalRot = localRot
package/src/math.ts CHANGED
@@ -26,10 +26,6 @@ export class Vec3 {
26
26
  return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z)
27
27
  }
28
28
 
29
- lengthSquared(): number {
30
- return this.x * this.x + this.y * this.y + this.z * this.z
31
- }
32
-
33
29
  normalize(): Vec3 {
34
30
  const len = this.length()
35
31
  if (len === 0) return new Vec3(0, 0, 0)
@@ -51,10 +47,6 @@ export class Vec3 {
51
47
  scale(scalar: number): Vec3 {
52
48
  return new Vec3(this.x * scalar, this.y * scalar, this.z * scalar)
53
49
  }
54
-
55
- clone(): Vec3 {
56
- return new Vec3(this.x, this.y, this.z)
57
- }
58
50
  }
59
51
 
60
52
  export class Quat {
@@ -103,47 +95,6 @@ export class Quat {
103
95
  return new Quat(this.x / len, this.y / len, this.z / len, this.w / len)
104
96
  }
105
97
 
106
- // Rotate a vector by this quaternion: result = q * v * q^-1
107
- rotateVec(v: Vec3): Vec3 {
108
- // Treat v as pure quaternion (x, y, z, 0)
109
- const qx = this.x,
110
- qy = this.y,
111
- qz = this.z,
112
- qw = this.w
113
- const vx = v.x,
114
- vy = v.y,
115
- vz = v.z
116
-
117
- // t = 2 * cross(q.xyz, v)
118
- const tx = 2 * (qy * vz - qz * vy)
119
- const ty = 2 * (qz * vx - qx * vz)
120
- const tz = 2 * (qx * vy - qy * vx)
121
-
122
- // result = v + q.w * t + cross(q.xyz, t)
123
- return new Vec3(
124
- vx + qw * tx + (qy * tz - qz * ty),
125
- vy + qw * ty + (qz * tx - qx * tz),
126
- vz + qw * tz + (qx * ty - qy * tx)
127
- )
128
- }
129
-
130
- // Static method: create quaternion that rotates from one direction to another
131
- static fromTo(from: Vec3, to: Vec3): Quat {
132
- const dot = from.dot(to)
133
- if (dot > 0.999999) return new Quat(0, 0, 0, 1) // Already aligned
134
- if (dot < -0.999999) {
135
- // 180 degrees
136
- let axis = from.cross(new Vec3(1, 0, 0))
137
- if (axis.length() < 0.001) axis = from.cross(new Vec3(0, 1, 0))
138
- return new Quat(axis.x, axis.y, axis.z, 0).normalize()
139
- }
140
-
141
- const axis = from.cross(to)
142
- const w = Math.sqrt((1 + dot) * 2)
143
- const invW = 1 / w
144
- return new Quat(axis.x * invW, axis.y * invW, axis.z * invW, w * 0.5).normalize()
145
- }
146
-
147
98
  // Static method: create quaternion from rotation axis and angle
148
99
  static fromAxisAngle(axis: Vec3, angle: number): Quat {
149
100
  const normalizedAxis = axis.normalize()
@@ -209,31 +160,6 @@ export class Quat {
209
160
 
210
161
  return new Quat(x, y, z, w).normalize()
211
162
  }
212
-
213
- // Convert quaternion to Euler angles (ZXY order, inverse of fromEuler)
214
- toEuler(): Vec3 {
215
- const qx = this.x
216
- const qy = this.y
217
- const qz = this.z
218
- const qw = this.w
219
-
220
- // ZXY order (left-handed)
221
- // Roll (X): rotation around X axis
222
- const sinr_cosp = 2 * (qw * qx + qy * qz)
223
- const cosr_cosp = 1 - 2 * (qx * qx + qy * qy)
224
- const rotX = Math.atan2(sinr_cosp, cosr_cosp)
225
-
226
- // Pitch (Y): rotation around Y axis
227
- const sinp = 2 * (qw * qy - qz * qx)
228
- const rotY = Math.abs(sinp) >= 1 ? (sinp >= 0 ? Math.PI / 2 : -Math.PI / 2) : Math.asin(sinp)
229
-
230
- // Yaw (Z): rotation around Z axis
231
- const siny_cosp = 2 * (qw * qz + qx * qy)
232
- const cosy_cosp = 1 - 2 * (qy * qy + qz * qz)
233
- const rotZ = Math.atan2(siny_cosp, cosy_cosp)
234
-
235
- return new Vec3(rotX, rotY, rotZ)
236
- }
237
163
  }
238
164
 
239
165
  export class Mat4 {