reze-engine 0.3.9 → 0.3.11

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
@@ -86,9 +86,9 @@ export class IKSolverSystem {
86
86
  public static solve(
87
87
  ikSolvers: IKSolver[],
88
88
  bones: Bone[],
89
- localRotations: Float32Array,
90
- localTranslations: Float32Array,
91
- worldMatrices: Float32Array,
89
+ localRotations: Quat[],
90
+ localTranslations: Vec3[],
91
+ worldMatrices: Mat4[],
92
92
  ikChainInfo: IKChainInfo[]
93
93
  ): void {
94
94
  for (const solver of ikSolvers) {
@@ -99,9 +99,9 @@ export class IKSolverSystem {
99
99
  private static solveIK(
100
100
  solver: IKSolver,
101
101
  bones: Bone[],
102
- localRotations: Float32Array,
103
- localTranslations: Float32Array,
104
- worldMatrices: Float32Array,
102
+ localRotations: Quat[],
103
+ localTranslations: Vec3[],
104
+ worldMatrices: Mat4[],
105
105
  ikChainInfo: IKChainInfo[]
106
106
  ): void {
107
107
  if (solver.links.length === 0) return
@@ -164,10 +164,9 @@ export class IKSolverSystem {
164
164
  for (const link of solver.links) {
165
165
  const chainInfo = ikChainInfo[link.boneIndex]
166
166
  if (chainInfo?.ikRotation) {
167
- const qi = link.boneIndex * 4
168
- const localRot = this.getQuatFromArray(localRotations, qi)
167
+ const localRot = localRotations[link.boneIndex]
169
168
  const finalRot = chainInfo.ikRotation.multiply(localRot).normalize()
170
- this.setQuatToArray(localRotations, qi, finalRot)
169
+ localRot.set(finalRot)
171
170
  }
172
171
  }
173
172
  }
@@ -179,9 +178,9 @@ export class IKSolverSystem {
179
178
  ikBoneIndex: number,
180
179
  targetBoneIndex: number,
181
180
  bones: Bone[],
182
- localRotations: Float32Array,
183
- localTranslations: Float32Array,
184
- worldMatrices: Float32Array,
181
+ localRotations: Quat[],
182
+ localTranslations: Vec3[],
183
+ worldMatrices: Mat4[],
185
184
  ikChainInfo: IKChainInfo[],
186
185
  useAxis: boolean
187
186
  ): void {
@@ -243,15 +242,15 @@ export class IKSolverSystem {
243
242
 
244
243
  // Apply angle constraints if present
245
244
  if (chain.minimumAngle && chain.maximumAngle) {
246
- const qi = chainBoneIndex * 4
247
- const localRot = this.getQuatFromArray(localRotations, qi)
245
+ const localRot = localRotations[chainBoneIndex]
248
246
  chainInfo.localRotation = localRot.clone()
249
247
 
250
248
  const combinedRot = chainInfo.ikRotation.multiply(localRot)
251
249
  const euler = this.extractEulerAngles(combinedRot, chain.rotationOrder)
252
250
  const limited = this.limitEulerAngles(euler, chain.minimumAngle, chain.maximumAngle, useAxis)
253
251
  chainInfo.ikRotation = this.reconstructQuatFromEuler(limited, chain.rotationOrder)
254
- chainInfo.ikRotation = chainInfo.ikRotation.multiply(localRot.conjugate().normalize())
252
+ // Clone localRot to avoid mutating, then conjugate and normalize
253
+ chainInfo.ikRotation = chainInfo.ikRotation.multiply(localRot.clone().conjugate().normalize())
255
254
  }
256
255
  }
257
256
 
@@ -275,26 +274,15 @@ export class IKSolverSystem {
275
274
  }
276
275
  }
277
276
 
278
- private static getDistance(boneIndex1: number, boneIndex2: number, worldMatrices: Float32Array): number {
277
+ private static getDistance(boneIndex1: number, boneIndex2: number, worldMatrices: Mat4[]): number {
279
278
  const pos1 = this.getWorldTranslation(boneIndex1, worldMatrices)
280
279
  const pos2 = this.getWorldTranslation(boneIndex2, worldMatrices)
281
280
  return pos1.subtract(pos2).length()
282
281
  }
283
282
 
284
- private static getWorldTranslation(boneIndex: number, worldMatrices: Float32Array): Vec3 {
285
- const offset = boneIndex * 16
286
- return new Vec3(worldMatrices[offset + 12], worldMatrices[offset + 13], worldMatrices[offset + 14])
287
- }
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
283
+ private static getWorldTranslation(boneIndex: number, worldMatrices: Mat4[]): Vec3 {
284
+ const mat = worldMatrices[boneIndex]
285
+ return new Vec3(mat.values[12], mat.values[13], mat.values[14])
298
286
  }
299
287
 
300
288
  private static extractEulerAngles(quat: Quat, order: InternalEulerRotationOrder): Vec3 {
@@ -361,11 +349,10 @@ export class IKSolverSystem {
361
349
  return result
362
350
  }
363
351
 
364
- private static getParentWorldRotationMatrix(boneIndex: number, bones: Bone[], worldMatrices: Float32Array): Mat4 {
352
+ private static getParentWorldRotationMatrix(boneIndex: number, bones: Bone[], worldMatrices: Mat4[]): Mat4 {
365
353
  const bone = bones[boneIndex]
366
354
  if (bone.parentIndex >= 0) {
367
- const parentOffset = bone.parentIndex * 16
368
- const parentMat = new Mat4(worldMatrices.subarray(parentOffset, parentOffset + 16))
355
+ const parentMat = worldMatrices[bone.parentIndex]
369
356
  // Remove translation
370
357
  const rotMat = Mat4.identity()
371
358
  const m = parentMat.values
@@ -388,16 +375,14 @@ export class IKSolverSystem {
388
375
  private static updateWorldMatrix(
389
376
  boneIndex: number,
390
377
  bones: Bone[],
391
- localRotations: Float32Array,
392
- localTranslations: Float32Array,
393
- worldMatrices: Float32Array,
378
+ localRotations: Quat[],
379
+ localTranslations: Vec3[],
380
+ worldMatrices: Mat4[],
394
381
  ikChainInfo?: IKChainInfo[]
395
382
  ): void {
396
383
  const bone = bones[boneIndex]
397
- const qi = boneIndex * 4
398
- const ti = boneIndex * 3
399
-
400
- const localRot = this.getQuatFromArray(localRotations, qi)
384
+ const localRot = localRotations[boneIndex]
385
+ const localTrans = localTranslations[boneIndex]
401
386
 
402
387
  // Apply IK rotation if available
403
388
  let finalRot = localRot
@@ -409,23 +394,18 @@ export class IKSolverSystem {
409
394
  }
410
395
  const rotateM = Mat4.fromQuat(finalRot.x, finalRot.y, finalRot.z, finalRot.w)
411
396
 
412
- const localTx = localTranslations[ti]
413
- const localTy = localTranslations[ti + 1]
414
- const localTz = localTranslations[ti + 2]
415
-
416
397
  const localM = Mat4.identity()
417
398
  .translateInPlace(bone.bindTranslation[0], bone.bindTranslation[1], bone.bindTranslation[2])
418
399
  .multiply(rotateM)
419
- .translateInPlace(localTx, localTy, localTz)
400
+ .translateInPlace(localTrans.x, localTrans.y, localTrans.z)
420
401
 
421
- const worldOffset = boneIndex * 16
402
+ const worldMat = worldMatrices[boneIndex]
422
403
  if (bone.parentIndex >= 0) {
423
- const parentOffset = bone.parentIndex * 16
424
- const parentMat = new Mat4(worldMatrices.subarray(parentOffset, parentOffset + 16))
425
- const worldMat = parentMat.multiply(localM)
426
- worldMatrices.subarray(worldOffset, worldOffset + 16).set(worldMat.values)
404
+ const parentMat = worldMatrices[bone.parentIndex]
405
+ const result = parentMat.multiply(localM)
406
+ worldMat.values.set(result.values)
427
407
  } else {
428
- worldMatrices.subarray(worldOffset, worldOffset + 16).set(localM.values)
408
+ worldMat.values.set(localM.values)
429
409
  }
430
410
  }
431
411
  }
package/src/math.ts CHANGED
@@ -14,6 +14,10 @@ export class Vec3 {
14
14
  this.z = z
15
15
  }
16
16
 
17
+ static zeros(): Vec3 {
18
+ return new Vec3(0, 0, 0)
19
+ }
20
+
17
21
  add(other: Vec3): Vec3 {
18
22
  return new Vec3(this.x + other.x, this.y + other.y, this.z + other.z)
19
23
  }
@@ -26,10 +30,20 @@ export class Vec3 {
26
30
  return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z)
27
31
  }
28
32
 
33
+ // Normalize this vector in-place (mutates this object)
29
34
  normalize(): Vec3 {
30
35
  const len = this.length()
31
- if (len === 0) return new Vec3(0, 0, 0)
32
- return new Vec3(this.x / len, this.y / len, this.z / len)
36
+ if (len === 0) {
37
+ this.x = 0
38
+ this.y = 0
39
+ this.z = 0
40
+ } else {
41
+ const invLen = 1 / len
42
+ this.x *= invLen
43
+ this.y *= invLen
44
+ this.z *= invLen
45
+ }
46
+ return this
33
47
  }
34
48
 
35
49
  cross(other: Vec3): Vec3 {
@@ -47,6 +61,14 @@ export class Vec3 {
47
61
  scale(scalar: number): Vec3 {
48
62
  return new Vec3(this.x * scalar, this.y * scalar, this.z * scalar)
49
63
  }
64
+
65
+ // Set this vector's components from another vector (in-place mutation)
66
+ set(other: Vec3): Vec3 {
67
+ this.x = other.x
68
+ this.y = other.y
69
+ this.z = other.z
70
+ return this
71
+ }
50
72
  }
51
73
 
52
74
  export class Quat {
@@ -62,6 +84,10 @@ export class Quat {
62
84
  this.w = w
63
85
  }
64
86
 
87
+ static identity(): Quat {
88
+ return new Quat(0, 0, 0, 1)
89
+ }
90
+
65
91
  add(other: Quat): Quat {
66
92
  return new Quat(this.x + other.x, this.y + other.y, this.z + other.z, this.w + other.w)
67
93
  }
@@ -80,34 +106,68 @@ export class Quat {
80
106
  )
81
107
  }
82
108
 
109
+ // Conjugate this quaternion in-place (mutates this object)
110
+ // Conjugate (inverse for unit quaternions): (x, y, z, w) -> (-x, -y, -z, w)
83
111
  conjugate(): Quat {
84
- // Conjugate (inverse for unit quaternions): (x, y, z, w) -> (-x, -y, -z, w)
85
- return new Quat(-this.x, -this.y, -this.z, this.w)
112
+ this.x = -this.x
113
+ this.y = -this.y
114
+ this.z = -this.z
115
+ return this
86
116
  }
87
117
 
88
118
  length(): number {
89
119
  return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w)
90
120
  }
91
121
 
122
+ // Normalize this quaternion in-place (mutates this object)
92
123
  normalize(): Quat {
93
124
  const len = this.length()
94
- if (len === 0) return new Quat(0, 0, 0, 1)
95
- return new Quat(this.x / len, this.y / len, this.z / len, this.w / len)
125
+ if (len === 0) {
126
+ this.x = 0
127
+ this.y = 0
128
+ this.z = 0
129
+ this.w = 1
130
+ } else {
131
+ const invLen = 1 / len
132
+ this.x *= invLen
133
+ this.y *= invLen
134
+ this.z *= invLen
135
+ this.w *= invLen
136
+ }
137
+ return this
96
138
  }
97
139
 
98
140
  // Static method: create quaternion from rotation axis and angle
99
141
  static fromAxisAngle(axis: Vec3, angle: number): Quat {
100
- const normalizedAxis = axis.normalize()
142
+ // Clone to avoid mutating input, then normalize
143
+ const nx = axis.x
144
+ const ny = axis.y
145
+ const nz = axis.z
146
+ const len = Math.sqrt(nx * nx + ny * ny + nz * nz)
147
+ const invLen = len > 0 ? 1 / len : 0
148
+ const normalizedX = nx * invLen
149
+ const normalizedY = ny * invLen
150
+ const normalizedZ = nz * invLen
151
+
101
152
  const halfAngle = angle * 0.5
102
153
  const sinHalf = Math.sin(halfAngle)
103
154
  const cosHalf = Math.cos(halfAngle)
104
- return new Quat(normalizedAxis.x * sinHalf, normalizedAxis.y * sinHalf, normalizedAxis.z * sinHalf, cosHalf)
155
+ return new Quat(normalizedX * sinHalf, normalizedY * sinHalf, normalizedZ * sinHalf, cosHalf)
105
156
  }
106
157
 
107
158
  toArray(): [number, number, number, number] {
108
159
  return [this.x, this.y, this.z, this.w]
109
160
  }
110
161
 
162
+ // Set this quaternion's components from another quaternion (in-place mutation)
163
+ set(other: Quat): Quat {
164
+ this.x = other.x
165
+ this.y = other.y
166
+ this.z = other.z
167
+ this.w = other.w
168
+ return this
169
+ }
170
+
111
171
  // Spherical linear interpolation between two quaternions
112
172
  static slerp(a: Quat, b: Quat, t: number): Quat {
113
173
  let cos = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w
@@ -205,9 +265,13 @@ export class Mat4 {
205
265
  // For left-handed: camera looks along +Z direction
206
266
  static lookAt(eye: Vec3, target: Vec3, up: Vec3): Mat4 {
207
267
  // In left-handed: forward = target - eye (Z+ direction)
208
- const forward = target.subtract(eye).normalize()
209
- const right = up.cross(forward).normalize() // X+ is right
210
- const upVec = forward.cross(right).normalize() // Y+ is up
268
+ // These operations create new Vec3 objects, so normalize() mutates those new objects
269
+ const forward = target.subtract(eye)
270
+ forward.normalize()
271
+ const right = up.cross(forward)
272
+ right.normalize() // X+ is right
273
+ const upVec = forward.cross(right)
274
+ upVec.normalize() // Y+ is up
211
275
 
212
276
  return new Mat4(
213
277
  new Float32Array([