reze-engine 0.27.4 → 0.28.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 +9 -9
- package/dist/ik-solver.d.ts +1 -1
- package/dist/ik-solver.d.ts.map +1 -1
- package/dist/ik-solver.js +4 -29
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/math.d.ts +26 -0
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +328 -0
- package/package.json +1 -1
- package/src/ik-solver.ts +5 -34
- package/src/index.ts +7 -2
- package/src/math.ts +345 -0
package/README.md
CHANGED
|
@@ -29,9 +29,9 @@ See [Physics](#physics) and [Rendering](#rendering) for the internals.
|
|
|
29
29
|
- [Reze Design](https://reze.design) (web-native scene composer & shader-graph styling)
|
|
30
30
|
- [Reze Studio](https://reze.studio) (MMD animation editor)
|
|
31
31
|
- [MiKaPo](https://mikapo.vercel.app) (motion capture)
|
|
32
|
+
- [Reze Rig](https://reze-rig.vercel.app) (FBX→VMD retarget)
|
|
32
33
|
- [Popo](https://popo.love) (LLM-generated poses)
|
|
33
34
|
- [MPL](https://mmd-mpl.vercel.app) (motion language)
|
|
34
|
-
- [Mixamo-MMD](https://mixamo-mmd.vercel.app) (FBX→VMD retarget)
|
|
35
35
|
|
|
36
36
|
## Quick start
|
|
37
37
|
|
|
@@ -252,14 +252,14 @@ const { ok, wgsl, diagnostics } = compileGraph(HAIR_GRAPH, {
|
|
|
252
252
|
|
|
253
253
|
The **built-in name hints**, checked top-to-bottom (first match wins), with the graph and pass-integration each category carries:
|
|
254
254
|
|
|
255
|
-
| Category | Graph · render-class / alpha | Matches a name containing (case-insensitive substring)
|
|
256
|
-
| -------------- | ---------------------------------- |
|
|
257
|
-
| `stockings` | `STOCKINGS_GRAPH` · `hashed` alpha | 靴下 · ソックス · タイツ · ニーソ · 袜 · stocking · socks · tights
|
|
258
|
-
| `eye` | `EYE_GRAPH` · `eye` | 白目 · 目影 · 二重 · 睫 · まつげ · まゆ · 眉 · 目 · 瞳 · 眼 · eye · iris · pupil · lash · brow
|
|
259
|
-
| `face` | `FACE_GRAPH` | 顔 · 颜 · 脸 · かお · face · 舌 · tongue · 牙 · 牙齿 · 歯 · teeth · tooth · 口腔 · 口内 · mouth · 嘴 · 歯茎 · gums
|
|
260
|
-
| `hair` | `HAIR_GRAPH` · `hair` | 前髪 · 後髪 · 髪 · 髮 · 头发 · 頭髪 · もみあげ · アホ毛 · ヘア · hair · ahoge · bang
|
|
261
|
-
| `body` | `BODY_GRAPH` | 肌 · 皮肤 · skin
|
|
262
|
-
| `metal` | `METAL_GRAPH` | 金属 · メタル · metal · earring · 耳环 · 耳環
|
|
255
|
+
| Category | Graph · render-class / alpha | Matches a name containing (case-insensitive substring) |
|
|
256
|
+
| -------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
257
|
+
| `stockings` | `STOCKINGS_GRAPH` · `hashed` alpha | 靴下 · ソックス · タイツ · ニーソ · 袜 · stocking · socks · tights |
|
|
258
|
+
| `eye` | `EYE_GRAPH` · `eye` | 白目 · 目影 · 二重 · 睫 · まつげ · まゆ · 眉 · 目 · 瞳 · 眼 · eye · iris · pupil · lash · brow |
|
|
259
|
+
| `face` | `FACE_GRAPH` | 顔 · 颜 · 脸 · かお · face · 舌 · tongue · 牙 · 牙齿 · 歯 · teeth · tooth · 口腔 · 口内 · mouth · 嘴 · 歯茎 · gums |
|
|
260
|
+
| `hair` | `HAIR_GRAPH` · `hair` | 前髪 · 後髪 · 髪 · 髮 · 头发 · 頭髪 · もみあげ · アホ毛 · ヘア · hair · ahoge · bang |
|
|
261
|
+
| `body` | `BODY_GRAPH` | 肌 · 皮肤 · skin |
|
|
262
|
+
| `metal` | `METAL_GRAPH` | 金属 · メタル · metal · earring · 耳环 · 耳環 |
|
|
263
263
|
| `cloth_smooth` | `CLOTH_SMOOTH_GRAPH` | 服 · 衣 · 裙 · 裤 · スカート · ワンピ · リボン · 袖 · 靴 · 鞋 · 帽 · 体 · 飾 · 饰 · 尾 · 套 · 腿 · skirt · dress · ribbon · sleeve · shoes · shirt · short · boot · hat · cloth · accessor · trigger |
|
|
264
264
|
|
|
265
265
|
`cloth_rough` and `default` have **no** name hints — a material reaches them only via an explicit `overrides` entry. The group `id` is the category name, so re-running `autoStyleGroups` is idempotent; its promise resolves after every graph compiles, so `getStyleGroups(model)` is ready the moment it resolves — seed your own store from it, then edit with `applyStyleGroups`.
|
package/dist/ik-solver.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export declare class IKSolverSystem {
|
|
|
9
9
|
private static solveChain;
|
|
10
10
|
private static limitAngle;
|
|
11
11
|
private static getDistance;
|
|
12
|
-
private static readonly
|
|
12
|
+
private static readonly EULER_ORDER_NAMES;
|
|
13
13
|
private static extractEulerAnglesInto;
|
|
14
14
|
private static limitEulerAnglesInto;
|
|
15
15
|
private static reconstructQuatFromEulerInto;
|
package/dist/ik-solver.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ik-solver.d.ts","sourceRoot":"","sources":["../src/ik-solver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,
|
|
1
|
+
{"version":3,"file":"ik-solver.d.ts","sourceRoot":"","sources":["../src/ik-solver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAmB,MAAM,QAAQ,CAAA;AAC1D,OAAO,EAAE,IAAI,EAAU,QAAQ,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAG7D,MAAM,MAAM,mBAAmB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;AAuF/E,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAS;IACxC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAuB;IAExD,MAAM,CAAC,KAAK,CACV,SAAS,EAAE,QAAQ,EAAE,EACrB,KAAK,EAAE,IAAI,EAAE,EACb,cAAc,EAAE,IAAI,EAAE,EACtB,iBAAiB,EAAE,IAAI,EAAE,EACzB,aAAa,EAAE,IAAI,EAAE,EACrB,WAAW,EAAE,WAAW,EAAE,EAC1B,iBAAiB,CAAC,EAAE,mBAAmB,GACtC,IAAI;IAMP,OAAO,CAAC,MAAM,CAAC,OAAO;IAqFtB,OAAO,CAAC,MAAM,CAAC,UAAU;IA4HzB,OAAO,CAAC,MAAM,CAAC,UAAU;IAYzB,OAAO,CAAC,MAAM,CAAC,WAAW;IAU1B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAA+C;IAExF,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAsCrC,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAMnC,OAAO,CAAC,MAAM,CAAC,4BAA4B;IAK3C,OAAO,CAAC,MAAM,CAAC,gCAAgC;IAkB/C,OAAO,CAAC,MAAM,CAAC,iBAAiB;CA2CjC"}
|
package/dist/ik-solver.js
CHANGED
|
@@ -71,7 +71,6 @@ const _ikVec = [
|
|
|
71
71
|
];
|
|
72
72
|
const _ikQuat = [
|
|
73
73
|
new Quat(0, 0, 0, 1), new Quat(0, 0, 0, 1), new Quat(0, 0, 0, 1),
|
|
74
|
-
new Quat(0, 0, 0, 1), new Quat(0, 0, 0, 1), new Quat(0, 0, 0, 1),
|
|
75
74
|
];
|
|
76
75
|
const _ikMat = [
|
|
77
76
|
new Float32Array(16), new Float32Array(16), new Float32Array(16), new Float32Array(16),
|
|
@@ -151,7 +150,7 @@ export class IKSolverSystem {
|
|
|
151
150
|
// _ikVec[0]=chainPos, [1]=ikPos, [2]=targetPos, [3]=chainTargetVec, [4]=chainIkVec,
|
|
152
151
|
// [5]=rotAxis, [6]=finalAxis, [7]=eulerTmp, [8]=limitedEuler
|
|
153
152
|
// _ikMat[0]=parentRot, [1]=invParentRot, [2]=quatToMatTmp
|
|
154
|
-
// _ikQuat[0]=ikRotation, [1]=combinedRot, [2]=localRotConj
|
|
153
|
+
// _ikQuat[0]=ikRotation, [1]=combinedRot, [2]=localRotConj
|
|
155
154
|
const chainPos = Vec3.setFromMat4Translation(worldMatrices[chainBoneIndex].values, _ikVec[0]);
|
|
156
155
|
const ikPos = Vec3.setFromMat4Translation(worldMatrices[ikBoneIndex].values, _ikVec[1]);
|
|
157
156
|
const targetPos = Vec3.setFromMat4Translation(worldMatrices[targetBoneIndex].values, _ikVec[2]);
|
|
@@ -317,23 +316,7 @@ export class IKSolverSystem {
|
|
|
317
316
|
out.z = this.limitAngle(euler.z, min.z, max.z, useAxis);
|
|
318
317
|
}
|
|
319
318
|
static reconstructQuatFromEulerInto(euler, order, out) {
|
|
320
|
-
|
|
321
|
-
const a1 = axes[0], a2 = axes[1], a3 = axes[2];
|
|
322
|
-
const ang1 = order === InternalEulerRotationOrder.YXZ ? euler.y
|
|
323
|
-
: order === InternalEulerRotationOrder.ZYX ? euler.z
|
|
324
|
-
: euler.x;
|
|
325
|
-
const ang2 = order === InternalEulerRotationOrder.YXZ ? euler.x
|
|
326
|
-
: order === InternalEulerRotationOrder.ZYX ? euler.y
|
|
327
|
-
: euler.z;
|
|
328
|
-
const ang3 = order === InternalEulerRotationOrder.YXZ ? euler.z
|
|
329
|
-
: order === InternalEulerRotationOrder.ZYX ? euler.x
|
|
330
|
-
: euler.y;
|
|
331
|
-
// result = axisAngle(a1, ang1); then *= axisAngle(a2, ang2); then *= axisAngle(a3, ang3)
|
|
332
|
-
Quat.fromAxisAngleInto(a1[0], a1[1], a1[2], ang1, out);
|
|
333
|
-
Quat.fromAxisAngleInto(a2[0], a2[1], a2[2], ang2, _ikQuat[3]);
|
|
334
|
-
Quat.multiplyInto(out, _ikQuat[3], out);
|
|
335
|
-
Quat.fromAxisAngleInto(a3[0], a3[1], a3[2], ang3, _ikQuat[3]);
|
|
336
|
-
Quat.multiplyInto(out, _ikQuat[3], out);
|
|
319
|
+
Quat.fromEulerOrderInto(euler.x, euler.y, euler.z, this.EULER_ORDER_NAMES[order], out);
|
|
337
320
|
}
|
|
338
321
|
// Write parent's world rotation (translation stripped) into out Float32Array.
|
|
339
322
|
static getParentWorldRotationMatrixInto(boneIndex, bones, worldMatrices, out) {
|
|
@@ -396,13 +379,5 @@ export class IKSolverSystem {
|
|
|
396
379
|
}
|
|
397
380
|
IKSolverSystem.EPSILON = 1.0e-8;
|
|
398
381
|
IKSolverSystem.THRESHOLD = (88 * Math.PI) / 180;
|
|
399
|
-
//
|
|
400
|
-
|
|
401
|
-
IKSolverSystem.EULER_AXES = [
|
|
402
|
-
// YXZ: Y, X, Z
|
|
403
|
-
[[0, 1, 0], [1, 0, 0], [0, 0, 1]],
|
|
404
|
-
// ZYX: Z, Y, X
|
|
405
|
-
[[0, 0, 1], [0, 1, 0], [1, 0, 0]],
|
|
406
|
-
// XZY: X, Z, Y
|
|
407
|
-
[[1, 0, 0], [0, 0, 1], [0, 1, 0]],
|
|
408
|
-
];
|
|
382
|
+
// Maps InternalEulerRotationOrder to the shared math-layer order names.
|
|
383
|
+
IKSolverSystem.EULER_ORDER_NAMES = ["YXZ", "ZYX", "XZY"];
|
package/dist/index.d.ts
CHANGED
|
@@ -15,9 +15,9 @@ export { STOCKINGS_GRAPH } from "./graph/presets/stockings";
|
|
|
15
15
|
export { EYE_GRAPH } from "./graph/presets/eye";
|
|
16
16
|
export { FACE_GRAPH } from "./graph/presets/face";
|
|
17
17
|
export { Model } from "./model";
|
|
18
|
-
export { Vec3, Quat, Mat4 } from "./math";
|
|
18
|
+
export { Vec3, Quat, Mat4, easeInOut, type EulerOrder } from "./math";
|
|
19
19
|
export type { AnimationClip, AnimationPlayOptions, AnimationProgress, BoneKeyframe, IkKeyframe, MorphKeyframe, BoneInterpolation, ControlPoint, } from "./animation";
|
|
20
|
-
export { FPS } from "./animation";
|
|
20
|
+
export { FPS, bezierInterpolate, interpolateControlPoints, rawInterpolationToBoneInterpolation, } from "./animation";
|
|
21
21
|
export { VMDLoader, type CameraKeyframe, type IkFrame } from "./vmd-loader";
|
|
22
22
|
export { CameraAnimation, type CameraPose } from "./camera-animation";
|
|
23
23
|
export { RezePhysics } from "./physics";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,0BAA0B,EAC/B,KAAK,sBAAsB,GAC5B,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACvG,OAAO,EACL,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,SAAS,GACf,MAAM,iBAAiB,CAAA;AACxB,YAAY,EACV,WAAW,EACX,SAAS,EACT,SAAS,EACT,YAAY,EACZ,WAAW,EACX,UAAU,GACX,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAC3E,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC7G,YAAY,EACV,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,0BAA0B,EAC/B,KAAK,sBAAsB,GAC5B,MAAM,UAAU,CAAA;AACjB,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACvG,OAAO,EACL,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,SAAS,GACf,MAAM,iBAAiB,CAAA;AACxB,YAAY,EACV,WAAW,EACX,SAAS,EACT,SAAS,EACT,YAAY,EACZ,WAAW,EACX,UAAU,GACX,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,aAAa,EAAE,KAAK,QAAQ,EAAE,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAC3E,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC7G,YAAY,EACV,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAA;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAA;AACrE,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,YAAY,GACb,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,GAAG,EACH,iBAAiB,EACjB,wBAAwB,EACxB,mCAAmC,GACpC,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3E,OAAO,EAAE,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -13,8 +13,8 @@ export { STOCKINGS_GRAPH } from "./graph/presets/stockings";
|
|
|
13
13
|
export { EYE_GRAPH } from "./graph/presets/eye";
|
|
14
14
|
export { FACE_GRAPH } from "./graph/presets/face";
|
|
15
15
|
export { Model } from "./model";
|
|
16
|
-
export { Vec3, Quat, Mat4 } from "./math";
|
|
17
|
-
export { FPS } from "./animation";
|
|
16
|
+
export { Vec3, Quat, Mat4, easeInOut } from "./math";
|
|
17
|
+
export { FPS, bezierInterpolate, interpolateControlPoints, rawInterpolationToBoneInterpolation, } from "./animation";
|
|
18
18
|
export { VMDLoader } from "./vmd-loader";
|
|
19
19
|
export { CameraAnimation } from "./camera-animation";
|
|
20
20
|
export { RezePhysics } from "./physics";
|
package/dist/math.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare function easeInOut(t: number): number;
|
|
2
|
+
export type EulerOrder = "XYZ" | "XZY" | "YXZ" | "YZX" | "ZXY" | "ZYX";
|
|
2
3
|
export declare class Vec3 {
|
|
3
4
|
x: number;
|
|
4
5
|
y: number;
|
|
@@ -18,6 +19,8 @@ export declare class Vec3 {
|
|
|
18
19
|
static crossInto(a: Vec3, b: Vec3, out: Vec3): Vec3;
|
|
19
20
|
static setFromMat4Translation(m: Float32Array, out: Vec3): Vec3;
|
|
20
21
|
static transformMat4RotationInto(normal: Vec3, m: Float32Array, out: Vec3): Vec3;
|
|
22
|
+
static mirrorZInto(v: Vec3, out: Vec3): Vec3;
|
|
23
|
+
static mirrorZ(v: Vec3): Vec3;
|
|
21
24
|
normalizeInPlace(): Vec3;
|
|
22
25
|
}
|
|
23
26
|
export declare class Quat {
|
|
@@ -43,6 +46,29 @@ export declare class Quat {
|
|
|
43
46
|
setXYZW(x: number, y: number, z: number, w: number): Quat;
|
|
44
47
|
setIdentity(): Quat;
|
|
45
48
|
static fromEuler(rotX: number, rotY: number, rotZ: number): Quat;
|
|
49
|
+
static dot(a: Quat, b: Quat): number;
|
|
50
|
+
static angleTo(a: Quat, b: Quat): number;
|
|
51
|
+
static conjugateInto(q: Quat, out: Quat): Quat;
|
|
52
|
+
static nlerpInto(a: Quat, b: Quat, t: number, out: Quat): Quat;
|
|
53
|
+
static nlerp(a: Quat, b: Quat, t: number): Quat;
|
|
54
|
+
static rotateVecInto(q: Quat, v: Vec3, out: Vec3): Vec3;
|
|
55
|
+
static rotateVec(q: Quat, v: Vec3): Vec3;
|
|
56
|
+
static rotateVecInvInto(q: Quat, v: Vec3, out: Vec3): Vec3;
|
|
57
|
+
static rotateVecInv(q: Quat, v: Vec3): Vec3;
|
|
58
|
+
static fromUnitVectorsInto(from: Vec3, to: Vec3, out: Quat): Quat;
|
|
59
|
+
static fromUnitVectors(from: Vec3, to: Vec3): Quat;
|
|
60
|
+
static fromBasisInto(x: Vec3, y: Vec3, z: Vec3, out: Quat): Quat;
|
|
61
|
+
static fromBasis(x: Vec3, y: Vec3, z: Vec3): Quat;
|
|
62
|
+
static twistAroundAxisInto(q: Quat, a: Vec3, out: Quat): Quat;
|
|
63
|
+
static twistAroundAxis(q: Quat, a: Vec3): Quat;
|
|
64
|
+
static lookRotationInto(forward: Vec3, up: Vec3, out: Quat): Quat;
|
|
65
|
+
static lookRotation(forward: Vec3, up: Vec3): Quat;
|
|
66
|
+
static mirrorZInto(q: Quat, out: Quat): Quat;
|
|
67
|
+
static mirrorZ(q: Quat): Quat;
|
|
68
|
+
static fromEulerOrderInto(x: number, y: number, z: number, order: EulerOrder, out: Quat): Quat;
|
|
69
|
+
static fromEulerOrder(x: number, y: number, z: number, order: EulerOrder): Quat;
|
|
70
|
+
static toEulerOrderInto(q: Quat, order: EulerOrder, out: Vec3): Vec3;
|
|
71
|
+
static toEulerOrder(q: Quat, order: EulerOrder): Vec3;
|
|
46
72
|
}
|
|
47
73
|
export declare class Mat4 {
|
|
48
74
|
values: Float32Array;
|
package/dist/math.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"math.d.ts","sourceRoot":"","sources":["../src/math.ts"],"names":[],"mappings":"AACA,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C;
|
|
1
|
+
{"version":3,"file":"math.d.ts","sourceRoot":"","sources":["../src/math.ts"],"names":[],"mappings":"AACA,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C;AAID,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAA;AAEtE,qBAAa,IAAI;IACf,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;gBAEG,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;IAM3C,MAAM,CAAC,KAAK,IAAI,IAAI;IAIpB,GAAG,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IAItB,QAAQ,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IAI3B,MAAM,IAAI,MAAM;IAKhB,SAAS,IAAI,IAAI;IAejB,KAAK,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IAQxB,GAAG,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM;IAIxB,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK3B,GAAG,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IAOtB,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAQ7C,MAAM,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAQtD,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAUnD,MAAM,CAAC,sBAAsB,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAS/D,MAAM,CAAC,yBAAyB,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAShF,MAAM,CAAC,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAO5C,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI;IAK7B,gBAAgB,IAAI,IAAI;CAGzB;AAED,qBAAa,IAAI;IACf,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;gBAEG,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;IAOtD,MAAM,CAAC,QAAQ,IAAI,IAAI;IAIvB,GAAG,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IAItB,KAAK,IAAI,IAAI;IAIb,QAAQ,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IAY3B,SAAS,IAAI,IAAI;IAOjB,MAAM,IAAI,MAAM;IAKhB,SAAS,IAAI,IAAI;IAkBjB,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAiBrD,OAAO,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;IAK3C,GAAG,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IAStB,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAqC/C,MAAM,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAWtD,MAAM,CAAC,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAc5F,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IA4B9D,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAKzD,WAAW,IAAI,IAAI;IAMnB,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAkBhE,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,MAAM;IAKpC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,MAAM;IAMxC,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAU9C,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAe9D,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAK/C,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAcvD,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,IAAI;IAKxC,MAAM,CAAC,gBAAgB,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAY1D,MAAM,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,IAAI;IAO3C,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAsBjE,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,GAAG,IAAI;IAMlD,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAsBhE,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,IAAI;IAOjD,MAAM,CAAC,mBAAmB,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAc7D,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,IAAI;IAM9C,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAgCjE,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,GAAG,IAAI;IAMlD,MAAM,CAAC,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAQ5C,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI;IAM7B,MAAM,CAAC,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IA6B9F,MAAM,CAAC,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;IAO/E,MAAM,CAAC,gBAAgB,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAwEpE,MAAM,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;CAGtD;AAED,qBAAa,IAAI;IACf,MAAM,EAAE,YAAY,CAAA;gBAER,MAAM,EAAE,YAAY;IAIhC,MAAM,CAAC,QAAQ,IAAI,IAAI;IAOvB,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IASvG,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAQhF,MAAM,CAAC,UAAU,CACf,GAAG,EAAE,YAAY,EACjB,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAClC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAClC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GACjC,IAAI;IAqBP,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,GAAG,IAAI;IAOtD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IA0BhH,QAAQ,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI;IAqB3B,MAAM,CAAC,cAAc,CACnB,CAAC,EAAE,YAAY,EACf,OAAO,EAAE,MAAM,EACf,CAAC,EAAE,YAAY,EACf,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,YAAY,EACjB,SAAS,EAAE,MAAM,GAChB,IAAI;IAiBP,KAAK,IAAI,IAAI;IAKb,MAAM,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,GAAE,MAAU,GAAG,IAAI;IA0B5G,MAAM,CAAC,kBAAkB,CACvB,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAClC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAC9C,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAClC,GAAG,EAAE,YAAY,GAChB,IAAI;IAkBP,MAAM,CAAC,wBAAwB,CAC7B,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAClC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAC9C,GAAG,EAAE,YAAY,GAChB,IAAI;IASP,MAAM,CAAC,6BAA6B,CAClC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAClC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAC9C,CAAC,EAAE,MAAM,EACT,GAAG,EAAE,YAAY,GAChB,IAAI;IAWP,MAAM,CAAC,WAAW,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO;IAyC/D,MAAM,CAAC,gBAAgB,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE,YAAY,GAAG,IAAI;IAOnE,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAmCjE,MAAM,CAAC,oBAAoB,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,GAAG,IAAI;IASjE,WAAW,IAAI,IAAI;IAKnB,MAAM,IAAI,IAAI;IAKd,MAAM,CAAC,mBAAmB,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI;IAwC5E,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IA6C7D,WAAW,IAAI,IAAI;IAqBnB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAY1D,OAAO,IAAI,IAAI;CA8DhB;AAWD,eAAO,MAAM,iBAAiB,EAAE,YAAY,EAO3C,CAAA;AAED,eAAO,MAAM,WAAW,EAAE,IAAI,EAS7B,CAAA;AAED,eAAO,MAAM,WAAW,EAAE,IAAI,EAK7B,CAAA"}
|
package/dist/math.js
CHANGED
|
@@ -90,6 +90,16 @@ export class Vec3 {
|
|
|
90
90
|
out.z = m[2] * nx + m[6] * ny + m[10] * nz;
|
|
91
91
|
return out;
|
|
92
92
|
}
|
|
93
|
+
// out = (x, y, -z): right-handed Y-up ↔ left-handed Y-up (involutive). Safe when out === v.
|
|
94
|
+
static mirrorZInto(v, out) {
|
|
95
|
+
out.x = v.x;
|
|
96
|
+
out.y = v.y;
|
|
97
|
+
out.z = -v.z;
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
static mirrorZ(v) {
|
|
101
|
+
return new Vec3(v.x, v.y, -v.z);
|
|
102
|
+
}
|
|
93
103
|
// In-place normalize returning length squared info via Vec3. Alias for normalize() but explicit.
|
|
94
104
|
normalizeInPlace() {
|
|
95
105
|
return this.normalize();
|
|
@@ -286,6 +296,318 @@ export class Quat {
|
|
|
286
296
|
const z = cy * cx * sz - sy * sx * cz;
|
|
287
297
|
return new Quat(x, y, z, w).normalize();
|
|
288
298
|
}
|
|
299
|
+
// 4D dot product. Negative means a and b are on opposite hemispheres (same rotation
|
|
300
|
+
// when |dot| ≈ 1 either way — quaternion double cover).
|
|
301
|
+
static dot(a, b) {
|
|
302
|
+
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
|
|
303
|
+
}
|
|
304
|
+
// Rotation angle between a and b in radians, insensitive to double cover.
|
|
305
|
+
static angleTo(a, b) {
|
|
306
|
+
const d = Math.abs(a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w);
|
|
307
|
+
return 2 * Math.acos(Math.min(1, d));
|
|
308
|
+
}
|
|
309
|
+
// out = conjugate of q (inverse for unit quaternions), without mutating q. Safe when out === q.
|
|
310
|
+
static conjugateInto(q, out) {
|
|
311
|
+
out.x = -q.x;
|
|
312
|
+
out.y = -q.y;
|
|
313
|
+
out.z = -q.z;
|
|
314
|
+
out.w = q.w;
|
|
315
|
+
return out;
|
|
316
|
+
}
|
|
317
|
+
// out = normalized lerp from a to b, taking the shorter path (negates b when dot < 0).
|
|
318
|
+
// Cheaper than slerp; non-constant angular velocity. Safe when out === a or out === b.
|
|
319
|
+
static nlerpInto(a, b, t, out) {
|
|
320
|
+
const d = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
|
|
321
|
+
const s = d < 0 ? -1 : 1;
|
|
322
|
+
const x = a.x + (b.x * s - a.x) * t;
|
|
323
|
+
const y = a.y + (b.y * s - a.y) * t;
|
|
324
|
+
const z = a.z + (b.z * s - a.z) * t;
|
|
325
|
+
const w = a.w + (b.w * s - a.w) * t;
|
|
326
|
+
const invLen = 1 / Math.hypot(x, y, z, w);
|
|
327
|
+
out.x = x * invLen;
|
|
328
|
+
out.y = y * invLen;
|
|
329
|
+
out.z = z * invLen;
|
|
330
|
+
out.w = w * invLen;
|
|
331
|
+
return out;
|
|
332
|
+
}
|
|
333
|
+
static nlerp(a, b, t) {
|
|
334
|
+
return Quat.nlerpInto(a, b, t, new Quat(0, 0, 0, 1));
|
|
335
|
+
}
|
|
336
|
+
// out = v rotated by unit quaternion q (no matrix, no allocation). Safe when out === v.
|
|
337
|
+
static rotateVecInto(q, v, out) {
|
|
338
|
+
// v' = v + 2*qw*(qv × v) + 2*(qv × (qv × v))
|
|
339
|
+
const qx = q.x, qy = q.y, qz = q.z, qw = q.w;
|
|
340
|
+
const vx = v.x, vy = v.y, vz = v.z;
|
|
341
|
+
// t = 2 * (qv × v)
|
|
342
|
+
const tx = 2 * (qy * vz - qz * vy);
|
|
343
|
+
const ty = 2 * (qz * vx - qx * vz);
|
|
344
|
+
const tz = 2 * (qx * vy - qy * vx);
|
|
345
|
+
out.x = vx + qw * tx + qy * tz - qz * ty;
|
|
346
|
+
out.y = vy + qw * ty + qz * tx - qx * tz;
|
|
347
|
+
out.z = vz + qw * tz + qx * ty - qy * tx;
|
|
348
|
+
return out;
|
|
349
|
+
}
|
|
350
|
+
static rotateVec(q, v) {
|
|
351
|
+
return Quat.rotateVecInto(q, v, new Vec3(0, 0, 0));
|
|
352
|
+
}
|
|
353
|
+
// out = v rotated by the inverse (conjugate) of unit quaternion q. Safe when out === v.
|
|
354
|
+
static rotateVecInvInto(q, v, out) {
|
|
355
|
+
const qx = -q.x, qy = -q.y, qz = -q.z, qw = q.w;
|
|
356
|
+
const vx = v.x, vy = v.y, vz = v.z;
|
|
357
|
+
const tx = 2 * (qy * vz - qz * vy);
|
|
358
|
+
const ty = 2 * (qz * vx - qx * vz);
|
|
359
|
+
const tz = 2 * (qx * vy - qy * vx);
|
|
360
|
+
out.x = vx + qw * tx + qy * tz - qz * ty;
|
|
361
|
+
out.y = vy + qw * ty + qz * tx - qx * tz;
|
|
362
|
+
out.z = vz + qw * tz + qx * ty - qy * tx;
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
365
|
+
static rotateVecInv(q, v) {
|
|
366
|
+
return Quat.rotateVecInvInto(q, v, new Vec3(0, 0, 0));
|
|
367
|
+
}
|
|
368
|
+
// out = shortest-arc rotation taking unit vector `from` to unit vector `to`.
|
|
369
|
+
// Matches Babylon's FromUnitVectorsToRef exactly, including the near-antiparallel
|
|
370
|
+
// branch (w = 1 + dot < 0.001 → 180° about a perpendicular picked the same way).
|
|
371
|
+
static fromUnitVectorsInto(from, to, out) {
|
|
372
|
+
const r = from.x * to.x + from.y * to.y + from.z * to.z + 1;
|
|
373
|
+
if (r < 0.001) {
|
|
374
|
+
if (Math.abs(from.x) > Math.abs(from.z)) {
|
|
375
|
+
out.setXYZW(-from.y, from.x, 0, 0);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
out.setXYZW(0, -from.z, from.y, 0);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
// q = (from × to, 1 + from·to)
|
|
383
|
+
out.setXYZW(from.y * to.z - from.z * to.y, from.z * to.x - from.x * to.z, from.x * to.y - from.y * to.x, r);
|
|
384
|
+
}
|
|
385
|
+
const invLen = 1 / Math.sqrt(out.x * out.x + out.y * out.y + out.z * out.z + out.w * out.w);
|
|
386
|
+
out.setXYZW(out.x * invLen, out.y * invLen, out.z * invLen, out.w * invLen);
|
|
387
|
+
return out;
|
|
388
|
+
}
|
|
389
|
+
static fromUnitVectors(from, to) {
|
|
390
|
+
return Quat.fromUnitVectorsInto(from, to, new Quat(0, 0, 0, 1));
|
|
391
|
+
}
|
|
392
|
+
// out = rotation taking the standard basis onto the orthonormal axes x, y, z
|
|
393
|
+
// (the columns of the column-major rotation matrix): rotateVec(out, (1,0,0)) = x, etc.
|
|
394
|
+
static fromBasisInto(x, y, z, out) {
|
|
395
|
+
// Shepperd's method on the 3x3 with columns x, y, z.
|
|
396
|
+
const m00 = x.x, m01 = x.y, m02 = x.z;
|
|
397
|
+
const m10 = y.x, m11 = y.y, m12 = y.z;
|
|
398
|
+
const m20 = z.x, m21 = z.y, m22 = z.z;
|
|
399
|
+
const trace = m00 + m11 + m22;
|
|
400
|
+
if (trace > 0) {
|
|
401
|
+
const s = 0.5 / Math.sqrt(trace + 1);
|
|
402
|
+
out.setXYZW((m12 - m21) * s, (m20 - m02) * s, (m01 - m10) * s, 0.25 / s);
|
|
403
|
+
}
|
|
404
|
+
else if (m00 > m11 && m00 > m22) {
|
|
405
|
+
const s = 2 * Math.sqrt(1 + m00 - m11 - m22);
|
|
406
|
+
out.setXYZW(0.25 * s, (m10 + m01) / s, (m20 + m02) / s, (m12 - m21) / s);
|
|
407
|
+
}
|
|
408
|
+
else if (m11 > m22) {
|
|
409
|
+
const s = 2 * Math.sqrt(1 + m11 - m00 - m22);
|
|
410
|
+
out.setXYZW((m10 + m01) / s, 0.25 * s, (m21 + m12) / s, (m20 - m02) / s);
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
const s = 2 * Math.sqrt(1 + m22 - m00 - m11);
|
|
414
|
+
out.setXYZW((m20 + m02) / s, (m21 + m12) / s, 0.25 * s, (m01 - m10) / s);
|
|
415
|
+
}
|
|
416
|
+
return out;
|
|
417
|
+
}
|
|
418
|
+
static fromBasis(x, y, z) {
|
|
419
|
+
return Quat.fromBasisInto(x, y, z, new Quat(0, 0, 0, 1));
|
|
420
|
+
}
|
|
421
|
+
// out = twist component of q around unit axis `a`, so that q = swing · twist
|
|
422
|
+
// (swing = Quat.multiply(q, conjugate(twist))). Singular when q is ~180° about an
|
|
423
|
+
// axis perpendicular to `a` — returns identity there.
|
|
424
|
+
static twistAroundAxisInto(q, a, out) {
|
|
425
|
+
const d = q.x * a.x + q.y * a.y + q.z * a.z;
|
|
426
|
+
const px = a.x * d;
|
|
427
|
+
const py = a.y * d;
|
|
428
|
+
const pz = a.z * d;
|
|
429
|
+
const len = Math.sqrt(px * px + py * py + pz * pz + q.w * q.w);
|
|
430
|
+
if (len < 1e-8) {
|
|
431
|
+
out.setIdentity();
|
|
432
|
+
return out;
|
|
433
|
+
}
|
|
434
|
+
out.setXYZW(px / len, py / len, pz / len, q.w / len);
|
|
435
|
+
return out;
|
|
436
|
+
}
|
|
437
|
+
static twistAroundAxis(q, a) {
|
|
438
|
+
return Quat.twistAroundAxisInto(q, a, new Quat(0, 0, 0, 1));
|
|
439
|
+
}
|
|
440
|
+
// out = orientation looking along `forward` (mapped to local +Z, the engine's LH forward)
|
|
441
|
+
// with local +Y toward `up`. Falls back to a reference up when forward ∥ up.
|
|
442
|
+
static lookRotationInto(forward, up, out) {
|
|
443
|
+
let zx = forward.x, zy = forward.y, zz = forward.z;
|
|
444
|
+
const zl = Math.sqrt(zx * zx + zy * zy + zz * zz);
|
|
445
|
+
if (zl === 0)
|
|
446
|
+
return out.setIdentity();
|
|
447
|
+
const zi = 1 / zl;
|
|
448
|
+
zx *= zi;
|
|
449
|
+
zy *= zi;
|
|
450
|
+
zz *= zi;
|
|
451
|
+
// x = up × z
|
|
452
|
+
let xx = up.y * zz - up.z * zy;
|
|
453
|
+
let xy = up.z * zx - up.x * zz;
|
|
454
|
+
let xz = up.x * zy - up.y * zx;
|
|
455
|
+
let xl = Math.sqrt(xx * xx + xy * xy + xz * xz);
|
|
456
|
+
if (xl < 1e-8) {
|
|
457
|
+
// forward ∥ up: substitute a reference up not parallel to forward
|
|
458
|
+
const uy = Math.abs(zy) < 0.99 ? 1 : 0;
|
|
459
|
+
const ux = 1 - uy;
|
|
460
|
+
xx = uy * zz;
|
|
461
|
+
xy = -ux * zz;
|
|
462
|
+
xz = ux * zy - uy * zx;
|
|
463
|
+
xl = Math.sqrt(xx * xx + xy * xy + xz * xz);
|
|
464
|
+
}
|
|
465
|
+
const xi = 1 / xl;
|
|
466
|
+
xx *= xi;
|
|
467
|
+
xy *= xi;
|
|
468
|
+
xz *= xi;
|
|
469
|
+
// y = z × x (unit: z ⊥ x)
|
|
470
|
+
const yx = zy * xz - zz * xy;
|
|
471
|
+
const yy = zz * xx - zx * xz;
|
|
472
|
+
const yz = zx * xy - zy * xx;
|
|
473
|
+
_lookX.setXYZ(xx, xy, xz);
|
|
474
|
+
_lookY.setXYZ(yx, yy, yz);
|
|
475
|
+
_lookZ.setXYZ(zx, zy, zz);
|
|
476
|
+
return Quat.fromBasisInto(_lookX, _lookY, _lookZ, out);
|
|
477
|
+
}
|
|
478
|
+
static lookRotation(forward, up) {
|
|
479
|
+
return Quat.lookRotationInto(forward, up, new Quat(0, 0, 0, 1));
|
|
480
|
+
}
|
|
481
|
+
// out = q with the Z axis mirrored: right-handed Y-up ↔ left-handed Y-up
|
|
482
|
+
// (conjugation by the Z-mirror; involutive). Pairs with Vec3.mirrorZInto. Safe when out === q.
|
|
483
|
+
static mirrorZInto(q, out) {
|
|
484
|
+
out.x = -q.x;
|
|
485
|
+
out.y = -q.y;
|
|
486
|
+
out.z = q.z;
|
|
487
|
+
out.w = q.w;
|
|
488
|
+
return out;
|
|
489
|
+
}
|
|
490
|
+
static mirrorZ(q) {
|
|
491
|
+
return new Quat(-q.x, -q.y, q.z, q.w);
|
|
492
|
+
}
|
|
493
|
+
// out = quaternion from euler angles (radians) applied in the given intrinsic order.
|
|
494
|
+
// fromEulerOrderInto(x, y, z, "YXZ", out) matches Quat.fromEuler (the MMD/PMX convention).
|
|
495
|
+
static fromEulerOrderInto(x, y, z, order, out) {
|
|
496
|
+
out.setIdentity();
|
|
497
|
+
for (let i = 0; i < 3; i++) {
|
|
498
|
+
const axis = order.charCodeAt(i) - 88; // "X" → 0, "Y" → 1, "Z" → 2
|
|
499
|
+
const angle = axis === 0 ? x : axis === 1 ? y : z;
|
|
500
|
+
const half = angle * 0.5;
|
|
501
|
+
const s = Math.sin(half);
|
|
502
|
+
const c = Math.cos(half);
|
|
503
|
+
const ax = out.x, ay = out.y, az = out.z, aw = out.w;
|
|
504
|
+
if (axis === 0) {
|
|
505
|
+
out.x = aw * s + ax * c;
|
|
506
|
+
out.y = ay * c + az * s;
|
|
507
|
+
out.z = az * c - ay * s;
|
|
508
|
+
out.w = aw * c - ax * s;
|
|
509
|
+
}
|
|
510
|
+
else if (axis === 1) {
|
|
511
|
+
out.x = ax * c - az * s;
|
|
512
|
+
out.y = aw * s + ay * c;
|
|
513
|
+
out.z = az * c + ax * s;
|
|
514
|
+
out.w = aw * c - ay * s;
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
out.x = ax * c + ay * s;
|
|
518
|
+
out.y = ay * c - ax * s;
|
|
519
|
+
out.z = aw * s + az * c;
|
|
520
|
+
out.w = aw * c - az * s;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return out;
|
|
524
|
+
}
|
|
525
|
+
static fromEulerOrder(x, y, z, order) {
|
|
526
|
+
return Quat.fromEulerOrderInto(x, y, z, order, new Quat(0, 0, 0, 1));
|
|
527
|
+
}
|
|
528
|
+
// Extract euler angles (radians) in the given intrinsic order into out (out.x/y/z = rotation
|
|
529
|
+
// about X/Y/Z). At gimbal lock (middle angle ±90°) the split between first and third angle is
|
|
530
|
+
// ambiguous; the third is set to 0.
|
|
531
|
+
static toEulerOrderInto(q, order, out) {
|
|
532
|
+
Mat4.fromQuatInto(q.x, q.y, q.z, q.w, _eulerMat, 0);
|
|
533
|
+
const m = _eulerMat;
|
|
534
|
+
const m11 = m[0], m12 = m[4], m13 = m[8];
|
|
535
|
+
const m21 = m[1], m22 = m[5], m23 = m[9];
|
|
536
|
+
const m31 = m[2], m32 = m[6], m33 = m[10];
|
|
537
|
+
const clamp = (v) => (v < -1 ? -1 : v > 1 ? 1 : v);
|
|
538
|
+
switch (order) {
|
|
539
|
+
case "XYZ":
|
|
540
|
+
out.y = Math.asin(clamp(m13));
|
|
541
|
+
if (Math.abs(m13) < 0.9999999) {
|
|
542
|
+
out.x = Math.atan2(-m23, m33);
|
|
543
|
+
out.z = Math.atan2(-m12, m11);
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
out.x = Math.atan2(m32, m22);
|
|
547
|
+
out.z = 0;
|
|
548
|
+
}
|
|
549
|
+
break;
|
|
550
|
+
case "YXZ":
|
|
551
|
+
out.x = Math.asin(-clamp(m23));
|
|
552
|
+
if (Math.abs(m23) < 0.9999999) {
|
|
553
|
+
out.y = Math.atan2(m13, m33);
|
|
554
|
+
out.z = Math.atan2(m21, m22);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
out.y = Math.atan2(-m31, m11);
|
|
558
|
+
out.z = 0;
|
|
559
|
+
}
|
|
560
|
+
break;
|
|
561
|
+
case "ZXY":
|
|
562
|
+
out.x = Math.asin(clamp(m32));
|
|
563
|
+
if (Math.abs(m32) < 0.9999999) {
|
|
564
|
+
out.y = Math.atan2(-m31, m33);
|
|
565
|
+
out.z = Math.atan2(-m12, m22);
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
out.y = 0;
|
|
569
|
+
out.z = Math.atan2(m21, m11);
|
|
570
|
+
}
|
|
571
|
+
break;
|
|
572
|
+
case "ZYX":
|
|
573
|
+
out.y = Math.asin(-clamp(m31));
|
|
574
|
+
if (Math.abs(m31) < 0.9999999) {
|
|
575
|
+
out.x = Math.atan2(m32, m33);
|
|
576
|
+
out.z = Math.atan2(m21, m11);
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
out.x = 0;
|
|
580
|
+
out.z = Math.atan2(-m12, m22);
|
|
581
|
+
}
|
|
582
|
+
break;
|
|
583
|
+
case "YZX":
|
|
584
|
+
out.z = Math.asin(clamp(m21));
|
|
585
|
+
if (Math.abs(m21) < 0.9999999) {
|
|
586
|
+
out.x = Math.atan2(-m23, m22);
|
|
587
|
+
out.y = Math.atan2(-m31, m11);
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
out.x = 0;
|
|
591
|
+
out.y = Math.atan2(m13, m33);
|
|
592
|
+
}
|
|
593
|
+
break;
|
|
594
|
+
case "XZY":
|
|
595
|
+
out.z = Math.asin(-clamp(m12));
|
|
596
|
+
if (Math.abs(m12) < 0.9999999) {
|
|
597
|
+
out.x = Math.atan2(m32, m22);
|
|
598
|
+
out.y = Math.atan2(m13, m11);
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
out.x = Math.atan2(-m23, m33);
|
|
602
|
+
out.y = 0;
|
|
603
|
+
}
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
return out;
|
|
607
|
+
}
|
|
608
|
+
static toEulerOrder(q, order) {
|
|
609
|
+
return Quat.toEulerOrderInto(q, order, new Vec3(0, 0, 0));
|
|
610
|
+
}
|
|
289
611
|
}
|
|
290
612
|
export class Mat4 {
|
|
291
613
|
constructor(values) {
|
|
@@ -762,6 +1084,12 @@ export class Mat4 {
|
|
|
762
1084
|
return new Mat4(out);
|
|
763
1085
|
}
|
|
764
1086
|
}
|
|
1087
|
+
// Module-private scratch for Quat.toEulerOrderInto / lookRotationInto (never handed out,
|
|
1088
|
+
// so no cross-call stomping with the public pools below).
|
|
1089
|
+
const _eulerMat = new Float32Array(16);
|
|
1090
|
+
const _lookX = new Vec3(0, 0, 0);
|
|
1091
|
+
const _lookY = new Vec3(0, 0, 0);
|
|
1092
|
+
const _lookZ = new Vec3(0, 0, 0);
|
|
765
1093
|
// Preallocated scratch instances for hot paths. Each subsystem should use its own
|
|
766
1094
|
// slot to avoid cross-call stomping. Bump the count if more call sites need scratch.
|
|
767
1095
|
export const scratchMat4Values = [
|
package/package.json
CHANGED
package/src/ik-solver.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// IK solver (MMD-style; see Saba MMDIkSolver.cpp)
|
|
2
2
|
|
|
3
|
-
import { Mat4, Quat, Vec3 } from "./math"
|
|
3
|
+
import { Mat4, Quat, Vec3, type EulerOrder } from "./math"
|
|
4
4
|
import { Bone, IKLink, IKSolver, IKChainInfo } from "./model"
|
|
5
5
|
|
|
6
6
|
// Callback type for updating world matrix (provided by model to handle append transformations)
|
|
@@ -80,7 +80,6 @@ const _ikVec: Vec3[] = [
|
|
|
80
80
|
]
|
|
81
81
|
const _ikQuat: Quat[] = [
|
|
82
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
83
|
]
|
|
85
84
|
const _ikMat: Float32Array[] = [
|
|
86
85
|
new Float32Array(16), new Float32Array(16), new Float32Array(16), new Float32Array(16),
|
|
@@ -215,7 +214,7 @@ export class IKSolverSystem {
|
|
|
215
214
|
// _ikVec[0]=chainPos, [1]=ikPos, [2]=targetPos, [3]=chainTargetVec, [4]=chainIkVec,
|
|
216
215
|
// [5]=rotAxis, [6]=finalAxis, [7]=eulerTmp, [8]=limitedEuler
|
|
217
216
|
// _ikMat[0]=parentRot, [1]=invParentRot, [2]=quatToMatTmp
|
|
218
|
-
// _ikQuat[0]=ikRotation, [1]=combinedRot, [2]=localRotConj
|
|
217
|
+
// _ikQuat[0]=ikRotation, [1]=combinedRot, [2]=localRotConj
|
|
219
218
|
|
|
220
219
|
const chainPos = Vec3.setFromMat4Translation(worldMatrices[chainBoneIndex].values, _ikVec[0])
|
|
221
220
|
const ikPos = Vec3.setFromMat4Translation(worldMatrices[ikBoneIndex].values, _ikVec[1])
|
|
@@ -340,16 +339,8 @@ export class IKSolverSystem {
|
|
|
340
339
|
return Math.sqrt(dx * dx + dy * dy + dz * dz)
|
|
341
340
|
}
|
|
342
341
|
|
|
343
|
-
//
|
|
344
|
-
|
|
345
|
-
private static readonly EULER_AXES: readonly [number, number, number][][] = [
|
|
346
|
-
// YXZ: Y, X, Z
|
|
347
|
-
[[0, 1, 0], [1, 0, 0], [0, 0, 1]],
|
|
348
|
-
// ZYX: Z, Y, X
|
|
349
|
-
[[0, 0, 1], [0, 1, 0], [1, 0, 0]],
|
|
350
|
-
// XZY: X, Z, Y
|
|
351
|
-
[[1, 0, 0], [0, 0, 1], [0, 1, 0]],
|
|
352
|
-
]
|
|
342
|
+
// Maps InternalEulerRotationOrder to the shared math-layer order names.
|
|
343
|
+
private static readonly EULER_ORDER_NAMES: readonly EulerOrder[] = ["YXZ", "ZYX", "XZY"]
|
|
353
344
|
|
|
354
345
|
private static extractEulerAnglesInto(quat: Quat, order: InternalEulerRotationOrder, out: Vec3): void {
|
|
355
346
|
Mat4.fromQuatInto(quat.x, quat.y, quat.z, quat.w, _ikMat[2], 0)
|
|
@@ -396,27 +387,7 @@ export class IKSolverSystem {
|
|
|
396
387
|
}
|
|
397
388
|
|
|
398
389
|
private static reconstructQuatFromEulerInto(euler: Vec3, order: InternalEulerRotationOrder, out: Quat): void {
|
|
399
|
-
|
|
400
|
-
const a1 = axes[0], a2 = axes[1], a3 = axes[2]
|
|
401
|
-
const ang1 =
|
|
402
|
-
order === InternalEulerRotationOrder.YXZ ? euler.y
|
|
403
|
-
: order === InternalEulerRotationOrder.ZYX ? euler.z
|
|
404
|
-
: euler.x
|
|
405
|
-
const ang2 =
|
|
406
|
-
order === InternalEulerRotationOrder.YXZ ? euler.x
|
|
407
|
-
: order === InternalEulerRotationOrder.ZYX ? euler.y
|
|
408
|
-
: euler.z
|
|
409
|
-
const ang3 =
|
|
410
|
-
order === InternalEulerRotationOrder.YXZ ? euler.z
|
|
411
|
-
: order === InternalEulerRotationOrder.ZYX ? euler.x
|
|
412
|
-
: euler.y
|
|
413
|
-
|
|
414
|
-
// result = axisAngle(a1, ang1); then *= axisAngle(a2, ang2); then *= axisAngle(a3, ang3)
|
|
415
|
-
Quat.fromAxisAngleInto(a1[0], a1[1], a1[2], ang1, out)
|
|
416
|
-
Quat.fromAxisAngleInto(a2[0], a2[1], a2[2], ang2, _ikQuat[3])
|
|
417
|
-
Quat.multiplyInto(out, _ikQuat[3], out)
|
|
418
|
-
Quat.fromAxisAngleInto(a3[0], a3[1], a3[2], ang3, _ikQuat[3])
|
|
419
|
-
Quat.multiplyInto(out, _ikQuat[3], out)
|
|
390
|
+
Quat.fromEulerOrderInto(euler.x, euler.y, euler.z, this.EULER_ORDER_NAMES[order], out)
|
|
420
391
|
}
|
|
421
392
|
|
|
422
393
|
// Write parent's world rotation (translation stripped) into out Float32Array.
|
package/src/index.ts
CHANGED
|
@@ -53,7 +53,7 @@ export { STOCKINGS_GRAPH } from "./graph/presets/stockings"
|
|
|
53
53
|
export { EYE_GRAPH } from "./graph/presets/eye"
|
|
54
54
|
export { FACE_GRAPH } from "./graph/presets/face"
|
|
55
55
|
export { Model } from "./model"
|
|
56
|
-
export { Vec3, Quat, Mat4 } from "./math"
|
|
56
|
+
export { Vec3, Quat, Mat4, easeInOut, type EulerOrder } from "./math"
|
|
57
57
|
export type {
|
|
58
58
|
AnimationClip,
|
|
59
59
|
AnimationPlayOptions,
|
|
@@ -64,7 +64,12 @@ export type {
|
|
|
64
64
|
BoneInterpolation,
|
|
65
65
|
ControlPoint,
|
|
66
66
|
} from "./animation"
|
|
67
|
-
export {
|
|
67
|
+
export {
|
|
68
|
+
FPS,
|
|
69
|
+
bezierInterpolate,
|
|
70
|
+
interpolateControlPoints,
|
|
71
|
+
rawInterpolationToBoneInterpolation,
|
|
72
|
+
} from "./animation"
|
|
68
73
|
export { VMDLoader, type CameraKeyframe, type IkFrame } from "./vmd-loader"
|
|
69
74
|
export { CameraAnimation, type CameraPose } from "./camera-animation"
|
|
70
75
|
export { RezePhysics } from "./physics"
|
package/src/math.ts
CHANGED
|
@@ -3,6 +3,10 @@ export function easeInOut(t: number): number {
|
|
|
3
3
|
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
// Euler rotation order, read as intrinsic axes left to right:
|
|
7
|
+
// "YXZ" = rotate about Y, then local X, then local Z (equals extrinsic ZXY — the MMD/PMX convention).
|
|
8
|
+
export type EulerOrder = "XYZ" | "XZY" | "YXZ" | "YZX" | "ZXY" | "ZYX"
|
|
9
|
+
|
|
6
10
|
export class Vec3 {
|
|
7
11
|
x: number
|
|
8
12
|
y: number
|
|
@@ -113,6 +117,18 @@ export class Vec3 {
|
|
|
113
117
|
return out
|
|
114
118
|
}
|
|
115
119
|
|
|
120
|
+
// out = (x, y, -z): right-handed Y-up ↔ left-handed Y-up (involutive). Safe when out === v.
|
|
121
|
+
static mirrorZInto(v: Vec3, out: Vec3): Vec3 {
|
|
122
|
+
out.x = v.x
|
|
123
|
+
out.y = v.y
|
|
124
|
+
out.z = -v.z
|
|
125
|
+
return out
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
static mirrorZ(v: Vec3): Vec3 {
|
|
129
|
+
return new Vec3(v.x, v.y, -v.z)
|
|
130
|
+
}
|
|
131
|
+
|
|
116
132
|
// In-place normalize returning length squared info via Vec3. Alias for normalize() but explicit.
|
|
117
133
|
normalizeInPlace(): Vec3 {
|
|
118
134
|
return this.normalize()
|
|
@@ -333,6 +349,328 @@ export class Quat {
|
|
|
333
349
|
|
|
334
350
|
return new Quat(x, y, z, w).normalize()
|
|
335
351
|
}
|
|
352
|
+
|
|
353
|
+
// 4D dot product. Negative means a and b are on opposite hemispheres (same rotation
|
|
354
|
+
// when |dot| ≈ 1 either way — quaternion double cover).
|
|
355
|
+
static dot(a: Quat, b: Quat): number {
|
|
356
|
+
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Rotation angle between a and b in radians, insensitive to double cover.
|
|
360
|
+
static angleTo(a: Quat, b: Quat): number {
|
|
361
|
+
const d = Math.abs(a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w)
|
|
362
|
+
return 2 * Math.acos(Math.min(1, d))
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// out = conjugate of q (inverse for unit quaternions), without mutating q. Safe when out === q.
|
|
366
|
+
static conjugateInto(q: Quat, out: Quat): Quat {
|
|
367
|
+
out.x = -q.x
|
|
368
|
+
out.y = -q.y
|
|
369
|
+
out.z = -q.z
|
|
370
|
+
out.w = q.w
|
|
371
|
+
return out
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// out = normalized lerp from a to b, taking the shorter path (negates b when dot < 0).
|
|
375
|
+
// Cheaper than slerp; non-constant angular velocity. Safe when out === a or out === b.
|
|
376
|
+
static nlerpInto(a: Quat, b: Quat, t: number, out: Quat): Quat {
|
|
377
|
+
const d = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w
|
|
378
|
+
const s = d < 0 ? -1 : 1
|
|
379
|
+
const x = a.x + (b.x * s - a.x) * t
|
|
380
|
+
const y = a.y + (b.y * s - a.y) * t
|
|
381
|
+
const z = a.z + (b.z * s - a.z) * t
|
|
382
|
+
const w = a.w + (b.w * s - a.w) * t
|
|
383
|
+
const invLen = 1 / Math.hypot(x, y, z, w)
|
|
384
|
+
out.x = x * invLen
|
|
385
|
+
out.y = y * invLen
|
|
386
|
+
out.z = z * invLen
|
|
387
|
+
out.w = w * invLen
|
|
388
|
+
return out
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
static nlerp(a: Quat, b: Quat, t: number): Quat {
|
|
392
|
+
return Quat.nlerpInto(a, b, t, new Quat(0, 0, 0, 1))
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// out = v rotated by unit quaternion q (no matrix, no allocation). Safe when out === v.
|
|
396
|
+
static rotateVecInto(q: Quat, v: Vec3, out: Vec3): Vec3 {
|
|
397
|
+
// v' = v + 2*qw*(qv × v) + 2*(qv × (qv × v))
|
|
398
|
+
const qx = q.x, qy = q.y, qz = q.z, qw = q.w
|
|
399
|
+
const vx = v.x, vy = v.y, vz = v.z
|
|
400
|
+
// t = 2 * (qv × v)
|
|
401
|
+
const tx = 2 * (qy * vz - qz * vy)
|
|
402
|
+
const ty = 2 * (qz * vx - qx * vz)
|
|
403
|
+
const tz = 2 * (qx * vy - qy * vx)
|
|
404
|
+
out.x = vx + qw * tx + qy * tz - qz * ty
|
|
405
|
+
out.y = vy + qw * ty + qz * tx - qx * tz
|
|
406
|
+
out.z = vz + qw * tz + qx * ty - qy * tx
|
|
407
|
+
return out
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
static rotateVec(q: Quat, v: Vec3): Vec3 {
|
|
411
|
+
return Quat.rotateVecInto(q, v, new Vec3(0, 0, 0))
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// out = v rotated by the inverse (conjugate) of unit quaternion q. Safe when out === v.
|
|
415
|
+
static rotateVecInvInto(q: Quat, v: Vec3, out: Vec3): Vec3 {
|
|
416
|
+
const qx = -q.x, qy = -q.y, qz = -q.z, qw = q.w
|
|
417
|
+
const vx = v.x, vy = v.y, vz = v.z
|
|
418
|
+
const tx = 2 * (qy * vz - qz * vy)
|
|
419
|
+
const ty = 2 * (qz * vx - qx * vz)
|
|
420
|
+
const tz = 2 * (qx * vy - qy * vx)
|
|
421
|
+
out.x = vx + qw * tx + qy * tz - qz * ty
|
|
422
|
+
out.y = vy + qw * ty + qz * tx - qx * tz
|
|
423
|
+
out.z = vz + qw * tz + qx * ty - qy * tx
|
|
424
|
+
return out
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
static rotateVecInv(q: Quat, v: Vec3): Vec3 {
|
|
428
|
+
return Quat.rotateVecInvInto(q, v, new Vec3(0, 0, 0))
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// out = shortest-arc rotation taking unit vector `from` to unit vector `to`.
|
|
432
|
+
// Matches Babylon's FromUnitVectorsToRef exactly, including the near-antiparallel
|
|
433
|
+
// branch (w = 1 + dot < 0.001 → 180° about a perpendicular picked the same way).
|
|
434
|
+
static fromUnitVectorsInto(from: Vec3, to: Vec3, out: Quat): Quat {
|
|
435
|
+
const r = from.x * to.x + from.y * to.y + from.z * to.z + 1
|
|
436
|
+
if (r < 0.001) {
|
|
437
|
+
if (Math.abs(from.x) > Math.abs(from.z)) {
|
|
438
|
+
out.setXYZW(-from.y, from.x, 0, 0)
|
|
439
|
+
} else {
|
|
440
|
+
out.setXYZW(0, -from.z, from.y, 0)
|
|
441
|
+
}
|
|
442
|
+
} else {
|
|
443
|
+
// q = (from × to, 1 + from·to)
|
|
444
|
+
out.setXYZW(
|
|
445
|
+
from.y * to.z - from.z * to.y,
|
|
446
|
+
from.z * to.x - from.x * to.z,
|
|
447
|
+
from.x * to.y - from.y * to.x,
|
|
448
|
+
r
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
const invLen = 1 / Math.sqrt(out.x * out.x + out.y * out.y + out.z * out.z + out.w * out.w)
|
|
452
|
+
out.setXYZW(out.x * invLen, out.y * invLen, out.z * invLen, out.w * invLen)
|
|
453
|
+
return out
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
static fromUnitVectors(from: Vec3, to: Vec3): Quat {
|
|
457
|
+
return Quat.fromUnitVectorsInto(from, to, new Quat(0, 0, 0, 1))
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// out = rotation taking the standard basis onto the orthonormal axes x, y, z
|
|
461
|
+
// (the columns of the column-major rotation matrix): rotateVec(out, (1,0,0)) = x, etc.
|
|
462
|
+
static fromBasisInto(x: Vec3, y: Vec3, z: Vec3, out: Quat): Quat {
|
|
463
|
+
// Shepperd's method on the 3x3 with columns x, y, z.
|
|
464
|
+
const m00 = x.x, m01 = x.y, m02 = x.z
|
|
465
|
+
const m10 = y.x, m11 = y.y, m12 = y.z
|
|
466
|
+
const m20 = z.x, m21 = z.y, m22 = z.z
|
|
467
|
+
const trace = m00 + m11 + m22
|
|
468
|
+
if (trace > 0) {
|
|
469
|
+
const s = 0.5 / Math.sqrt(trace + 1)
|
|
470
|
+
out.setXYZW((m12 - m21) * s, (m20 - m02) * s, (m01 - m10) * s, 0.25 / s)
|
|
471
|
+
} else if (m00 > m11 && m00 > m22) {
|
|
472
|
+
const s = 2 * Math.sqrt(1 + m00 - m11 - m22)
|
|
473
|
+
out.setXYZW(0.25 * s, (m10 + m01) / s, (m20 + m02) / s, (m12 - m21) / s)
|
|
474
|
+
} else if (m11 > m22) {
|
|
475
|
+
const s = 2 * Math.sqrt(1 + m11 - m00 - m22)
|
|
476
|
+
out.setXYZW((m10 + m01) / s, 0.25 * s, (m21 + m12) / s, (m20 - m02) / s)
|
|
477
|
+
} else {
|
|
478
|
+
const s = 2 * Math.sqrt(1 + m22 - m00 - m11)
|
|
479
|
+
out.setXYZW((m20 + m02) / s, (m21 + m12) / s, 0.25 * s, (m01 - m10) / s)
|
|
480
|
+
}
|
|
481
|
+
return out
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
static fromBasis(x: Vec3, y: Vec3, z: Vec3): Quat {
|
|
485
|
+
return Quat.fromBasisInto(x, y, z, new Quat(0, 0, 0, 1))
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// out = twist component of q around unit axis `a`, so that q = swing · twist
|
|
489
|
+
// (swing = Quat.multiply(q, conjugate(twist))). Singular when q is ~180° about an
|
|
490
|
+
// axis perpendicular to `a` — returns identity there.
|
|
491
|
+
static twistAroundAxisInto(q: Quat, a: Vec3, out: Quat): Quat {
|
|
492
|
+
const d = q.x * a.x + q.y * a.y + q.z * a.z
|
|
493
|
+
const px = a.x * d
|
|
494
|
+
const py = a.y * d
|
|
495
|
+
const pz = a.z * d
|
|
496
|
+
const len = Math.sqrt(px * px + py * py + pz * pz + q.w * q.w)
|
|
497
|
+
if (len < 1e-8) {
|
|
498
|
+
out.setIdentity()
|
|
499
|
+
return out
|
|
500
|
+
}
|
|
501
|
+
out.setXYZW(px / len, py / len, pz / len, q.w / len)
|
|
502
|
+
return out
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
static twistAroundAxis(q: Quat, a: Vec3): Quat {
|
|
506
|
+
return Quat.twistAroundAxisInto(q, a, new Quat(0, 0, 0, 1))
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// out = orientation looking along `forward` (mapped to local +Z, the engine's LH forward)
|
|
510
|
+
// with local +Y toward `up`. Falls back to a reference up when forward ∥ up.
|
|
511
|
+
static lookRotationInto(forward: Vec3, up: Vec3, out: Quat): Quat {
|
|
512
|
+
let zx = forward.x, zy = forward.y, zz = forward.z
|
|
513
|
+
const zl = Math.sqrt(zx * zx + zy * zy + zz * zz)
|
|
514
|
+
if (zl === 0) return out.setIdentity()
|
|
515
|
+
const zi = 1 / zl
|
|
516
|
+
zx *= zi; zy *= zi; zz *= zi
|
|
517
|
+
// x = up × z
|
|
518
|
+
let xx = up.y * zz - up.z * zy
|
|
519
|
+
let xy = up.z * zx - up.x * zz
|
|
520
|
+
let xz = up.x * zy - up.y * zx
|
|
521
|
+
let xl = Math.sqrt(xx * xx + xy * xy + xz * xz)
|
|
522
|
+
if (xl < 1e-8) {
|
|
523
|
+
// forward ∥ up: substitute a reference up not parallel to forward
|
|
524
|
+
const uy = Math.abs(zy) < 0.99 ? 1 : 0
|
|
525
|
+
const ux = 1 - uy
|
|
526
|
+
xx = uy * zz
|
|
527
|
+
xy = -ux * zz
|
|
528
|
+
xz = ux * zy - uy * zx
|
|
529
|
+
xl = Math.sqrt(xx * xx + xy * xy + xz * xz)
|
|
530
|
+
}
|
|
531
|
+
const xi = 1 / xl
|
|
532
|
+
xx *= xi; xy *= xi; xz *= xi
|
|
533
|
+
// y = z × x (unit: z ⊥ x)
|
|
534
|
+
const yx = zy * xz - zz * xy
|
|
535
|
+
const yy = zz * xx - zx * xz
|
|
536
|
+
const yz = zx * xy - zy * xx
|
|
537
|
+
_lookX.setXYZ(xx, xy, xz)
|
|
538
|
+
_lookY.setXYZ(yx, yy, yz)
|
|
539
|
+
_lookZ.setXYZ(zx, zy, zz)
|
|
540
|
+
return Quat.fromBasisInto(_lookX, _lookY, _lookZ, out)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
static lookRotation(forward: Vec3, up: Vec3): Quat {
|
|
544
|
+
return Quat.lookRotationInto(forward, up, new Quat(0, 0, 0, 1))
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// out = q with the Z axis mirrored: right-handed Y-up ↔ left-handed Y-up
|
|
548
|
+
// (conjugation by the Z-mirror; involutive). Pairs with Vec3.mirrorZInto. Safe when out === q.
|
|
549
|
+
static mirrorZInto(q: Quat, out: Quat): Quat {
|
|
550
|
+
out.x = -q.x
|
|
551
|
+
out.y = -q.y
|
|
552
|
+
out.z = q.z
|
|
553
|
+
out.w = q.w
|
|
554
|
+
return out
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
static mirrorZ(q: Quat): Quat {
|
|
558
|
+
return new Quat(-q.x, -q.y, q.z, q.w)
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// out = quaternion from euler angles (radians) applied in the given intrinsic order.
|
|
562
|
+
// fromEulerOrderInto(x, y, z, "YXZ", out) matches Quat.fromEuler (the MMD/PMX convention).
|
|
563
|
+
static fromEulerOrderInto(x: number, y: number, z: number, order: EulerOrder, out: Quat): Quat {
|
|
564
|
+
out.setIdentity()
|
|
565
|
+
for (let i = 0; i < 3; i++) {
|
|
566
|
+
const axis = order.charCodeAt(i) - 88 // "X" → 0, "Y" → 1, "Z" → 2
|
|
567
|
+
const angle = axis === 0 ? x : axis === 1 ? y : z
|
|
568
|
+
const half = angle * 0.5
|
|
569
|
+
const s = Math.sin(half)
|
|
570
|
+
const c = Math.cos(half)
|
|
571
|
+
const ax = out.x, ay = out.y, az = out.z, aw = out.w
|
|
572
|
+
if (axis === 0) {
|
|
573
|
+
out.x = aw * s + ax * c
|
|
574
|
+
out.y = ay * c + az * s
|
|
575
|
+
out.z = az * c - ay * s
|
|
576
|
+
out.w = aw * c - ax * s
|
|
577
|
+
} else if (axis === 1) {
|
|
578
|
+
out.x = ax * c - az * s
|
|
579
|
+
out.y = aw * s + ay * c
|
|
580
|
+
out.z = az * c + ax * s
|
|
581
|
+
out.w = aw * c - ay * s
|
|
582
|
+
} else {
|
|
583
|
+
out.x = ax * c + ay * s
|
|
584
|
+
out.y = ay * c - ax * s
|
|
585
|
+
out.z = aw * s + az * c
|
|
586
|
+
out.w = aw * c - az * s
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return out
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
static fromEulerOrder(x: number, y: number, z: number, order: EulerOrder): Quat {
|
|
593
|
+
return Quat.fromEulerOrderInto(x, y, z, order, new Quat(0, 0, 0, 1))
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Extract euler angles (radians) in the given intrinsic order into out (out.x/y/z = rotation
|
|
597
|
+
// about X/Y/Z). At gimbal lock (middle angle ±90°) the split between first and third angle is
|
|
598
|
+
// ambiguous; the third is set to 0.
|
|
599
|
+
static toEulerOrderInto(q: Quat, order: EulerOrder, out: Vec3): Vec3 {
|
|
600
|
+
Mat4.fromQuatInto(q.x, q.y, q.z, q.w, _eulerMat, 0)
|
|
601
|
+
const m = _eulerMat
|
|
602
|
+
const m11 = m[0], m12 = m[4], m13 = m[8]
|
|
603
|
+
const m21 = m[1], m22 = m[5], m23 = m[9]
|
|
604
|
+
const m31 = m[2], m32 = m[6], m33 = m[10]
|
|
605
|
+
const clamp = (v: number) => (v < -1 ? -1 : v > 1 ? 1 : v)
|
|
606
|
+
switch (order) {
|
|
607
|
+
case "XYZ":
|
|
608
|
+
out.y = Math.asin(clamp(m13))
|
|
609
|
+
if (Math.abs(m13) < 0.9999999) {
|
|
610
|
+
out.x = Math.atan2(-m23, m33)
|
|
611
|
+
out.z = Math.atan2(-m12, m11)
|
|
612
|
+
} else {
|
|
613
|
+
out.x = Math.atan2(m32, m22)
|
|
614
|
+
out.z = 0
|
|
615
|
+
}
|
|
616
|
+
break
|
|
617
|
+
case "YXZ":
|
|
618
|
+
out.x = Math.asin(-clamp(m23))
|
|
619
|
+
if (Math.abs(m23) < 0.9999999) {
|
|
620
|
+
out.y = Math.atan2(m13, m33)
|
|
621
|
+
out.z = Math.atan2(m21, m22)
|
|
622
|
+
} else {
|
|
623
|
+
out.y = Math.atan2(-m31, m11)
|
|
624
|
+
out.z = 0
|
|
625
|
+
}
|
|
626
|
+
break
|
|
627
|
+
case "ZXY":
|
|
628
|
+
out.x = Math.asin(clamp(m32))
|
|
629
|
+
if (Math.abs(m32) < 0.9999999) {
|
|
630
|
+
out.y = Math.atan2(-m31, m33)
|
|
631
|
+
out.z = Math.atan2(-m12, m22)
|
|
632
|
+
} else {
|
|
633
|
+
out.y = 0
|
|
634
|
+
out.z = Math.atan2(m21, m11)
|
|
635
|
+
}
|
|
636
|
+
break
|
|
637
|
+
case "ZYX":
|
|
638
|
+
out.y = Math.asin(-clamp(m31))
|
|
639
|
+
if (Math.abs(m31) < 0.9999999) {
|
|
640
|
+
out.x = Math.atan2(m32, m33)
|
|
641
|
+
out.z = Math.atan2(m21, m11)
|
|
642
|
+
} else {
|
|
643
|
+
out.x = 0
|
|
644
|
+
out.z = Math.atan2(-m12, m22)
|
|
645
|
+
}
|
|
646
|
+
break
|
|
647
|
+
case "YZX":
|
|
648
|
+
out.z = Math.asin(clamp(m21))
|
|
649
|
+
if (Math.abs(m21) < 0.9999999) {
|
|
650
|
+
out.x = Math.atan2(-m23, m22)
|
|
651
|
+
out.y = Math.atan2(-m31, m11)
|
|
652
|
+
} else {
|
|
653
|
+
out.x = 0
|
|
654
|
+
out.y = Math.atan2(m13, m33)
|
|
655
|
+
}
|
|
656
|
+
break
|
|
657
|
+
case "XZY":
|
|
658
|
+
out.z = Math.asin(-clamp(m12))
|
|
659
|
+
if (Math.abs(m12) < 0.9999999) {
|
|
660
|
+
out.x = Math.atan2(m32, m22)
|
|
661
|
+
out.y = Math.atan2(m13, m11)
|
|
662
|
+
} else {
|
|
663
|
+
out.x = Math.atan2(-m23, m33)
|
|
664
|
+
out.y = 0
|
|
665
|
+
}
|
|
666
|
+
break
|
|
667
|
+
}
|
|
668
|
+
return out
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
static toEulerOrder(q: Quat, order: EulerOrder): Vec3 {
|
|
672
|
+
return Quat.toEulerOrderInto(q, order, new Vec3(0, 0, 0))
|
|
673
|
+
}
|
|
336
674
|
}
|
|
337
675
|
|
|
338
676
|
export class Mat4 {
|
|
@@ -837,6 +1175,13 @@ export class Mat4 {
|
|
|
837
1175
|
}
|
|
838
1176
|
}
|
|
839
1177
|
|
|
1178
|
+
// Module-private scratch for Quat.toEulerOrderInto / lookRotationInto (never handed out,
|
|
1179
|
+
// so no cross-call stomping with the public pools below).
|
|
1180
|
+
const _eulerMat = new Float32Array(16)
|
|
1181
|
+
const _lookX = new Vec3(0, 0, 0)
|
|
1182
|
+
const _lookY = new Vec3(0, 0, 0)
|
|
1183
|
+
const _lookZ = new Vec3(0, 0, 0)
|
|
1184
|
+
|
|
840
1185
|
// Preallocated scratch instances for hot paths. Each subsystem should use its own
|
|
841
1186
|
// slot to avoid cross-call stomping. Bump the count if more call sites need scratch.
|
|
842
1187
|
export const scratchMat4Values: Float32Array[] = [
|