reze-engine 0.13.0 → 0.13.2
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/README.md +32 -29
- package/dist/ik-solver.d.ts +5 -6
- package/dist/ik-solver.d.ts.map +1 -1
- package/dist/ik-solver.js +163 -98
- package/dist/math.d.ts +20 -0
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +300 -0
- package/dist/model.d.ts +9 -2
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +113 -52
- package/dist/physics.d.ts.map +1 -1
- package/dist/physics.js +27 -19
- package/package.json +1 -1
- package/src/ik-solver.ts +164 -111
- package/src/math.ts +291 -0
- package/src/model.ts +124 -60
- package/src/physics.ts +31 -23
package/dist/physics.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { Quat, Vec3, Mat4 } from "./math";
|
|
2
|
+
// Physics-local scratch pool for per-frame sync (syncFromBones, applyAmmoRigidbodiesToBones).
|
|
3
|
+
// Each method uses only these slots and completes synchronously before the next is called.
|
|
4
|
+
const _physMat = [
|
|
5
|
+
new Float32Array(16), new Float32Array(16), new Float32Array(16),
|
|
6
|
+
];
|
|
7
|
+
const _physQuat = new Quat(0, 0, 0, 1);
|
|
2
8
|
import { loadAmmo } from "./ammo-loader";
|
|
3
9
|
export var RigidbodyShape;
|
|
4
10
|
(function (RigidbodyShape) {
|
|
@@ -358,9 +364,9 @@ export class Physics {
|
|
|
358
364
|
this.firstFrame = false;
|
|
359
365
|
}
|
|
360
366
|
// Step order: 1) Sync Static/Kinematic from bones, 2) Step physics, 3) Apply dynamic to bones
|
|
361
|
-
this.syncFromBones(boneWorldMatrices,
|
|
367
|
+
this.syncFromBones(boneWorldMatrices, boneCount);
|
|
362
368
|
this.stepAmmoPhysics(dt);
|
|
363
|
-
this.applyAmmoRigidbodiesToBones(boneWorldMatrices,
|
|
369
|
+
this.applyAmmoRigidbodiesToBones(boneWorldMatrices, boneCount);
|
|
364
370
|
}
|
|
365
371
|
// Compute bodyOffsetMatrixInverse for all rigidbodies (called once during initialization)
|
|
366
372
|
computeBodyOffsets(boneInverseBindMatrices, boneCount) {
|
|
@@ -425,7 +431,7 @@ export class Physics {
|
|
|
425
431
|
}
|
|
426
432
|
}
|
|
427
433
|
// Sync Static (FollowBone) and Kinematic rigidbodies to follow bone transforms
|
|
428
|
-
syncFromBones(boneWorldMatrices,
|
|
434
|
+
syncFromBones(boneWorldMatrices, boneCount) {
|
|
429
435
|
if (!this.ammo || !this.dynamicsWorld)
|
|
430
436
|
return;
|
|
431
437
|
const Ammo = this.ammo;
|
|
@@ -440,14 +446,17 @@ export class Physics {
|
|
|
440
446
|
rb.boneIndex < boneCount) {
|
|
441
447
|
const boneIdx = rb.boneIndex;
|
|
442
448
|
const boneWorldMat = boneWorldMatrices[boneIdx];
|
|
443
|
-
//
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
449
|
+
// Lazy-cache bodyOffsetMatrix on first hit (cold path).
|
|
450
|
+
if (!rb.bodyOffsetMatrix)
|
|
451
|
+
rb.bodyOffsetMatrix = rb.bodyOffsetMatrixInverse.inverse();
|
|
452
|
+
// nodeWorld = boneWorld × bodyOffsetMatrix → _physMat[0]
|
|
453
|
+
Mat4.multiplyArrays(boneWorldMat.values, 0, rb.bodyOffsetMatrix.values, 0, _physMat[0], 0);
|
|
454
|
+
const nodeVals = _physMat[0];
|
|
455
|
+
const wx = nodeVals[12], wy = nodeVals[13], wz = nodeVals[14];
|
|
456
|
+
Mat4.toQuatFromArrayInto(nodeVals, 0, _physQuat);
|
|
448
457
|
const transform = new Ammo.btTransform();
|
|
449
|
-
const pos = new Ammo.btVector3(
|
|
450
|
-
const quat = new Ammo.btQuaternion(
|
|
458
|
+
const pos = new Ammo.btVector3(wx, wy, wz);
|
|
459
|
+
const quat = new Ammo.btQuaternion(_physQuat.x, _physQuat.y, _physQuat.z, _physQuat.w);
|
|
451
460
|
transform.setOrigin(pos);
|
|
452
461
|
transform.setRotation(quat);
|
|
453
462
|
ammoBody.setWorldTransform(transform);
|
|
@@ -472,7 +481,7 @@ export class Physics {
|
|
|
472
481
|
this.dynamicsWorld.stepSimulation(dt, maxSubSteps, fixedTimeStep);
|
|
473
482
|
}
|
|
474
483
|
// Apply dynamic rigidbody world transforms to bone world matrices in-place
|
|
475
|
-
applyAmmoRigidbodiesToBones(boneWorldMatrices,
|
|
484
|
+
applyAmmoRigidbodiesToBones(boneWorldMatrices, boneCount) {
|
|
476
485
|
if (!this.ammo || !this.dynamicsWorld)
|
|
477
486
|
return;
|
|
478
487
|
for (let i = 0; i < this.rigidbodies.length; i++) {
|
|
@@ -486,14 +495,13 @@ export class Physics {
|
|
|
486
495
|
const transform = ammoBody.getWorldTransform();
|
|
487
496
|
const origin = transform.getOrigin();
|
|
488
497
|
const rotation = transform.getRotation();
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
boneWorldMatrices[boneIdx].values.set(values);
|
|
498
|
+
// nodeWorldMatrix → _physMat[0] (from ammo position/rotation directly)
|
|
499
|
+
Mat4.fromPositionRotationInto(origin.x(), origin.y(), origin.z(), rotation.x(), rotation.y(), rotation.z(), rotation.w(), _physMat[0]);
|
|
500
|
+
// boneWorld = nodeWorld × bodyOffsetMatrixInverse → _physMat[1]
|
|
501
|
+
const boneVals = _physMat[1];
|
|
502
|
+
Mat4.multiplyArrays(_physMat[0], 0, rb.bodyOffsetMatrixInverse.values, 0, boneVals, 0);
|
|
503
|
+
if (!isNaN(boneVals[0]) && !isNaN(boneVals[15]) && Math.abs(boneVals[0]) < 1e6 && Math.abs(boneVals[15]) < 1e6) {
|
|
504
|
+
boneWorldMatrices[boneIdx].values.set(boneVals);
|
|
497
505
|
}
|
|
498
506
|
else {
|
|
499
507
|
console.warn(`[Physics] Invalid bone world matrix for rigidbody ${i} (${rb.name}), skipping update`);
|
package/package.json
CHANGED
package/src/ik-solver.ts
CHANGED
|
@@ -72,6 +72,20 @@ class IKChain {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
// IK-local scratch pool. Safe because solve() runs synchronously and all scratch
|
|
76
|
+
// use completes before the updateWorldMatrix callback is invoked.
|
|
77
|
+
const _ikVec: Vec3[] = [
|
|
78
|
+
new Vec3(0, 0, 0), new Vec3(0, 0, 0), new Vec3(0, 0, 0), new Vec3(0, 0, 0), new Vec3(0, 0, 0),
|
|
79
|
+
new Vec3(0, 0, 0), new Vec3(0, 0, 0), new Vec3(0, 0, 0), new Vec3(0, 0, 0),
|
|
80
|
+
]
|
|
81
|
+
const _ikQuat: Quat[] = [
|
|
82
|
+
new Quat(0, 0, 0, 1), new Quat(0, 0, 0, 1), new Quat(0, 0, 0, 1),
|
|
83
|
+
new Quat(0, 0, 0, 1), new Quat(0, 0, 0, 1), new Quat(0, 0, 0, 1),
|
|
84
|
+
]
|
|
85
|
+
const _ikMat: Float32Array[] = [
|
|
86
|
+
new Float32Array(16), new Float32Array(16), new Float32Array(16), new Float32Array(16),
|
|
87
|
+
]
|
|
88
|
+
|
|
75
89
|
export class IKSolverSystem {
|
|
76
90
|
private static readonly EPSILON = 1.0e-8
|
|
77
91
|
private static readonly THRESHOLD = (88 * Math.PI) / 180
|
|
@@ -163,13 +177,13 @@ export class IKSolverSystem {
|
|
|
163
177
|
if (this.getDistance(ikBoneIndex, targetBoneIndex, worldMatrices) < this.EPSILON) break
|
|
164
178
|
}
|
|
165
179
|
|
|
166
|
-
// Apply IK rotations to local rotations
|
|
180
|
+
// Apply IK rotations to local rotations (mutate localRot in place)
|
|
167
181
|
for (const link of solver.links) {
|
|
168
182
|
const chainInfo = ikChainInfo[link.boneIndex]
|
|
169
183
|
if (chainInfo?.ikRotation) {
|
|
170
184
|
const localRot = localRotations[link.boneIndex]
|
|
171
|
-
|
|
172
|
-
localRot.
|
|
185
|
+
Quat.multiplyInto(chainInfo.ikRotation, localRot, localRot)
|
|
186
|
+
localRot.normalize()
|
|
173
187
|
}
|
|
174
188
|
}
|
|
175
189
|
}
|
|
@@ -189,72 +203,95 @@ export class IKSolverSystem {
|
|
|
189
203
|
updateWorldMatrix?: UpdateWorldMatrixFn
|
|
190
204
|
): void {
|
|
191
205
|
const chainBoneIndex = chain.boneIndex
|
|
192
|
-
const chainPosition = this.getWorldTranslation(chainBoneIndex, worldMatrices)
|
|
193
|
-
const ikPosition = this.getWorldTranslation(ikBoneIndex, worldMatrices)
|
|
194
|
-
const targetPosition = this.getWorldTranslation(targetBoneIndex, worldMatrices)
|
|
195
206
|
|
|
196
|
-
|
|
197
|
-
|
|
207
|
+
// scratch layout:
|
|
208
|
+
// _ikVec[0]=chainPos, [1]=ikPos, [2]=targetPos, [3]=chainTargetVec, [4]=chainIkVec,
|
|
209
|
+
// [5]=rotAxis, [6]=finalAxis, [7]=eulerTmp, [8]=limitedEuler
|
|
210
|
+
// _ikMat[0]=parentRot, [1]=invParentRot, [2]=quatToMatTmp
|
|
211
|
+
// _ikQuat[0]=ikRotation, [1]=combinedRot, [2]=localRotConj, [3..5]=axisAngleTmp
|
|
212
|
+
|
|
213
|
+
const chainPos = Vec3.setFromMat4Translation(worldMatrices[chainBoneIndex].values, _ikVec[0])
|
|
214
|
+
const ikPos = Vec3.setFromMat4Translation(worldMatrices[ikBoneIndex].values, _ikVec[1])
|
|
215
|
+
const targetPos = Vec3.setFromMat4Translation(worldMatrices[targetBoneIndex].values, _ikVec[2])
|
|
216
|
+
|
|
217
|
+
const chainTargetVec = Vec3.subtractInto(chainPos, targetPos, _ikVec[3])
|
|
218
|
+
chainTargetVec.normalize()
|
|
219
|
+
const chainIkVec = Vec3.subtractInto(chainPos, ikPos, _ikVec[4])
|
|
220
|
+
chainIkVec.normalize()
|
|
198
221
|
|
|
199
|
-
const
|
|
200
|
-
if (
|
|
222
|
+
const rotAxis = Vec3.crossInto(chainTargetVec, chainIkVec, _ikVec[5])
|
|
223
|
+
if (rotAxis.length() < this.EPSILON) return
|
|
201
224
|
|
|
202
|
-
//
|
|
203
|
-
|
|
225
|
+
// Parent world rotation matrix (translation removed) into _ikMat[0]
|
|
226
|
+
this.getParentWorldRotationMatrixInto(chainBoneIndex, bones, worldMatrices, _ikMat[0])
|
|
204
227
|
|
|
205
|
-
let
|
|
228
|
+
let finalAxis: Vec3
|
|
206
229
|
if (chain.minimumAngle !== null && useAxis) {
|
|
207
230
|
switch (chain.solveAxis) {
|
|
208
231
|
case InternalSolveAxis.None: {
|
|
209
|
-
|
|
210
|
-
|
|
232
|
+
if (!Mat4.inverseInto(_ikMat[0], _ikMat[1])) {
|
|
233
|
+
finalAxis = rotAxis
|
|
234
|
+
} else {
|
|
235
|
+
finalAxis = Vec3.transformMat4RotationInto(rotAxis, _ikMat[1], _ikVec[6])
|
|
236
|
+
finalAxis.normalize()
|
|
237
|
+
}
|
|
211
238
|
break
|
|
212
239
|
}
|
|
213
240
|
case InternalSolveAxis.X:
|
|
214
241
|
case InternalSolveAxis.Y:
|
|
215
242
|
case InternalSolveAxis.Z: {
|
|
216
|
-
const m =
|
|
243
|
+
const m = _ikMat[0]
|
|
217
244
|
const axisOffset = (chain.solveAxis - InternalSolveAxis.X) * 4
|
|
218
|
-
const
|
|
219
|
-
const
|
|
220
|
-
const sign =
|
|
221
|
-
|
|
245
|
+
const ax = m[axisOffset], ay = m[axisOffset + 1], az = m[axisOffset + 2]
|
|
246
|
+
const dotA = rotAxis.x * ax + rotAxis.y * ay + rotAxis.z * az
|
|
247
|
+
const sign = dotA >= 0 ? 1 : -1
|
|
248
|
+
finalAxis =
|
|
222
249
|
chain.solveAxis === InternalSolveAxis.X
|
|
223
|
-
?
|
|
250
|
+
? _ikVec[6].setXYZ(sign, 0, 0)
|
|
224
251
|
: chain.solveAxis === InternalSolveAxis.Y
|
|
225
|
-
?
|
|
226
|
-
:
|
|
252
|
+
? _ikVec[6].setXYZ(0, sign, 0)
|
|
253
|
+
: _ikVec[6].setXYZ(0, 0, sign)
|
|
227
254
|
break
|
|
228
255
|
}
|
|
229
256
|
default:
|
|
230
|
-
|
|
257
|
+
finalAxis = rotAxis
|
|
231
258
|
}
|
|
232
259
|
} else {
|
|
233
|
-
|
|
234
|
-
|
|
260
|
+
if (!Mat4.inverseInto(_ikMat[0], _ikMat[1])) {
|
|
261
|
+
finalAxis = rotAxis
|
|
262
|
+
} else {
|
|
263
|
+
finalAxis = Vec3.transformMat4RotationInto(rotAxis, _ikMat[1], _ikVec[6])
|
|
264
|
+
finalAxis.normalize()
|
|
265
|
+
}
|
|
235
266
|
}
|
|
236
267
|
|
|
237
|
-
let
|
|
238
|
-
|
|
268
|
+
let dotTI = chainTargetVec.dot(chainIkVec)
|
|
269
|
+
dotTI = Math.max(-1.0, Math.min(1.0, dotTI))
|
|
239
270
|
|
|
240
|
-
const angle = Math.min(solver.limitAngle * (chainIndex + 1), Math.acos(
|
|
241
|
-
const ikRotation = Quat.
|
|
271
|
+
const angle = Math.min(solver.limitAngle * (chainIndex + 1), Math.acos(dotTI))
|
|
272
|
+
const ikRotation = Quat.fromAxisAngleInto(finalAxis.x, finalAxis.y, finalAxis.z, angle, _ikQuat[0])
|
|
242
273
|
|
|
243
274
|
const chainInfo = ikChainInfo[chainBoneIndex]
|
|
244
275
|
if (chainInfo) {
|
|
245
|
-
chainInfo.ikRotation = ikRotation
|
|
276
|
+
// chainInfo.ikRotation = ikRotation * chainInfo.ikRotation (in place)
|
|
277
|
+
Quat.multiplyInto(ikRotation, chainInfo.ikRotation, chainInfo.ikRotation)
|
|
246
278
|
|
|
247
|
-
// Apply angle constraints if present
|
|
248
279
|
if (chain.minimumAngle && chain.maximumAngle) {
|
|
249
280
|
const localRot = localRotations[chainBoneIndex]
|
|
250
281
|
chainInfo.localRotation = localRot.clone()
|
|
251
282
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
//
|
|
257
|
-
|
|
283
|
+
// combinedRot = chainInfo.ikRotation * localRot
|
|
284
|
+
Quat.multiplyInto(chainInfo.ikRotation, localRot, _ikQuat[1])
|
|
285
|
+
// extract euler into _ikVec[7]
|
|
286
|
+
this.extractEulerAnglesInto(_ikQuat[1], chain.rotationOrder, _ikVec[7])
|
|
287
|
+
// limit into _ikVec[8]
|
|
288
|
+
this.limitEulerAnglesInto(_ikVec[7], chain.minimumAngle, chain.maximumAngle, useAxis, _ikVec[8])
|
|
289
|
+
// reconstruct quat into chainInfo.ikRotation (uses _ikQuat[3..5] as tmp)
|
|
290
|
+
this.reconstructQuatFromEulerInto(_ikVec[8], chain.rotationOrder, chainInfo.ikRotation)
|
|
291
|
+
// localRot conjugate into _ikQuat[2] (localRot is unit, so conjugate == inverse)
|
|
292
|
+
_ikQuat[2].setXYZW(-localRot.x, -localRot.y, -localRot.z, localRot.w)
|
|
293
|
+
// chainInfo.ikRotation *= localRotConj
|
|
294
|
+
Quat.multiplyInto(chainInfo.ikRotation, _ikQuat[2], chainInfo.ikRotation)
|
|
258
295
|
}
|
|
259
296
|
}
|
|
260
297
|
|
|
@@ -288,19 +325,28 @@ export class IKSolverSystem {
|
|
|
288
325
|
}
|
|
289
326
|
|
|
290
327
|
private static getDistance(boneIndex1: number, boneIndex2: number, worldMatrices: Mat4[]): number {
|
|
291
|
-
const
|
|
292
|
-
const
|
|
293
|
-
|
|
328
|
+
const m1 = worldMatrices[boneIndex1].values
|
|
329
|
+
const m2 = worldMatrices[boneIndex2].values
|
|
330
|
+
const dx = m1[12] - m2[12]
|
|
331
|
+
const dy = m1[13] - m2[13]
|
|
332
|
+
const dz = m1[14] - m2[14]
|
|
333
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz)
|
|
294
334
|
}
|
|
295
335
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
336
|
+
// Euler axis triples for each rotation order (indexed by order enum).
|
|
337
|
+
// Reused to avoid allocations in reconstructQuatFromEulerInto.
|
|
338
|
+
private static readonly EULER_AXES: readonly [number, number, number][][] = [
|
|
339
|
+
// YXZ: Y, X, Z
|
|
340
|
+
[[0, 1, 0], [1, 0, 0], [0, 0, 1]],
|
|
341
|
+
// ZYX: Z, Y, X
|
|
342
|
+
[[0, 0, 1], [0, 1, 0], [1, 0, 0]],
|
|
343
|
+
// XZY: X, Z, Y
|
|
344
|
+
[[1, 0, 0], [0, 0, 1], [0, 1, 0]],
|
|
345
|
+
]
|
|
346
|
+
|
|
347
|
+
private static extractEulerAnglesInto(quat: Quat, order: InternalEulerRotationOrder, out: Vec3): void {
|
|
348
|
+
Mat4.fromQuatInto(quat.x, quat.y, quat.z, quat.w, _ikMat[2], 0)
|
|
349
|
+
const m = _ikMat[2]
|
|
304
350
|
|
|
305
351
|
switch (order) {
|
|
306
352
|
case InternalEulerRotationOrder.YXZ: {
|
|
@@ -308,83 +354,83 @@ export class IKSolverSystem {
|
|
|
308
354
|
if (Math.abs(rX) > this.THRESHOLD) rX = rX < 0 ? -this.THRESHOLD : this.THRESHOLD
|
|
309
355
|
let cosX = Math.cos(rX)
|
|
310
356
|
if (cosX !== 0) cosX = 1 / cosX
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
357
|
+
out.x = rX
|
|
358
|
+
out.y = Math.atan2(m[8] * cosX, m[10] * cosX)
|
|
359
|
+
out.z = Math.atan2(m[1] * cosX, m[5] * cosX)
|
|
360
|
+
return
|
|
314
361
|
}
|
|
315
362
|
case InternalEulerRotationOrder.ZYX: {
|
|
316
363
|
let rY = Math.asin(-m[2])
|
|
317
364
|
if (Math.abs(rY) > this.THRESHOLD) rY = rY < 0 ? -this.THRESHOLD : this.THRESHOLD
|
|
318
365
|
let cosY = Math.cos(rY)
|
|
319
366
|
if (cosY !== 0) cosY = 1 / cosY
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
367
|
+
out.x = Math.atan2(m[6] * cosY, m[10] * cosY)
|
|
368
|
+
out.y = rY
|
|
369
|
+
out.z = Math.atan2(m[1] * cosY, m[0] * cosY)
|
|
370
|
+
return
|
|
323
371
|
}
|
|
324
372
|
case InternalEulerRotationOrder.XZY: {
|
|
325
373
|
let rZ = Math.asin(-m[4])
|
|
326
374
|
if (Math.abs(rZ) > this.THRESHOLD) rZ = rZ < 0 ? -this.THRESHOLD : this.THRESHOLD
|
|
327
375
|
let cosZ = Math.cos(rZ)
|
|
328
376
|
if (cosZ !== 0) cosZ = 1 / cosZ
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
377
|
+
out.x = Math.atan2(m[6] * cosZ, m[5] * cosZ)
|
|
378
|
+
out.y = Math.atan2(m[8] * cosZ, m[0] * cosZ)
|
|
379
|
+
out.z = rZ
|
|
380
|
+
return
|
|
332
381
|
}
|
|
333
382
|
}
|
|
334
383
|
}
|
|
335
384
|
|
|
336
|
-
private static
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
this.limitAngle(euler.z, min.z, max.z, useAxis)
|
|
341
|
-
)
|
|
385
|
+
private static limitEulerAnglesInto(euler: Vec3, min: Vec3, max: Vec3, useAxis: boolean, out: Vec3): void {
|
|
386
|
+
out.x = this.limitAngle(euler.x, min.x, max.x, useAxis)
|
|
387
|
+
out.y = this.limitAngle(euler.y, min.y, max.y, useAxis)
|
|
388
|
+
out.z = this.limitAngle(euler.z, min.z, max.z, useAxis)
|
|
342
389
|
}
|
|
343
390
|
|
|
344
|
-
private static
|
|
345
|
-
const axes = [
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
order === InternalEulerRotationOrder.
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
result =
|
|
361
|
-
|
|
362
|
-
|
|
391
|
+
private static reconstructQuatFromEulerInto(euler: Vec3, order: InternalEulerRotationOrder, out: Quat): void {
|
|
392
|
+
const axes = this.EULER_AXES[order]
|
|
393
|
+
const a1 = axes[0], a2 = axes[1], a3 = axes[2]
|
|
394
|
+
const ang1 =
|
|
395
|
+
order === InternalEulerRotationOrder.YXZ ? euler.y
|
|
396
|
+
: order === InternalEulerRotationOrder.ZYX ? euler.z
|
|
397
|
+
: euler.x
|
|
398
|
+
const ang2 =
|
|
399
|
+
order === InternalEulerRotationOrder.YXZ ? euler.x
|
|
400
|
+
: order === InternalEulerRotationOrder.ZYX ? euler.y
|
|
401
|
+
: euler.z
|
|
402
|
+
const ang3 =
|
|
403
|
+
order === InternalEulerRotationOrder.YXZ ? euler.z
|
|
404
|
+
: order === InternalEulerRotationOrder.ZYX ? euler.x
|
|
405
|
+
: euler.y
|
|
406
|
+
|
|
407
|
+
// result = axisAngle(a1, ang1); then *= axisAngle(a2, ang2); then *= axisAngle(a3, ang3)
|
|
408
|
+
Quat.fromAxisAngleInto(a1[0], a1[1], a1[2], ang1, out)
|
|
409
|
+
Quat.fromAxisAngleInto(a2[0], a2[1], a2[2], ang2, _ikQuat[3])
|
|
410
|
+
Quat.multiplyInto(out, _ikQuat[3], out)
|
|
411
|
+
Quat.fromAxisAngleInto(a3[0], a3[1], a3[2], ang3, _ikQuat[3])
|
|
412
|
+
Quat.multiplyInto(out, _ikQuat[3], out)
|
|
363
413
|
}
|
|
364
414
|
|
|
365
|
-
|
|
415
|
+
// Write parent's world rotation (translation stripped) into out Float32Array.
|
|
416
|
+
private static getParentWorldRotationMatrixInto(
|
|
417
|
+
boneIndex: number,
|
|
418
|
+
bones: Bone[],
|
|
419
|
+
worldMatrices: Mat4[],
|
|
420
|
+
out: Float32Array
|
|
421
|
+
): void {
|
|
366
422
|
const bone = bones[boneIndex]
|
|
367
423
|
if (bone.parentIndex >= 0) {
|
|
368
|
-
|
|
369
|
-
// Remove translation
|
|
370
|
-
const rotMat = Mat4.identity()
|
|
371
|
-
const m = parentMat.values
|
|
372
|
-
rotMat.values.set([m[0], m[1], m[2], 0, m[4], m[5], m[6], 0, m[8], m[9], m[10], 0, 0, 0, 0, 1])
|
|
373
|
-
return rotMat
|
|
424
|
+
Mat4.copyRotationInto(worldMatrices[bone.parentIndex].values, out)
|
|
374
425
|
} else {
|
|
375
|
-
|
|
426
|
+
// Identity
|
|
427
|
+
out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 0
|
|
428
|
+
out[4] = 0; out[5] = 1; out[6] = 0; out[7] = 0
|
|
429
|
+
out[8] = 0; out[9] = 0; out[10] = 1; out[11] = 0
|
|
430
|
+
out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1
|
|
376
431
|
}
|
|
377
432
|
}
|
|
378
433
|
|
|
379
|
-
private static transformNormal(normal: Vec3, matrix: Mat4): Vec3 {
|
|
380
|
-
const m = matrix.values
|
|
381
|
-
return new Vec3(
|
|
382
|
-
m[0] * normal.x + m[4] * normal.y + m[8] * normal.z,
|
|
383
|
-
m[1] * normal.x + m[5] * normal.y + m[9] * normal.z,
|
|
384
|
-
m[2] * normal.x + m[6] * normal.y + m[10] * normal.z
|
|
385
|
-
)
|
|
386
|
-
}
|
|
387
|
-
|
|
388
434
|
private static updateWorldMatrix(
|
|
389
435
|
boneIndex: number,
|
|
390
436
|
bones: Bone[],
|
|
@@ -397,28 +443,35 @@ export class IKSolverSystem {
|
|
|
397
443
|
const localRot = localRotations[boneIndex]
|
|
398
444
|
const localTrans = localTranslations[boneIndex]
|
|
399
445
|
|
|
400
|
-
|
|
401
|
-
let finalRot = localRot
|
|
446
|
+
let fx = localRot.x, fy = localRot.y, fz = localRot.z, fw = localRot.w
|
|
402
447
|
if (ikChainInfo) {
|
|
403
448
|
const chainInfo = ikChainInfo[boneIndex]
|
|
404
449
|
if (chainInfo && chainInfo.ikRotation) {
|
|
405
|
-
|
|
450
|
+
const ik = chainInfo.ikRotation
|
|
451
|
+
const nx = ik.w * fx + ik.x * fw + ik.y * fz - ik.z * fy
|
|
452
|
+
const ny = ik.w * fy - ik.x * fz + ik.y * fw + ik.z * fx
|
|
453
|
+
const nz = ik.w * fz + ik.x * fy - ik.y * fx + ik.z * fw
|
|
454
|
+
const nw = ik.w * fw - ik.x * fx - ik.y * fy - ik.z * fz
|
|
455
|
+
const len = Math.sqrt(nx * nx + ny * ny + nz * nz + nw * nw)
|
|
456
|
+
const inv = len > 0 ? 1 / len : 0
|
|
457
|
+
fx = nx * inv; fy = ny * inv; fz = nz * inv; fw = nw * inv
|
|
406
458
|
}
|
|
407
459
|
}
|
|
408
|
-
const rotateM = Mat4.fromQuat(finalRot.x, finalRot.y, finalRot.z, finalRot.w)
|
|
409
460
|
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
.
|
|
413
|
-
|
|
461
|
+
const localMVals = _ikMat[3]
|
|
462
|
+
Mat4.localTransformInto(
|
|
463
|
+
bone.bindTranslation[0], bone.bindTranslation[1], bone.bindTranslation[2],
|
|
464
|
+
fx, fy, fz, fw,
|
|
465
|
+
localTrans.x, localTrans.y, localTrans.z,
|
|
466
|
+
localMVals
|
|
467
|
+
)
|
|
414
468
|
|
|
415
469
|
const worldMat = worldMatrices[boneIndex]
|
|
416
470
|
if (bone.parentIndex >= 0) {
|
|
417
471
|
const parentMat = worldMatrices[bone.parentIndex]
|
|
418
|
-
|
|
419
|
-
worldMat.values.set(result.values)
|
|
472
|
+
Mat4.multiplyArrays(parentMat.values, 0, localMVals, 0, worldMat.values, 0)
|
|
420
473
|
} else {
|
|
421
|
-
worldMat.values.set(
|
|
474
|
+
worldMat.values.set(localMVals)
|
|
422
475
|
}
|
|
423
476
|
}
|
|
424
477
|
}
|