reze-engine 0.2.18 → 0.3.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.
- package/README.md +67 -66
- package/dist/bezier-interpolate.d.ts +15 -0
- package/dist/bezier-interpolate.d.ts.map +1 -0
- package/dist/bezier-interpolate.js +40 -0
- package/dist/engine.d.ts +10 -9
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +284 -144
- package/dist/ik-solver.d.ts +26 -0
- package/dist/ik-solver.d.ts.map +1 -0
- package/dist/ik-solver.js +372 -0
- package/dist/math.d.ts +1 -0
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +8 -0
- package/dist/model.d.ts +82 -3
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +357 -4
- package/dist/pmx-loader.d.ts +3 -1
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +218 -130
- package/dist/vmd-loader.d.ts +11 -1
- package/dist/vmd-loader.d.ts.map +1 -1
- package/dist/vmd-loader.js +91 -15
- package/package.json +1 -1
- package/src/bezier-interpolate.ts +47 -0
- package/src/camera.ts +358 -358
- package/src/engine.ts +308 -165
- package/src/ik-solver.ts +488 -0
- package/src/math.ts +555 -546
- package/src/model.ts +930 -421
- package/src/physics.ts +752 -752
- package/src/pmx-loader.ts +1173 -1054
- package/src/vmd-loader.ts +276 -179
package/src/ik-solver.ts
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IK Solver implementation
|
|
3
|
+
* Based on reference from babylon-mmd and Saba MMD library
|
|
4
|
+
* https://github.com/benikabocha/saba/blob/master/src/Saba/Model/MMD/MMDIkSolver.cpp
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Mat4, Quat, Vec3 } from "./math"
|
|
8
|
+
import { Bone, IKLink, IKSolver, IKChainInfo, EulerRotationOrder, SolveAxis } from "./model"
|
|
9
|
+
|
|
10
|
+
const enum InternalEulerRotationOrder {
|
|
11
|
+
YXZ = 0,
|
|
12
|
+
ZYX = 1,
|
|
13
|
+
XZY = 2,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const enum InternalSolveAxis {
|
|
17
|
+
None = 0,
|
|
18
|
+
Fixed = 1,
|
|
19
|
+
X = 2,
|
|
20
|
+
Y = 3,
|
|
21
|
+
Z = 4,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class IKChain {
|
|
25
|
+
public readonly boneIndex: number
|
|
26
|
+
public readonly minimumAngle: Vec3 | null
|
|
27
|
+
public readonly maximumAngle: Vec3 | null
|
|
28
|
+
public readonly rotationOrder: InternalEulerRotationOrder
|
|
29
|
+
public readonly solveAxis: InternalSolveAxis
|
|
30
|
+
|
|
31
|
+
public constructor(boneIndex: number, link: IKLink) {
|
|
32
|
+
this.boneIndex = boneIndex
|
|
33
|
+
|
|
34
|
+
if (link.hasLimit && link.minAngle && link.maxAngle) {
|
|
35
|
+
// Normalize min/max angles
|
|
36
|
+
const minX = Math.min(link.minAngle.x, link.maxAngle.x)
|
|
37
|
+
const minY = Math.min(link.minAngle.y, link.maxAngle.y)
|
|
38
|
+
const minZ = Math.min(link.minAngle.z, link.maxAngle.z)
|
|
39
|
+
const maxX = Math.max(link.minAngle.x, link.maxAngle.x)
|
|
40
|
+
const maxY = Math.max(link.minAngle.y, link.maxAngle.y)
|
|
41
|
+
const maxZ = Math.max(link.minAngle.z, link.maxAngle.z)
|
|
42
|
+
this.minimumAngle = new Vec3(minX, minY, minZ)
|
|
43
|
+
this.maximumAngle = new Vec3(maxX, maxY, maxZ)
|
|
44
|
+
|
|
45
|
+
// Determine rotation order based on constraint ranges
|
|
46
|
+
const halfPi = Math.PI * 0.5
|
|
47
|
+
if (-halfPi < minX && maxX < halfPi) {
|
|
48
|
+
this.rotationOrder = InternalEulerRotationOrder.YXZ
|
|
49
|
+
} else if (-halfPi < minY && maxY < halfPi) {
|
|
50
|
+
this.rotationOrder = InternalEulerRotationOrder.ZYX
|
|
51
|
+
} else {
|
|
52
|
+
this.rotationOrder = InternalEulerRotationOrder.XZY
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Determine solve axis optimization
|
|
56
|
+
if (minX === 0 && maxX === 0 && minY === 0 && maxY === 0 && minZ === 0 && maxZ === 0) {
|
|
57
|
+
this.solveAxis = InternalSolveAxis.Fixed
|
|
58
|
+
} else if (minY === 0 && maxY === 0 && minZ === 0 && maxZ === 0) {
|
|
59
|
+
this.solveAxis = InternalSolveAxis.X
|
|
60
|
+
} else if (minX === 0 && maxX === 0 && minZ === 0 && maxZ === 0) {
|
|
61
|
+
this.solveAxis = InternalSolveAxis.Y
|
|
62
|
+
} else if (minX === 0 && maxX === 0 && minY === 0 && maxY === 0) {
|
|
63
|
+
this.solveAxis = InternalSolveAxis.Z
|
|
64
|
+
} else {
|
|
65
|
+
this.solveAxis = InternalSolveAxis.None
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
this.minimumAngle = null
|
|
69
|
+
this.maximumAngle = null
|
|
70
|
+
this.rotationOrder = InternalEulerRotationOrder.XZY // not used
|
|
71
|
+
this.solveAxis = InternalSolveAxis.None
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Solve IK chains for a model
|
|
78
|
+
*/
|
|
79
|
+
export class IKSolverSystem {
|
|
80
|
+
private static readonly EPSILON = 1.0e-8
|
|
81
|
+
private static readonly THRESHOLD = (88 * Math.PI) / 180
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Solve all IK chains
|
|
85
|
+
*/
|
|
86
|
+
public static solve(
|
|
87
|
+
ikSolvers: IKSolver[],
|
|
88
|
+
bones: Bone[],
|
|
89
|
+
localRotations: Float32Array,
|
|
90
|
+
localTranslations: Float32Array,
|
|
91
|
+
worldMatrices: Float32Array,
|
|
92
|
+
ikChainInfo: IKChainInfo[],
|
|
93
|
+
usePhysics: boolean = false
|
|
94
|
+
): void {
|
|
95
|
+
for (const solver of ikSolvers) {
|
|
96
|
+
if (usePhysics && solver.canSkipWhenPhysicsEnabled) {
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
this.solveIK(solver, bones, localRotations, localTranslations, worldMatrices, ikChainInfo)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private static solveIK(
|
|
104
|
+
solver: IKSolver,
|
|
105
|
+
bones: Bone[],
|
|
106
|
+
localRotations: Float32Array,
|
|
107
|
+
localTranslations: Float32Array,
|
|
108
|
+
worldMatrices: Float32Array,
|
|
109
|
+
ikChainInfo: IKChainInfo[]
|
|
110
|
+
): void {
|
|
111
|
+
if (solver.links.length === 0) return
|
|
112
|
+
|
|
113
|
+
const ikBoneIndex = solver.ikBoneIndex
|
|
114
|
+
const targetBoneIndex = solver.targetBoneIndex
|
|
115
|
+
|
|
116
|
+
// Reset IK rotations
|
|
117
|
+
for (const link of solver.links) {
|
|
118
|
+
const chainInfo = ikChainInfo[link.boneIndex]
|
|
119
|
+
if (chainInfo) {
|
|
120
|
+
chainInfo.ikRotation = new Quat(0, 0, 0, 1)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Get IK bone and target positions
|
|
125
|
+
const ikPosition = this.getWorldTranslation(ikBoneIndex, worldMatrices)
|
|
126
|
+
const targetPosition = this.getWorldTranslation(targetBoneIndex, worldMatrices)
|
|
127
|
+
|
|
128
|
+
if (ikPosition.subtract(targetPosition).length() < this.EPSILON) return
|
|
129
|
+
|
|
130
|
+
// Build IK chains
|
|
131
|
+
const chains: IKChain[] = []
|
|
132
|
+
for (const link of solver.links) {
|
|
133
|
+
chains.push(new IKChain(link.boneIndex, link))
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Update chain bones and target bone world matrices (initial state, no IK yet)
|
|
137
|
+
for (let i = chains.length - 1; i >= 0; i--) {
|
|
138
|
+
this.updateWorldMatrix(chains[i].boneIndex, bones, localRotations, localTranslations, worldMatrices)
|
|
139
|
+
}
|
|
140
|
+
this.updateWorldMatrix(targetBoneIndex, bones, localRotations, localTranslations, worldMatrices)
|
|
141
|
+
|
|
142
|
+
// Re-read positions after initial update
|
|
143
|
+
const updatedIkPosition = this.getWorldTranslation(ikBoneIndex, worldMatrices)
|
|
144
|
+
const updatedTargetPosition = this.getWorldTranslation(targetBoneIndex, worldMatrices)
|
|
145
|
+
|
|
146
|
+
if (updatedIkPosition.subtract(updatedTargetPosition).length() < this.EPSILON) return
|
|
147
|
+
|
|
148
|
+
// Solve iteratively
|
|
149
|
+
const iteration = Math.min(solver.iterationCount, 256)
|
|
150
|
+
const halfIteration = iteration >> 1
|
|
151
|
+
|
|
152
|
+
for (let i = 0; i < iteration; i++) {
|
|
153
|
+
for (let chainIndex = 0; chainIndex < chains.length; chainIndex++) {
|
|
154
|
+
const chain = chains[chainIndex]
|
|
155
|
+
if (chain.solveAxis !== InternalSolveAxis.Fixed) {
|
|
156
|
+
this.solveChain(
|
|
157
|
+
chain,
|
|
158
|
+
chainIndex,
|
|
159
|
+
solver,
|
|
160
|
+
ikBoneIndex,
|
|
161
|
+
targetBoneIndex,
|
|
162
|
+
bones,
|
|
163
|
+
localRotations,
|
|
164
|
+
localTranslations,
|
|
165
|
+
worldMatrices,
|
|
166
|
+
ikChainInfo,
|
|
167
|
+
i < halfIteration
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Re-read positions after this iteration
|
|
173
|
+
const currentIkPosition = this.getWorldTranslation(ikBoneIndex, worldMatrices)
|
|
174
|
+
const currentTargetPosition = this.getWorldTranslation(targetBoneIndex, worldMatrices)
|
|
175
|
+
const distance = currentIkPosition.subtract(currentTargetPosition).length()
|
|
176
|
+
if (distance < this.EPSILON) break
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Apply IK rotations to local rotations
|
|
180
|
+
for (const link of solver.links) {
|
|
181
|
+
const chainInfo = ikChainInfo[link.boneIndex]
|
|
182
|
+
if (chainInfo && chainInfo.ikRotation) {
|
|
183
|
+
const qi = link.boneIndex * 4
|
|
184
|
+
const localRot = new Quat(
|
|
185
|
+
localRotations[qi],
|
|
186
|
+
localRotations[qi + 1],
|
|
187
|
+
localRotations[qi + 2],
|
|
188
|
+
localRotations[qi + 3]
|
|
189
|
+
)
|
|
190
|
+
const finalRot = chainInfo.ikRotation.multiply(localRot).normalize()
|
|
191
|
+
localRotations[qi] = finalRot.x
|
|
192
|
+
localRotations[qi + 1] = finalRot.y
|
|
193
|
+
localRotations[qi + 2] = finalRot.z
|
|
194
|
+
localRotations[qi + 3] = finalRot.w
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private static solveChain(
|
|
200
|
+
chain: IKChain,
|
|
201
|
+
chainIndex: number,
|
|
202
|
+
solver: IKSolver,
|
|
203
|
+
ikBoneIndex: number,
|
|
204
|
+
targetBoneIndex: number,
|
|
205
|
+
bones: Bone[],
|
|
206
|
+
localRotations: Float32Array,
|
|
207
|
+
localTranslations: Float32Array,
|
|
208
|
+
worldMatrices: Float32Array,
|
|
209
|
+
ikChainInfo: IKChainInfo[],
|
|
210
|
+
useAxis: boolean
|
|
211
|
+
): void {
|
|
212
|
+
const chainBoneIndex = chain.boneIndex
|
|
213
|
+
const chainPosition = this.getWorldTranslation(chainBoneIndex, worldMatrices)
|
|
214
|
+
const ikPosition = this.getWorldTranslation(ikBoneIndex, worldMatrices)
|
|
215
|
+
const targetPosition = this.getWorldTranslation(targetBoneIndex, worldMatrices)
|
|
216
|
+
|
|
217
|
+
const chainTargetVector = chainPosition.subtract(targetPosition).normalize()
|
|
218
|
+
const chainIkVector = chainPosition.subtract(ikPosition).normalize()
|
|
219
|
+
|
|
220
|
+
const chainRotationAxis = chainTargetVector.cross(chainIkVector)
|
|
221
|
+
if (chainRotationAxis.length() < this.EPSILON) return
|
|
222
|
+
|
|
223
|
+
// Get parent's world rotation matrix (translation removed)
|
|
224
|
+
const parentWorldRotMatrix = this.getParentWorldRotationMatrix(chainBoneIndex, bones, worldMatrices)
|
|
225
|
+
|
|
226
|
+
let finalRotationAxis: Vec3
|
|
227
|
+
if (chain.minimumAngle !== null && useAxis) {
|
|
228
|
+
switch (chain.solveAxis) {
|
|
229
|
+
case InternalSolveAxis.None: {
|
|
230
|
+
const invParentRot = parentWorldRotMatrix.inverse()
|
|
231
|
+
finalRotationAxis = this.transformNormal(chainRotationAxis, invParentRot).normalize()
|
|
232
|
+
break
|
|
233
|
+
}
|
|
234
|
+
case InternalSolveAxis.X: {
|
|
235
|
+
const m = parentWorldRotMatrix.values
|
|
236
|
+
const axisX = new Vec3(m[0], m[1], m[2])
|
|
237
|
+
const dot = chainRotationAxis.dot(axisX)
|
|
238
|
+
finalRotationAxis = new Vec3(dot >= 0 ? 1 : -1, 0, 0)
|
|
239
|
+
break
|
|
240
|
+
}
|
|
241
|
+
case InternalSolveAxis.Y: {
|
|
242
|
+
const m = parentWorldRotMatrix.values
|
|
243
|
+
const axisY = new Vec3(m[4], m[5], m[6])
|
|
244
|
+
const dot = chainRotationAxis.dot(axisY)
|
|
245
|
+
finalRotationAxis = new Vec3(0, dot >= 0 ? 1 : -1, 0)
|
|
246
|
+
break
|
|
247
|
+
}
|
|
248
|
+
case InternalSolveAxis.Z: {
|
|
249
|
+
const m = parentWorldRotMatrix.values
|
|
250
|
+
const axisZ = new Vec3(m[8], m[9], m[10])
|
|
251
|
+
const dot = chainRotationAxis.dot(axisZ)
|
|
252
|
+
finalRotationAxis = new Vec3(0, 0, dot >= 0 ? 1 : -1)
|
|
253
|
+
break
|
|
254
|
+
}
|
|
255
|
+
default:
|
|
256
|
+
finalRotationAxis = chainRotationAxis
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
const invParentRot = parentWorldRotMatrix.inverse()
|
|
260
|
+
finalRotationAxis = this.transformNormal(chainRotationAxis, invParentRot).normalize()
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
let dot = chainTargetVector.dot(chainIkVector)
|
|
264
|
+
dot = Math.max(-1.0, Math.min(1.0, dot))
|
|
265
|
+
|
|
266
|
+
const angle = Math.min(solver.limitAngle * (chainIndex + 1), Math.acos(dot))
|
|
267
|
+
const ikRotation = Quat.fromAxisAngle(finalRotationAxis, angle)
|
|
268
|
+
|
|
269
|
+
const chainInfo = ikChainInfo[chainBoneIndex]
|
|
270
|
+
if (chainInfo) {
|
|
271
|
+
chainInfo.ikRotation = ikRotation.multiply(chainInfo.ikRotation)
|
|
272
|
+
|
|
273
|
+
// Apply angle constraints if present
|
|
274
|
+
if (chain.minimumAngle !== null && chain.maximumAngle !== null) {
|
|
275
|
+
const qi = chainBoneIndex * 4
|
|
276
|
+
const localRot = new Quat(
|
|
277
|
+
localRotations[qi],
|
|
278
|
+
localRotations[qi + 1],
|
|
279
|
+
localRotations[qi + 2],
|
|
280
|
+
localRotations[qi + 3]
|
|
281
|
+
)
|
|
282
|
+
chainInfo.localRotation = localRot.clone()
|
|
283
|
+
|
|
284
|
+
const combinedRot = chainInfo.ikRotation.multiply(localRot)
|
|
285
|
+
const rotMatrix = Mat4.fromQuat(combinedRot.x, combinedRot.y, combinedRot.z, combinedRot.w)
|
|
286
|
+
const m = rotMatrix.values
|
|
287
|
+
|
|
288
|
+
let rX: number, rY: number, rZ: number
|
|
289
|
+
|
|
290
|
+
switch (chain.rotationOrder) {
|
|
291
|
+
case InternalEulerRotationOrder.YXZ: {
|
|
292
|
+
rX = Math.asin(-m[9]) // m32
|
|
293
|
+
if (Math.abs(rX) > this.THRESHOLD) {
|
|
294
|
+
rX = rX < 0 ? -this.THRESHOLD : this.THRESHOLD
|
|
295
|
+
}
|
|
296
|
+
let cosX = Math.cos(rX)
|
|
297
|
+
if (cosX !== 0) cosX = 1 / cosX
|
|
298
|
+
rY = Math.atan2(m[8] * cosX, m[10] * cosX) // m31, m33
|
|
299
|
+
rZ = Math.atan2(m[1] * cosX, m[5] * cosX) // m12, m22
|
|
300
|
+
|
|
301
|
+
rX = this.limitAngle(rX, chain.minimumAngle.x, chain.maximumAngle.x, useAxis)
|
|
302
|
+
rY = this.limitAngle(rY, chain.minimumAngle.y, chain.maximumAngle.y, useAxis)
|
|
303
|
+
rZ = this.limitAngle(rZ, chain.minimumAngle.z, chain.maximumAngle.z, useAxis)
|
|
304
|
+
|
|
305
|
+
chainInfo.ikRotation = Quat.fromAxisAngle(new Vec3(0, 1, 0), rY)
|
|
306
|
+
chainInfo.ikRotation = chainInfo.ikRotation.multiply(Quat.fromAxisAngle(new Vec3(1, 0, 0), rX))
|
|
307
|
+
chainInfo.ikRotation = chainInfo.ikRotation.multiply(Quat.fromAxisAngle(new Vec3(0, 0, 1), rZ))
|
|
308
|
+
break
|
|
309
|
+
}
|
|
310
|
+
case InternalEulerRotationOrder.ZYX: {
|
|
311
|
+
rY = Math.asin(-m[2]) // m13
|
|
312
|
+
if (Math.abs(rY) > this.THRESHOLD) {
|
|
313
|
+
rY = rY < 0 ? -this.THRESHOLD : this.THRESHOLD
|
|
314
|
+
}
|
|
315
|
+
let cosY = Math.cos(rY)
|
|
316
|
+
if (cosY !== 0) cosY = 1 / cosY
|
|
317
|
+
rX = Math.atan2(m[6] * cosY, m[10] * cosY) // m23, m33
|
|
318
|
+
rZ = Math.atan2(m[1] * cosY, m[0] * cosY) // m12, m11
|
|
319
|
+
|
|
320
|
+
rX = this.limitAngle(rX, chain.minimumAngle.x, chain.maximumAngle.x, useAxis)
|
|
321
|
+
rY = this.limitAngle(rY, chain.minimumAngle.y, chain.maximumAngle.y, useAxis)
|
|
322
|
+
rZ = this.limitAngle(rZ, chain.minimumAngle.z, chain.maximumAngle.z, useAxis)
|
|
323
|
+
|
|
324
|
+
chainInfo.ikRotation = Quat.fromAxisAngle(new Vec3(0, 0, 1), rZ)
|
|
325
|
+
chainInfo.ikRotation = chainInfo.ikRotation.multiply(Quat.fromAxisAngle(new Vec3(0, 1, 0), rY))
|
|
326
|
+
chainInfo.ikRotation = chainInfo.ikRotation.multiply(Quat.fromAxisAngle(new Vec3(1, 0, 0), rX))
|
|
327
|
+
break
|
|
328
|
+
}
|
|
329
|
+
case InternalEulerRotationOrder.XZY: {
|
|
330
|
+
rZ = Math.asin(-m[4]) // m21
|
|
331
|
+
if (Math.abs(rZ) > this.THRESHOLD) {
|
|
332
|
+
rZ = rZ < 0 ? -this.THRESHOLD : this.THRESHOLD
|
|
333
|
+
}
|
|
334
|
+
let cosZ = Math.cos(rZ)
|
|
335
|
+
if (cosZ !== 0) cosZ = 1 / cosZ
|
|
336
|
+
rX = Math.atan2(m[6] * cosZ, m[5] * cosZ) // m23, m22
|
|
337
|
+
rY = Math.atan2(m[8] * cosZ, m[0] * cosZ) // m31, m11
|
|
338
|
+
|
|
339
|
+
rX = this.limitAngle(rX, chain.minimumAngle.x, chain.maximumAngle.x, useAxis)
|
|
340
|
+
rY = this.limitAngle(rY, chain.minimumAngle.y, chain.maximumAngle.y, useAxis)
|
|
341
|
+
rZ = this.limitAngle(rZ, chain.minimumAngle.z, chain.maximumAngle.z, useAxis)
|
|
342
|
+
|
|
343
|
+
chainInfo.ikRotation = Quat.fromAxisAngle(new Vec3(1, 0, 0), rX)
|
|
344
|
+
chainInfo.ikRotation = chainInfo.ikRotation.multiply(Quat.fromAxisAngle(new Vec3(0, 0, 1), rZ))
|
|
345
|
+
chainInfo.ikRotation = chainInfo.ikRotation.multiply(Quat.fromAxisAngle(new Vec3(0, 1, 0), rY))
|
|
346
|
+
break
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const invertedLocalRotation = localRot.conjugate().normalize()
|
|
351
|
+
chainInfo.ikRotation = chainInfo.ikRotation.multiply(invertedLocalRotation)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Update world matrices for affected bones (using IK-modified rotations)
|
|
356
|
+
for (let i = chainIndex; i >= 0; i--) {
|
|
357
|
+
const link = solver.links[i]
|
|
358
|
+
this.updateWorldMatrixWithIK(link.boneIndex, bones, localRotations, localTranslations, worldMatrices, ikChainInfo)
|
|
359
|
+
}
|
|
360
|
+
this.updateWorldMatrix(targetBoneIndex, bones, localRotations, localTranslations, worldMatrices)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private static limitAngle(angle: number, min: number, max: number, useAxis: boolean): number {
|
|
364
|
+
if (angle < min) {
|
|
365
|
+
const diff = 2 * min - angle
|
|
366
|
+
return diff <= max && useAxis ? diff : min
|
|
367
|
+
} else if (angle > max) {
|
|
368
|
+
const diff = 2 * max - angle
|
|
369
|
+
return diff >= min && useAxis ? diff : max
|
|
370
|
+
} else {
|
|
371
|
+
return angle
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private static getWorldTranslation(boneIndex: number, worldMatrices: Float32Array): Vec3 {
|
|
376
|
+
const offset = boneIndex * 16
|
|
377
|
+
return new Vec3(worldMatrices[offset + 12], worldMatrices[offset + 13], worldMatrices[offset + 14])
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private static getParentWorldRotationMatrix(boneIndex: number, bones: Bone[], worldMatrices: Float32Array): Mat4 {
|
|
381
|
+
const bone = bones[boneIndex]
|
|
382
|
+
if (bone.parentIndex >= 0) {
|
|
383
|
+
const parentOffset = bone.parentIndex * 16
|
|
384
|
+
const parentMat = new Mat4(worldMatrices.subarray(parentOffset, parentOffset + 16))
|
|
385
|
+
// Remove translation
|
|
386
|
+
const rotMat = Mat4.identity()
|
|
387
|
+
const m = parentMat.values
|
|
388
|
+
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])
|
|
389
|
+
return rotMat
|
|
390
|
+
} else {
|
|
391
|
+
return Mat4.identity()
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
private static transformNormal(normal: Vec3, matrix: Mat4): Vec3 {
|
|
396
|
+
const m = matrix.values
|
|
397
|
+
return new Vec3(
|
|
398
|
+
m[0] * normal.x + m[4] * normal.y + m[8] * normal.z,
|
|
399
|
+
m[1] * normal.x + m[5] * normal.y + m[9] * normal.z,
|
|
400
|
+
m[2] * normal.x + m[6] * normal.y + m[10] * normal.z
|
|
401
|
+
)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private static updateWorldMatrixWithIK(
|
|
405
|
+
boneIndex: number,
|
|
406
|
+
bones: Bone[],
|
|
407
|
+
localRotations: Float32Array,
|
|
408
|
+
localTranslations: Float32Array,
|
|
409
|
+
worldMatrices: Float32Array,
|
|
410
|
+
ikChainInfo: IKChainInfo[]
|
|
411
|
+
): void {
|
|
412
|
+
const bone = bones[boneIndex]
|
|
413
|
+
const qi = boneIndex * 4
|
|
414
|
+
const ti = boneIndex * 3
|
|
415
|
+
|
|
416
|
+
// Use IK-modified rotation if available
|
|
417
|
+
const localRot = new Quat(
|
|
418
|
+
localRotations[qi],
|
|
419
|
+
localRotations[qi + 1],
|
|
420
|
+
localRotations[qi + 2],
|
|
421
|
+
localRotations[qi + 3]
|
|
422
|
+
)
|
|
423
|
+
const chainInfo = ikChainInfo[boneIndex]
|
|
424
|
+
let finalRot = localRot
|
|
425
|
+
if (chainInfo && chainInfo.ikRotation) {
|
|
426
|
+
finalRot = chainInfo.ikRotation.multiply(localRot).normalize()
|
|
427
|
+
}
|
|
428
|
+
const rotateM = Mat4.fromQuat(finalRot.x, finalRot.y, finalRot.z, finalRot.w)
|
|
429
|
+
|
|
430
|
+
const localTx = localTranslations[ti]
|
|
431
|
+
const localTy = localTranslations[ti + 1]
|
|
432
|
+
const localTz = localTranslations[ti + 2]
|
|
433
|
+
|
|
434
|
+
const localM = Mat4.identity()
|
|
435
|
+
.translateInPlace(bone.bindTranslation[0], bone.bindTranslation[1], bone.bindTranslation[2])
|
|
436
|
+
.multiply(rotateM)
|
|
437
|
+
.translateInPlace(localTx, localTy, localTz)
|
|
438
|
+
|
|
439
|
+
const worldOffset = boneIndex * 16
|
|
440
|
+
if (bone.parentIndex >= 0) {
|
|
441
|
+
const parentOffset = bone.parentIndex * 16
|
|
442
|
+
const parentMat = new Mat4(worldMatrices.subarray(parentOffset, parentOffset + 16))
|
|
443
|
+
const worldMat = parentMat.multiply(localM)
|
|
444
|
+
worldMatrices.subarray(worldOffset, worldOffset + 16).set(worldMat.values)
|
|
445
|
+
} else {
|
|
446
|
+
worldMatrices.subarray(worldOffset, worldOffset + 16).set(localM.values)
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
private static updateWorldMatrix(
|
|
451
|
+
boneIndex: number,
|
|
452
|
+
bones: Bone[],
|
|
453
|
+
localRotations: Float32Array,
|
|
454
|
+
localTranslations: Float32Array,
|
|
455
|
+
worldMatrices: Float32Array
|
|
456
|
+
): void {
|
|
457
|
+
const bone = bones[boneIndex]
|
|
458
|
+
const qi = boneIndex * 4
|
|
459
|
+
const ti = boneIndex * 3
|
|
460
|
+
|
|
461
|
+
const localRot = new Quat(
|
|
462
|
+
localRotations[qi],
|
|
463
|
+
localRotations[qi + 1],
|
|
464
|
+
localRotations[qi + 2],
|
|
465
|
+
localRotations[qi + 3]
|
|
466
|
+
)
|
|
467
|
+
const rotateM = Mat4.fromQuat(localRot.x, localRot.y, localRot.z, localRot.w)
|
|
468
|
+
|
|
469
|
+
const localTx = localTranslations[ti]
|
|
470
|
+
const localTy = localTranslations[ti + 1]
|
|
471
|
+
const localTz = localTranslations[ti + 2]
|
|
472
|
+
|
|
473
|
+
const localM = Mat4.identity()
|
|
474
|
+
.translateInPlace(bone.bindTranslation[0], bone.bindTranslation[1], bone.bindTranslation[2])
|
|
475
|
+
.multiply(rotateM)
|
|
476
|
+
.translateInPlace(localTx, localTy, localTz)
|
|
477
|
+
|
|
478
|
+
const worldOffset = boneIndex * 16
|
|
479
|
+
if (bone.parentIndex >= 0) {
|
|
480
|
+
const parentOffset = bone.parentIndex * 16
|
|
481
|
+
const parentMat = new Mat4(worldMatrices.subarray(parentOffset, parentOffset + 16))
|
|
482
|
+
const worldMat = parentMat.multiply(localM)
|
|
483
|
+
worldMatrices.subarray(worldOffset, worldOffset + 16).set(worldMat.values)
|
|
484
|
+
} else {
|
|
485
|
+
worldMatrices.subarray(worldOffset, worldOffset + 16).set(localM.values)
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|