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/README.md +0 -1
- package/dist/engine.d.ts +10 -26
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +261 -670
- package/dist/engine_r.d.ts +132 -0
- package/dist/engine_r.d.ts.map +1 -0
- package/dist/engine_r.js +1489 -0
- package/dist/engine_ts.d.ts +143 -0
- package/dist/engine_ts.d.ts.map +1 -0
- package/dist/engine_ts.js +1575 -0
- package/dist/ik-solver.d.ts +6 -0
- package/dist/ik-solver.d.ts.map +1 -1
- package/dist/ik-solver.js +98 -101
- package/dist/math.d.ts +0 -5
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +0 -55
- package/dist/model.d.ts +59 -13
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +415 -141
- package/dist/player.d.ts +6 -20
- package/dist/player.d.ts.map +1 -1
- package/dist/player.js +88 -191
- package/package.json +1 -1
- package/src/engine.ts +299 -729
- package/src/ik-solver.ts +106 -124
- package/src/math.ts +0 -74
- package/src/model.ts +516 -186
- package/src/player.ts +115 -210
package/src/ik-solver.ts
CHANGED
|
@@ -117,11 +117,7 @@ export class IKSolverSystem {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
166
|
+
if (chainInfo?.ikRotation) {
|
|
179
167
|
const qi = link.boneIndex * 4
|
|
180
|
-
const localRot =
|
|
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
|
|
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
|
-
|
|
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
|
|
247
|
-
const
|
|
248
|
-
|
|
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
|
|
245
|
+
if (chain.minimumAngle && chain.maximumAngle) {
|
|
271
246
|
const qi = chainBoneIndex * 4
|
|
272
|
-
const localRot =
|
|
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
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
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 {
|