lecodes-cli 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +300 -149
- package/package.json +5 -3
- package/runtime/sdk/animate/animate.ts +238 -0
- package/runtime/sdk/animate/bezier.ts +138 -0
- package/runtime/sdk/animate/easings.ts +126 -0
- package/runtime/sdk/core/color.ts +66 -0
- package/runtime/sdk/core/events.ts +38 -0
- package/runtime/sdk/core/registry.ts +23 -0
- package/runtime/sdk/g2/Camera2D.ts +73 -0
- package/runtime/sdk/g2/Node2D.ts +104 -0
- package/runtime/sdk/g2/Scene2D.ts +73 -0
- package/runtime/sdk/g2/Sprite.ts +164 -0
- package/runtime/sdk/g2/Texture2D.ts +37 -0
- package/runtime/sdk/g2/Tilemap.ts +41 -0
- package/runtime/sdk/g2/loop.ts +22 -0
- package/runtime/sdk/gl/Body.ts +101 -0
- package/runtime/sdk/gl/Camera.ts +40 -0
- package/runtime/sdk/gl/Geometry.ts +218 -0
- package/runtime/sdk/gl/Light.ts +37 -0
- package/runtime/sdk/gl/Material.ts +124 -0
- package/runtime/sdk/gl/Mesh.ts +123 -0
- package/runtime/sdk/gl/Node.ts +307 -0
- package/runtime/sdk/gl/Noise.ts +30 -0
- package/runtime/sdk/gl/Particles.ts +185 -0
- package/runtime/sdk/gl/Plane.ts +53 -0
- package/runtime/sdk/gl/Ray.ts +16 -0
- package/runtime/sdk/gl/Scene.ts +179 -0
- package/runtime/sdk/gl/Texture.ts +35 -0
- package/runtime/sdk/gl/controls.ts +95 -0
- package/runtime/sdk/gl/state.ts +6 -0
- package/runtime/sdk/gl/touch.ts +56 -0
- package/runtime/sdk/inject.ts +72 -0
- package/runtime/sdk/math/Mathf.ts +118 -0
- package/runtime/sdk/math/mat4.ts +278 -0
- package/runtime/sdk/math/quat.ts +232 -0
- package/runtime/sdk/math/vec.ts +255 -0
- package/runtime/sdk/plugins/qr.ts +36 -0
- package/runtime/sdk/runtime/device.ts +33 -0
- package/runtime/sdk/runtime/fetch.ts +71 -0
- package/runtime/sdk/runtime/files.ts +23 -0
- package/runtime/sdk/runtime/input.ts +12 -0
- package/runtime/sdk/runtime/media.ts +111 -0
- package/runtime/sdk/runtime/misc.ts +12 -0
- package/runtime/sdk/runtime/net.ts +36 -0
- package/runtime/sdk/runtime/storage.ts +13 -0
- package/runtime/sdk/runtime/touch.ts +44 -0
- package/runtime/sdk/ui/UI.ts +19 -0
- package/runtime/sdk/ui/UIButton.ts +84 -0
- package/runtime/sdk/ui/UIContainer.ts +100 -0
- package/runtime/sdk/ui/UIImage.ts +68 -0
- package/runtime/sdk/ui/UIInput.ts +78 -0
- package/runtime/sdk/ui/UINode.ts +342 -0
- package/runtime/sdk/ui/UIScreen.ts +122 -0
- package/runtime/sdk/ui/UIScrollable.ts +70 -0
- package/runtime/sdk/ui/UISpacer.ts +14 -0
- package/runtime/sdk/ui/UIText.ts +40 -0
- package/runtime/sdk/ui/UIVideo.ts +38 -0
- package/runtime/sdk/ui/UIVirtualizedList.ts +222 -0
- package/runtime/sdk/ui/UIWidget.ts +97 -0
- package/runtime/sdk/ui/fonts.ts +10 -0
- package/runtime/sdk/ui/router.ts +73 -0
- package/runtime/gl-matrix/common.js +0 -54
- package/runtime/gl-matrix/header.js +0 -12
- package/runtime/gl-matrix/index.js +0 -11
- package/runtime/gl-matrix/mat2.js +0 -425
- package/runtime/gl-matrix/mat2d.js +0 -476
- package/runtime/gl-matrix/mat3.js +0 -765
- package/runtime/gl-matrix/mat4.js +0 -1947
- package/runtime/gl-matrix/quat.js +0 -737
- package/runtime/gl-matrix/quat2.js +0 -822
- package/runtime/gl-matrix/vec2.js +0 -620
- package/runtime/gl-matrix/vec3.js +0 -791
- package/runtime/gl-matrix/vec4.js +0 -652
- package/runtime/worker/animate.js +0 -304
- package/runtime/worker/chunk-4ZN32GYS.js +0 -34
- package/runtime/worker/chunk-G5ZA3LKB.js +0 -52
- package/runtime/worker/chunk-MI3UXE5H.js +0 -1395
- package/runtime/worker/creator.js +0 -930
- package/runtime/worker/globals.js +0 -208
- package/runtime/worker/ui.js +0 -763
- package/runtime/worker/utils.js +0 -16
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
// Fluent 4×4 matrix (column-major) — the SDK's transform/projection type. Same contract as Vec/Quat:
|
|
2
|
+
// the methods are PURE (each returns a new Mat4), but — per the "never wrap away the metal" rule — the
|
|
3
|
+
// raw backing array is PUBLIC as `.m` (length 16). So low-level numeric surgery (`mat.m[12] += vx*dt`)
|
|
4
|
+
// drops straight to the array with zero ceremony, while the fluent methods express the high-level logic.
|
|
5
|
+
//
|
|
6
|
+
// Backing is a plain `number[]` (not a Float32Array) so a future optimizer can see through it; the
|
|
7
|
+
// Float32Array the host bridge wants is produced only at the edge via `toFloat32Array()`.
|
|
8
|
+
//
|
|
9
|
+
// This folds in the old `math/transform.ts` decomposition (position / scale / rotation / euler).
|
|
10
|
+
|
|
11
|
+
import { Vec3, type Vec3Like } from "./vec"
|
|
12
|
+
import { Quat, quatFromColMajor, eulerFromColMajor, type QuatLike, type EulerOrder } from "./quat"
|
|
13
|
+
|
|
14
|
+
export type Mat4Like = Mat4 | number[] | Float32Array | readonly number[]
|
|
15
|
+
|
|
16
|
+
const arr = (m: Mat4Like): ArrayLike<number> => (m as { m?: ArrayLike<number> }).m ?? (m as ArrayLike<number>)
|
|
17
|
+
const IDENTITY = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]
|
|
18
|
+
|
|
19
|
+
export class Mat4 {
|
|
20
|
+
/** The raw column-major elements (length 16). Public on purpose — read/write directly when you want
|
|
21
|
+
* the metal: `mat.m[12] += vx * dt`. The fluent methods never mutate it (they return a new Mat4). */
|
|
22
|
+
m: number[]
|
|
23
|
+
|
|
24
|
+
constructor(src?: Mat4Like) {
|
|
25
|
+
if (src === undefined) { this.m = IDENTITY.slice(); return }
|
|
26
|
+
const s = arr(src)
|
|
27
|
+
this.m = [ s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11], s[12], s[13], s[14], s[15] ]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// --- mutation ---
|
|
31
|
+
set(values: ArrayLike<number>): this { for (let i = 0; i < 16; i++) this.m[i] = values[i]; return this }
|
|
32
|
+
copy(src: Mat4Like): this { return this.set(arr(src)) }
|
|
33
|
+
|
|
34
|
+
// --- products (pure) ---
|
|
35
|
+
/** `this · b` (apply `b` first in local space, then `this`). */
|
|
36
|
+
mul(b: Mat4Like): Mat4 {
|
|
37
|
+
const a = this.m, o = arr(b), out = new Array(16)
|
|
38
|
+
const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]
|
|
39
|
+
const a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]
|
|
40
|
+
const a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]
|
|
41
|
+
const a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]
|
|
42
|
+
for (let i = 0; i < 4; i++) {
|
|
43
|
+
const b0 = o[i * 4], b1 = o[i * 4 + 1], b2 = o[i * 4 + 2], b3 = o[i * 4 + 3]
|
|
44
|
+
out[i * 4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30
|
|
45
|
+
out[i * 4 + 1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31
|
|
46
|
+
out[i * 4 + 2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32
|
|
47
|
+
out[i * 4 + 3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33
|
|
48
|
+
}
|
|
49
|
+
return fromArray(out)
|
|
50
|
+
}
|
|
51
|
+
/** `b · this` — multiply on the other side (apply `this` first, then `b`). */
|
|
52
|
+
premul(b: Mat4Like): Mat4 { return new Mat4(b).mul(this) }
|
|
53
|
+
|
|
54
|
+
invert(): Mat4 {
|
|
55
|
+
const a = this.m
|
|
56
|
+
const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]
|
|
57
|
+
const a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]
|
|
58
|
+
const a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]
|
|
59
|
+
const a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]
|
|
60
|
+
const b00 = a00 * a11 - a01 * a10, b01 = a00 * a12 - a02 * a10, b02 = a00 * a13 - a03 * a10
|
|
61
|
+
const b03 = a01 * a12 - a02 * a11, b04 = a01 * a13 - a03 * a11, b05 = a02 * a13 - a03 * a12
|
|
62
|
+
const b06 = a20 * a31 - a21 * a30, b07 = a20 * a32 - a22 * a30, b08 = a20 * a33 - a23 * a30
|
|
63
|
+
const b09 = a21 * a32 - a22 * a31, b10 = a21 * a33 - a23 * a31, b11 = a22 * a33 - a23 * a32
|
|
64
|
+
let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06
|
|
65
|
+
if (!det) return new Mat4() // singular → identity
|
|
66
|
+
det = 1 / det
|
|
67
|
+
return fromArray([
|
|
68
|
+
(a11 * b11 - a12 * b10 + a13 * b09) * det, (a02 * b10 - a01 * b11 - a03 * b09) * det,
|
|
69
|
+
(a31 * b05 - a32 * b04 + a33 * b03) * det, (a22 * b04 - a21 * b05 - a23 * b03) * det,
|
|
70
|
+
(a12 * b08 - a10 * b11 - a13 * b07) * det, (a00 * b11 - a02 * b08 + a03 * b07) * det,
|
|
71
|
+
(a32 * b02 - a30 * b05 - a33 * b01) * det, (a20 * b05 - a22 * b02 + a23 * b01) * det,
|
|
72
|
+
(a10 * b10 - a11 * b08 + a13 * b06) * det, (a01 * b08 - a00 * b10 - a03 * b06) * det,
|
|
73
|
+
(a30 * b04 - a31 * b02 + a33 * b00) * det, (a21 * b02 - a20 * b04 - a23 * b00) * det,
|
|
74
|
+
(a11 * b07 - a10 * b09 - a12 * b06) * det, (a00 * b09 - a01 * b07 + a02 * b06) * det,
|
|
75
|
+
(a31 * b01 - a30 * b03 - a32 * b00) * det, (a20 * b03 - a21 * b01 + a22 * b00) * det,
|
|
76
|
+
])
|
|
77
|
+
}
|
|
78
|
+
transpose(): Mat4 {
|
|
79
|
+
const a = this.m
|
|
80
|
+
return fromArray([ a[0], a[4], a[8], a[12], a[1], a[5], a[9], a[13], a[2], a[6], a[10], a[14], a[3], a[7], a[11], a[15] ])
|
|
81
|
+
}
|
|
82
|
+
determinant(): number {
|
|
83
|
+
const a = this.m
|
|
84
|
+
const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]
|
|
85
|
+
const a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]
|
|
86
|
+
const a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]
|
|
87
|
+
const a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15]
|
|
88
|
+
const b0 = a00 * a11 - a01 * a10, b1 = a00 * a12 - a02 * a10, b2 = a01 * a12 - a02 * a11
|
|
89
|
+
const b3 = a20 * a31 - a21 * a30, b4 = a20 * a32 - a22 * a30, b5 = a21 * a32 - a22 * a31
|
|
90
|
+
const b6 = a00 * b5 - a01 * b4 + a02 * b3, b7 = a10 * b5 - a11 * b4 + a12 * b3
|
|
91
|
+
const b8 = a20 * b2 - a21 * b1 + a22 * b0, b9 = a30 * b2 - a31 * b1 + a32 * b0
|
|
92
|
+
return a13 * b6 - a03 * b7 + a33 * b8 - a23 * b9
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// --- local-space transforms (post-multiply: this · T) ---
|
|
96
|
+
/** `this · translate(v)`. */
|
|
97
|
+
translate(v: Vec3Like): Mat4 {
|
|
98
|
+
const a = this.m, x = ax(v), y = ay(v), z = az(v), out = a.slice()
|
|
99
|
+
out[12] = a[0] * x + a[4] * y + a[8] * z + a[12]
|
|
100
|
+
out[13] = a[1] * x + a[5] * y + a[9] * z + a[13]
|
|
101
|
+
out[14] = a[2] * x + a[6] * y + a[10] * z + a[14]
|
|
102
|
+
out[15] = a[3] * x + a[7] * y + a[11] * z + a[15]
|
|
103
|
+
return fromArray(out)
|
|
104
|
+
}
|
|
105
|
+
/** `this · scale(v)`. Accepts a vector or a uniform scalar. */
|
|
106
|
+
scale(v: Vec3Like | number): Mat4 {
|
|
107
|
+
const a = this.m
|
|
108
|
+
const x = typeof v === "number" ? v : ax(v), y = typeof v === "number" ? v : ay(v), z = typeof v === "number" ? v : az(v)
|
|
109
|
+
return fromArray([
|
|
110
|
+
a[0] * x, a[1] * x, a[2] * x, a[3] * x, a[4] * y, a[5] * y, a[6] * y, a[7] * y,
|
|
111
|
+
a[8] * z, a[9] * z, a[10] * z, a[11] * z, a[12], a[13], a[14], a[15],
|
|
112
|
+
])
|
|
113
|
+
}
|
|
114
|
+
/** `this · rotate(axis, rad)`. */
|
|
115
|
+
rotate(rad: number, axis: Vec3Like): Mat4 {
|
|
116
|
+
let x = ax(axis), y = ay(axis), z = az(axis)
|
|
117
|
+
let len = Math.hypot(x, y, z)
|
|
118
|
+
if (len < 1e-6) return this.clone()
|
|
119
|
+
len = 1 / len; x *= len; y *= len; z *= len
|
|
120
|
+
const s = Math.sin(rad), c = Math.cos(rad), t = 1 - c
|
|
121
|
+
const a = this.m
|
|
122
|
+
const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3]
|
|
123
|
+
const a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7]
|
|
124
|
+
const a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11]
|
|
125
|
+
const b00 = x * x * t + c, b01 = y * x * t + z * s, b02 = z * x * t - y * s
|
|
126
|
+
const b10 = x * y * t - z * s, b11 = y * y * t + c, b12 = z * y * t + x * s
|
|
127
|
+
const b20 = x * z * t + y * s, b21 = y * z * t - x * s, b22 = z * z * t + c
|
|
128
|
+
return fromArray([
|
|
129
|
+
a00 * b00 + a10 * b01 + a20 * b02, a01 * b00 + a11 * b01 + a21 * b02, a02 * b00 + a12 * b01 + a22 * b02, a03 * b00 + a13 * b01 + a23 * b02,
|
|
130
|
+
a00 * b10 + a10 * b11 + a20 * b12, a01 * b10 + a11 * b11 + a21 * b12, a02 * b10 + a12 * b11 + a22 * b12, a03 * b10 + a13 * b11 + a23 * b12,
|
|
131
|
+
a00 * b20 + a10 * b21 + a20 * b22, a01 * b20 + a11 * b21 + a21 * b22, a02 * b20 + a12 * b21 + a22 * b22, a03 * b20 + a13 * b21 + a23 * b22,
|
|
132
|
+
a[12], a[13], a[14], a[15],
|
|
133
|
+
])
|
|
134
|
+
}
|
|
135
|
+
rotateX(rad: number): Mat4 { return this.rotate(rad, AXIS_X) }
|
|
136
|
+
rotateY(rad: number): Mat4 { return this.rotate(rad, AXIS_Y) }
|
|
137
|
+
rotateZ(rad: number): Mat4 { return this.rotate(rad, AXIS_Z) }
|
|
138
|
+
|
|
139
|
+
// --- vector transforms ---
|
|
140
|
+
/** Transform a point (applies translation + perspective divide). */
|
|
141
|
+
transformPoint(v: Vec3Like): Vec3 { return new Vec3(v).transform(this) }
|
|
142
|
+
/** Transform a direction (rotation/scale only — no translation, no divide). */
|
|
143
|
+
transformDirection(v: Vec3Like): Vec3 {
|
|
144
|
+
const a = this.m, x = ax(v), y = ay(v), z = az(v)
|
|
145
|
+
return new Vec3(a[0] * x + a[4] * y + a[8] * z, a[1] * x + a[5] * y + a[9] * z, a[2] * x + a[6] * y + a[10] * z)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// --- decomposition (folded from transform.ts) ---
|
|
149
|
+
get position(): Vec3 { const a = this.m; return new Vec3(a[12], a[13], a[14]) }
|
|
150
|
+
get scaling(): Vec3 {
|
|
151
|
+
const a = this.m
|
|
152
|
+
return new Vec3(Math.hypot(a[0], a[1], a[2]), Math.hypot(a[4], a[5], a[6]), Math.hypot(a[8], a[9], a[10]))
|
|
153
|
+
}
|
|
154
|
+
get rotation(): Quat { return quatFromColMajor(rotationOnly(this.m)) }
|
|
155
|
+
get eulerAngles(): Vec3 { return eulerFromColMajor(rotationOnly(this.m), "YXZ") }
|
|
156
|
+
toEuler(order: EulerOrder = "YXZ"): Vec3 { return eulerFromColMajor(rotationOnly(this.m), order) }
|
|
157
|
+
/** `{ position, rotation, scale }` in one shot. */
|
|
158
|
+
decompose(): { position: Vec3, rotation: Quat, scale: Vec3 } {
|
|
159
|
+
return { position: this.position, rotation: this.rotation, scale: this.scaling }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Column basis vectors (the local X / Y / Z axes in world space). */
|
|
163
|
+
get basisX(): Vec3 { const a = this.m; return new Vec3(a[0], a[1], a[2]) }
|
|
164
|
+
get basisY(): Vec3 { const a = this.m; return new Vec3(a[4], a[5], a[6]) }
|
|
165
|
+
get basisZ(): Vec3 { const a = this.m; return new Vec3(a[8], a[9], a[10]) }
|
|
166
|
+
|
|
167
|
+
// --- interop ---
|
|
168
|
+
clone(): Mat4 { return new Mat4(this) }
|
|
169
|
+
toArray(): number[] { return this.m.slice() }
|
|
170
|
+
toFloat32Array(): Float32Array { return Float32Array.from(this.m) }
|
|
171
|
+
equals(b: Mat4Like, eps = 1e-6): boolean {
|
|
172
|
+
const o = arr(b)
|
|
173
|
+
for (let i = 0; i < 16; i++) {
|
|
174
|
+
if (Math.abs(this.m[i] - o[i]) > eps * Math.max(1, Math.abs(this.m[i]), Math.abs(o[i]))) return false
|
|
175
|
+
}
|
|
176
|
+
return true
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// --- factories ---
|
|
180
|
+
static identity(): Mat4 { return new Mat4() }
|
|
181
|
+
static from(src: Mat4Like): Mat4 { return new Mat4(src) }
|
|
182
|
+
|
|
183
|
+
/** Translation · Rotation · Scale into one matrix. `scale` defaults to 1 and accepts a scalar. */
|
|
184
|
+
static compose(position: Vec3Like, rotation: QuatLike, scale: Vec3Like | number = 1): Mat4 {
|
|
185
|
+
const x = qx(rotation), y = qy(rotation), z = qz(rotation), w = qw(rotation)
|
|
186
|
+
const x2 = x + x, y2 = y + y, z2 = z + z
|
|
187
|
+
const xx = x * x2, xy = x * y2, xz = x * z2
|
|
188
|
+
const yy = y * y2, yz = y * z2, zz = z * z2
|
|
189
|
+
const wx = w * x2, wy = w * y2, wz = w * z2
|
|
190
|
+
const sx = typeof scale === "number" ? scale : ax(scale)
|
|
191
|
+
const sy = typeof scale === "number" ? scale : ay(scale)
|
|
192
|
+
const sz = typeof scale === "number" ? scale : az(scale)
|
|
193
|
+
return fromArray([
|
|
194
|
+
(1 - (yy + zz)) * sx, (xy + wz) * sx, (xz - wy) * sx, 0,
|
|
195
|
+
(xy - wz) * sy, (1 - (xx + zz)) * sy, (yz + wx) * sy, 0,
|
|
196
|
+
(xz + wy) * sz, (yz - wx) * sz, (1 - (xx + yy)) * sz, 0,
|
|
197
|
+
ax(position), ay(position), az(position), 1,
|
|
198
|
+
])
|
|
199
|
+
}
|
|
200
|
+
static fromQuat(q: QuatLike): Mat4 { return Mat4.compose(ORIGIN, q, 1) }
|
|
201
|
+
static fromTranslation(v: Vec3Like): Mat4 { const m = new Mat4(); m.m[12] = ax(v); m.m[13] = ay(v); m.m[14] = az(v); return m }
|
|
202
|
+
static fromScale(v: Vec3Like | number): Mat4 { return new Mat4().scale(v) }
|
|
203
|
+
static fromEuler(x: number, y: number, z: number, order: EulerOrder = "YXZ"): Mat4 {
|
|
204
|
+
return Mat4.fromQuat(Quat.fromEuler(x, y, z, order))
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** A view matrix that looks from `eye` toward `center` with `up`. */
|
|
208
|
+
static lookAt(eye: Vec3Like, center: Vec3Like, up: Vec3Like = Vec3.up): Mat4 {
|
|
209
|
+
const ex = ax(eye), ey = ay(eye), ez = az(eye)
|
|
210
|
+
let z0 = ex - ax(center), z1 = ey - ay(center), z2 = ez - az(center)
|
|
211
|
+
let len = Math.hypot(z0, z1, z2)
|
|
212
|
+
if (len < 1e-6) return new Mat4()
|
|
213
|
+
len = 1 / len; z0 *= len; z1 *= len; z2 *= len
|
|
214
|
+
const ux = ax(up), uy = ay(up), uz = az(up)
|
|
215
|
+
let x0 = uy * z2 - uz * z1, x1 = uz * z0 - ux * z2, x2 = ux * z1 - uy * z0
|
|
216
|
+
len = Math.hypot(x0, x1, x2)
|
|
217
|
+
if (len) { len = 1 / len; x0 *= len; x1 *= len; x2 *= len } else { x0 = x1 = x2 = 0 }
|
|
218
|
+
const y0 = z1 * x2 - z2 * x1, y1 = z2 * x0 - z0 * x2, y2 = z0 * x1 - z1 * x0
|
|
219
|
+
return fromArray([
|
|
220
|
+
x0, y0, z0, 0, x1, y1, z1, 0, x2, y2, z2, 0,
|
|
221
|
+
-(x0 * ex + x1 * ey + x2 * ez), -(y0 * ex + y1 * ey + y2 * ez), -(z0 * ex + z1 * ey + z2 * ez), 1,
|
|
222
|
+
])
|
|
223
|
+
}
|
|
224
|
+
/** A world matrix that places something at `eye` oriented to look at `target`. */
|
|
225
|
+
static targetTo(eye: Vec3Like, target: Vec3Like, up: Vec3Like = Vec3.up): Mat4 {
|
|
226
|
+
const ex = ax(eye), ey = ay(eye), ez = az(eye)
|
|
227
|
+
let z0 = ex - ax(target), z1 = ey - ay(target), z2 = ez - az(target)
|
|
228
|
+
let len = z0 * z0 + z1 * z1 + z2 * z2
|
|
229
|
+
if (len > 0) { len = 1 / Math.sqrt(len); z0 *= len; z1 *= len; z2 *= len }
|
|
230
|
+
const ux = ax(up), uy = ay(up), uz = az(up)
|
|
231
|
+
let x0 = uy * z2 - uz * z1, x1 = uz * z0 - ux * z2, x2 = ux * z1 - uy * z0
|
|
232
|
+
len = x0 * x0 + x1 * x1 + x2 * x2
|
|
233
|
+
if (len > 0) { len = 1 / Math.sqrt(len); x0 *= len; x1 *= len; x2 *= len }
|
|
234
|
+
return fromArray([
|
|
235
|
+
x0, x1, x2, 0, z1 * x2 - z2 * x1, z2 * x0 - z0 * x2, z0 * x1 - z1 * x0, 0,
|
|
236
|
+
z0, z1, z2, 0, ex, ey, ez, 1,
|
|
237
|
+
])
|
|
238
|
+
}
|
|
239
|
+
/** Perspective projection (WebGL/OpenGL clip, NDC z ∈ [−1, 1]). `fovy` is in RADIANS. */
|
|
240
|
+
static perspective(fovy: number, aspect: number, near: number, far: number): Mat4 {
|
|
241
|
+
const f = 1 / Math.tan(fovy / 2), out = new Array(16).fill(0)
|
|
242
|
+
out[0] = f / aspect; out[5] = f; out[11] = -1
|
|
243
|
+
if (far !== Infinity) { const nf = 1 / (near - far); out[10] = (far + near) * nf; out[14] = 2 * far * near * nf }
|
|
244
|
+
else { out[10] = -1; out[14] = -2 * near }
|
|
245
|
+
return fromArray(out)
|
|
246
|
+
}
|
|
247
|
+
/** Orthographic projection (WebGL/OpenGL clip, NDC z ∈ [−1, 1]). */
|
|
248
|
+
static ortho(left: number, right: number, bottom: number, top: number, near: number, far: number): Mat4 {
|
|
249
|
+
const lr = 1 / (left - right), bt = 1 / (bottom - top), nf = 1 / (near - far)
|
|
250
|
+
return fromArray([
|
|
251
|
+
-2 * lr, 0, 0, 0, 0, -2 * bt, 0, 0, 0, 0, 2 * nf, 0,
|
|
252
|
+
(left + right) * lr, (top + bottom) * bt, (far + near) * nf, 1,
|
|
253
|
+
])
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// fast path: wrap an already-built length-16 array as a Mat4 without re-copying it.
|
|
258
|
+
const fromArray = (m: number[]): Mat4 => { const r = Object.create(Mat4.prototype) as Mat4; r.m = m; return r }
|
|
259
|
+
|
|
260
|
+
/** The 3×3 rotation part with scale divided out, as a column-major length-≥11 array (for quat/euler). */
|
|
261
|
+
const rotationOnly = (a: ArrayLike<number>): number[] => {
|
|
262
|
+
const sx = Math.hypot(a[0], a[1], a[2]) || 1, sy = Math.hypot(a[4], a[5], a[6]) || 1, sz = Math.hypot(a[8], a[9], a[10]) || 1
|
|
263
|
+
const i1 = 1 / sx, i2 = 1 / sy, i3 = 1 / sz
|
|
264
|
+
return [ a[0] * i1, a[1] * i1, a[2] * i1, 0, a[4] * i2, a[5] * i2, a[6] * i2, 0, a[8] * i3, a[9] * i3, a[10] * i3 ]
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const ax = (v: Vec3Like): number => (v as { x?: number }).x ?? (v as ArrayLike<number>)[0]
|
|
268
|
+
const ay = (v: Vec3Like): number => (v as { y?: number }).y ?? (v as ArrayLike<number>)[1]
|
|
269
|
+
const az = (v: Vec3Like): number => (v as { z?: number }).z ?? (v as ArrayLike<number>)[2]
|
|
270
|
+
const qx = (q: QuatLike): number => (q as { x?: number }).x ?? (q as ArrayLike<number>)[0]
|
|
271
|
+
const qy = (q: QuatLike): number => (q as { y?: number }).y ?? (q as ArrayLike<number>)[1]
|
|
272
|
+
const qz = (q: QuatLike): number => (q as { z?: number }).z ?? (q as ArrayLike<number>)[2]
|
|
273
|
+
const qw = (q: QuatLike): number => (q as { w?: number }).w ?? (q as ArrayLike<number>)[3]
|
|
274
|
+
|
|
275
|
+
const AXIS_X: Vec3Like = [ 1, 0, 0 ]
|
|
276
|
+
const AXIS_Y: Vec3Like = [ 0, 1, 0 ]
|
|
277
|
+
const AXIS_Z: Vec3Like = [ 0, 0, 1 ]
|
|
278
|
+
const ORIGIN: Vec3Like = [ 0, 0, 0 ]
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// Fluent quaternion (x, y, z, w) — the rotation type for the 3D node API. Same contract as Vec: mutable
|
|
2
|
+
// fields, PURE methods (each returns a new Quat). Accepts a `Quat` or a raw `[x, y, z, w]` everywhere.
|
|
3
|
+
//
|
|
4
|
+
// Convention: standard right-handed, column-major (matches gl-matrix and three.js). Euler angles are in
|
|
5
|
+
// DEGREES. The euler order default is "YXZ" (yaw-pitch-roll, the usual choice for cameras / characters).
|
|
6
|
+
//
|
|
7
|
+
// NB: this fixes a transpose bug in the old `transform.ts` euler extraction, which read the matrix with
|
|
8
|
+
// row/col swapped and so returned a NEGATED angle for a positive rotation. `eulerFromColMajor` below uses
|
|
9
|
+
// the correct column-major element mapping, so `node.eulerAngles` now round-trips with the setter.
|
|
10
|
+
|
|
11
|
+
import { Vec3, cx, cy, cz, type Vec3Like } from "./vec"
|
|
12
|
+
|
|
13
|
+
export type QuatLike = Quat | readonly [number, number, number, number] | number[] | Float32Array
|
|
14
|
+
export type EulerOrder = "XYZ" | "YXZ" | "ZXY" | "ZYX" | "YZX" | "XZY"
|
|
15
|
+
|
|
16
|
+
/** Read the w / 4th component of any quaternion-like (wrapper `.w`, or array-like `[3]`). */
|
|
17
|
+
export const cw = (q: { w?: number } | ArrayLike<number>): number => (q as { w?: number }).w ?? (q as ArrayLike<number>)[3]
|
|
18
|
+
const DEG2RAD = Math.PI / 180
|
|
19
|
+
const RAD2DEG = 180 / Math.PI
|
|
20
|
+
const EPS = 1e-6
|
|
21
|
+
const approx = (a: number, b: number, eps: number): boolean => Math.abs(a - b) <= eps * Math.max(1, Math.abs(a), Math.abs(b))
|
|
22
|
+
const clamp1 = (v: number): number => (v < -1 ? -1 : v > 1 ? 1 : v)
|
|
23
|
+
|
|
24
|
+
export class Quat {
|
|
25
|
+
x: number
|
|
26
|
+
y: number
|
|
27
|
+
z: number
|
|
28
|
+
w: number
|
|
29
|
+
|
|
30
|
+
constructor(x: number | QuatLike = 0, y = 0, z = 0, w = 1) {
|
|
31
|
+
if (typeof x === "number") { this.x = x; this.y = y; this.z = z; this.w = w }
|
|
32
|
+
else { this.x = cx(x); this.y = cy(x); this.z = cz(x); this.w = cw(x) }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// --- mutation ---
|
|
36
|
+
set(x: number, y: number, z: number, w: number): this { this.x = x; this.y = y; this.z = z; this.w = w; return this }
|
|
37
|
+
copy(q: QuatLike): this { this.x = cx(q); this.y = cy(q); this.z = cz(q); this.w = cw(q); return this }
|
|
38
|
+
|
|
39
|
+
// --- pure ops ---
|
|
40
|
+
/** Hamilton product `this ⊗ b` (apply `b` first, then `this`). */
|
|
41
|
+
mul(b: QuatLike): Quat {
|
|
42
|
+
const ax = this.x, ay = this.y, az = this.z, aw = this.w
|
|
43
|
+
const bx = cx(b), by = cy(b), bz = cz(b), bw = cw(b)
|
|
44
|
+
return new Quat(
|
|
45
|
+
ax * bw + aw * bx + ay * bz - az * by,
|
|
46
|
+
ay * bw + aw * by + az * bx - ax * bz,
|
|
47
|
+
az * bw + aw * bz + ax * by - ay * bx,
|
|
48
|
+
aw * bw - ax * bx - ay * by - az * bz,
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
normalize(): Quat {
|
|
52
|
+
let l = Math.hypot(this.x, this.y, this.z, this.w)
|
|
53
|
+
if (l === 0) return new Quat(0, 0, 0, 1)
|
|
54
|
+
l = 1 / l
|
|
55
|
+
return new Quat(this.x * l, this.y * l, this.z * l, this.w * l)
|
|
56
|
+
}
|
|
57
|
+
conjugate(): Quat { return new Quat(-this.x, -this.y, -this.z, this.w) }
|
|
58
|
+
/** Inverse rotation. For a unit quaternion this equals `conjugate()`. */
|
|
59
|
+
invert(): Quat {
|
|
60
|
+
const d = this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w
|
|
61
|
+
if (d === 0) return new Quat(0, 0, 0, 0)
|
|
62
|
+
const i = 1 / d
|
|
63
|
+
return new Quat(-this.x * i, -this.y * i, -this.z * i, this.w * i)
|
|
64
|
+
}
|
|
65
|
+
dot(b: QuatLike): number { return this.x * cx(b) + this.y * cy(b) + this.z * cz(b) + this.w * cw(b) }
|
|
66
|
+
/** Spherical interpolation toward `b` by `t ∈ [0, 1]`. */
|
|
67
|
+
slerp(b: QuatLike, t: number): Quat {
|
|
68
|
+
const ax = this.x, ay = this.y, az = this.z, aw = this.w
|
|
69
|
+
let bx = cx(b), by = cy(b), bz = cz(b), bw = cw(b)
|
|
70
|
+
let cosom = ax * bx + ay * by + az * bz + aw * bw
|
|
71
|
+
if (cosom < 0) { cosom = -cosom; bx = -bx; by = -by; bz = -bz; bw = -bw }
|
|
72
|
+
let s0: number, s1: number
|
|
73
|
+
if (1 - cosom > EPS) {
|
|
74
|
+
const omega = Math.acos(cosom), sinom = Math.sin(omega)
|
|
75
|
+
s0 = Math.sin((1 - t) * omega) / sinom
|
|
76
|
+
s1 = Math.sin(t * omega) / sinom
|
|
77
|
+
} else { s0 = 1 - t; s1 = t }
|
|
78
|
+
return new Quat(s0 * ax + s1 * bx, s0 * ay + s1 * by, s0 * az + s1 * bz, s0 * aw + s1 * bw)
|
|
79
|
+
}
|
|
80
|
+
/** Angular distance (radians) between two unit quaternions. */
|
|
81
|
+
angle(b: QuatLike): number {
|
|
82
|
+
const d = this.dot(b)
|
|
83
|
+
return Math.acos(clamp1(2 * d * d - 1))
|
|
84
|
+
}
|
|
85
|
+
/** Rotate a vector by this quaternion → a new `Vec3`. */
|
|
86
|
+
rotateVec3(v: Vec3Like): Vec3 { return new Vec3(v).rotate(this) }
|
|
87
|
+
|
|
88
|
+
/** Extract euler angles (DEGREES) in the given order. */
|
|
89
|
+
toEuler(order: EulerOrder = "YXZ"): Vec3 {
|
|
90
|
+
// build the (column-major) rotation matrix entries from the unit quaternion, then extract.
|
|
91
|
+
const x = this.x, y = this.y, z = this.z, w = this.w
|
|
92
|
+
const x2 = x + x, y2 = y + y, z2 = z + z
|
|
93
|
+
const xx = x * x2, xy = x * y2, xz = x * z2
|
|
94
|
+
const yy = y * y2, yz = y * z2, zz = z * z2
|
|
95
|
+
const wx = w * x2, wy = w * y2, wz = w * z2
|
|
96
|
+
const m = [
|
|
97
|
+
1 - (yy + zz), xy + wz, xz - wy, 0,
|
|
98
|
+
xy - wz, 1 - (xx + zz), yz + wx, 0,
|
|
99
|
+
xz + wy, yz - wx, 1 - (xx + yy), 0,
|
|
100
|
+
]
|
|
101
|
+
return eulerFromColMajor(m, order)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// --- interop ---
|
|
105
|
+
equals(b: QuatLike, eps = 1e-6): boolean {
|
|
106
|
+
return approx(this.x, cx(b), eps) && approx(this.y, cy(b), eps) && approx(this.z, cz(b), eps) && approx(this.w, cw(b), eps)
|
|
107
|
+
}
|
|
108
|
+
/** True if the two unit quaternions represent the same rotation (±q are equal). */
|
|
109
|
+
sameRotation(b: QuatLike, eps = 1e-6): boolean { return Math.abs(this.dot(b)) >= 1 - eps }
|
|
110
|
+
clone(): Quat { return new Quat(this.x, this.y, this.z, this.w) }
|
|
111
|
+
toArray(): [number, number, number, number] { return [ this.x, this.y, this.z, this.w ] }
|
|
112
|
+
*[Symbol.iterator](): Iterator<number> { yield this.x; yield this.y; yield this.z; yield this.w }
|
|
113
|
+
|
|
114
|
+
// --- factories ---
|
|
115
|
+
static from(q: QuatLike): Quat { return new Quat(cx(q), cy(q), cz(q), cw(q)) }
|
|
116
|
+
static get identity(): Quat { return new Quat(0, 0, 0, 1) }
|
|
117
|
+
|
|
118
|
+
/** From euler angles in DEGREES (default order "YXZ"). Inverts `toEuler`. */
|
|
119
|
+
static fromEuler(x: number, y: number, z: number, order: EulerOrder = "YXZ"): Quat {
|
|
120
|
+
const c1 = Math.cos(x * DEG2RAD / 2), c2 = Math.cos(y * DEG2RAD / 2), c3 = Math.cos(z * DEG2RAD / 2)
|
|
121
|
+
const s1 = Math.sin(x * DEG2RAD / 2), s2 = Math.sin(y * DEG2RAD / 2), s3 = Math.sin(z * DEG2RAD / 2)
|
|
122
|
+
switch (order) {
|
|
123
|
+
case "XYZ": return new Quat(s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3)
|
|
124
|
+
case "YXZ": return new Quat(s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3)
|
|
125
|
+
case "ZXY": return new Quat(s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3)
|
|
126
|
+
case "ZYX": return new Quat(s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3)
|
|
127
|
+
case "YZX": return new Quat(s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3)
|
|
128
|
+
case "XZY": return new Quat(s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** From a rotation `axis` and an angle in RADIANS. The axis is normalized for you. */
|
|
133
|
+
static fromAxisAngle(axis: Vec3Like, rad: number): Quat {
|
|
134
|
+
let ax = cx(axis), ay = cy(axis), az = cz(axis)
|
|
135
|
+
const l = Math.hypot(ax, ay, az)
|
|
136
|
+
if (l === 0) return new Quat(0, 0, 0, 1)
|
|
137
|
+
ax /= l; ay /= l; az /= l
|
|
138
|
+
const s = Math.sin(rad / 2)
|
|
139
|
+
return new Quat(ax * s, ay * s, az * s, Math.cos(rad / 2))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** The shortest rotation that takes direction `a` onto direction `b`. */
|
|
143
|
+
static fromTo(a: Vec3Like, b: Vec3Like): Quat {
|
|
144
|
+
const A = new Vec3(a).normalize(), B = new Vec3(b).normalize()
|
|
145
|
+
const d = A.dot(B)
|
|
146
|
+
if (d > 0.999999) return new Quat(0, 0, 0, 1)
|
|
147
|
+
if (d < -0.999999) {
|
|
148
|
+
// antiparallel: rotate 180° about any axis ⟂ A.
|
|
149
|
+
let axis = new Vec3(1, 0, 0).cross(A)
|
|
150
|
+
if (axis.lengthSq() < 1e-12) axis = new Vec3(0, 1, 0).cross(A)
|
|
151
|
+
return Quat.fromAxisAngle(axis.normalize(), Math.PI)
|
|
152
|
+
}
|
|
153
|
+
const c = A.cross(B)
|
|
154
|
+
return new Quat(c.x, c.y, c.z, 1 + d).normalize()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** A rotation whose forward (−Z) points along `forward`, with `up` as the reference up axis. */
|
|
158
|
+
static lookRotation(forward: Vec3Like, up: Vec3Like = Vec3.up): Quat {
|
|
159
|
+
const zc = new Vec3(forward).normalize().negate() // local +Z basis (forward is −Z)
|
|
160
|
+
let xc = new Vec3(up).cross(zc)
|
|
161
|
+
if (xc.lengthSq() < 1e-12) xc = new Vec3(up).cross(zc.add([ 1e-4, 0, 0 ]))
|
|
162
|
+
xc = xc.normalize()
|
|
163
|
+
const yc = zc.cross(xc)
|
|
164
|
+
return quatFromColMajor([ xc.x, xc.y, xc.z, 0, yc.x, yc.y, yc.z, 0, zc.x, zc.y, zc.z ])
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Quaternion from a column-major rotation matrix (≥3×3, indices 0,1,2,4,5,6,8,9,10). gl-matrix's
|
|
169
|
+
* `getRotation` algorithm — the exact inverse of building a matrix from a quaternion. */
|
|
170
|
+
export const quatFromColMajor = (m: ArrayLike<number>): Quat => {
|
|
171
|
+
const m00 = m[0], m01 = m[1], m02 = m[2]
|
|
172
|
+
const m10 = m[4], m11 = m[5], m12 = m[6]
|
|
173
|
+
const m20 = m[8], m21 = m[9], m22 = m[10]
|
|
174
|
+
const trace = m00 + m11 + m22
|
|
175
|
+
let S: number
|
|
176
|
+
if (trace > 0) {
|
|
177
|
+
S = Math.sqrt(trace + 1) * 2
|
|
178
|
+
return new Quat((m12 - m21) / S, (m20 - m02) / S, (m01 - m10) / S, 0.25 * S)
|
|
179
|
+
} else if (m00 > m11 && m00 > m22) {
|
|
180
|
+
S = Math.sqrt(1 + m00 - m11 - m22) * 2
|
|
181
|
+
return new Quat(0.25 * S, (m01 + m10) / S, (m20 + m02) / S, (m12 - m21) / S)
|
|
182
|
+
} else if (m11 > m22) {
|
|
183
|
+
S = Math.sqrt(1 + m11 - m00 - m22) * 2
|
|
184
|
+
return new Quat((m01 + m10) / S, 0.25 * S, (m12 + m21) / S, (m20 - m02) / S)
|
|
185
|
+
} else {
|
|
186
|
+
S = Math.sqrt(1 + m22 - m00 - m11) * 2
|
|
187
|
+
return new Quat((m20 + m02) / S, (m12 + m21) / S, 0.25 * S, (m01 - m10) / S)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Euler angles (DEGREES) from a column-major rotation matrix, standard three.js extraction.
|
|
192
|
+
* Element naming follows the column-major layout: mRC = m[(C-1)*4 + (R-1)]. */
|
|
193
|
+
export const eulerFromColMajor = (m: ArrayLike<number>, order: EulerOrder = "YXZ"): Vec3 => {
|
|
194
|
+
const m11 = m[0], m21 = m[1], m31 = m[2]
|
|
195
|
+
const m12 = m[4], m22 = m[5], m32 = m[6]
|
|
196
|
+
const m13 = m[8], m23 = m[9], m33 = m[10]
|
|
197
|
+
let x = 0, y = 0, z = 0
|
|
198
|
+
const LIM = 0.9999999
|
|
199
|
+
switch (order) {
|
|
200
|
+
case "XYZ":
|
|
201
|
+
y = Math.asin(clamp1(m13))
|
|
202
|
+
if (Math.abs(m13) < LIM) { x = Math.atan2(-m23, m33); z = Math.atan2(-m12, m11) }
|
|
203
|
+
else { x = Math.atan2(m32, m22); z = 0 }
|
|
204
|
+
break
|
|
205
|
+
case "YXZ":
|
|
206
|
+
x = Math.asin(-clamp1(m23))
|
|
207
|
+
if (Math.abs(m23) < LIM) { y = Math.atan2(m13, m33); z = Math.atan2(m21, m22) }
|
|
208
|
+
else { y = Math.atan2(-m31, m11); z = 0 }
|
|
209
|
+
break
|
|
210
|
+
case "ZXY":
|
|
211
|
+
x = Math.asin(clamp1(m32))
|
|
212
|
+
if (Math.abs(m32) < LIM) { y = Math.atan2(-m31, m33); z = Math.atan2(-m12, m22) }
|
|
213
|
+
else { y = 0; z = Math.atan2(m21, m11) }
|
|
214
|
+
break
|
|
215
|
+
case "ZYX":
|
|
216
|
+
y = Math.asin(-clamp1(m31))
|
|
217
|
+
if (Math.abs(m31) < LIM) { x = Math.atan2(m32, m33); z = Math.atan2(m21, m11) }
|
|
218
|
+
else { x = 0; z = Math.atan2(-m12, m22) }
|
|
219
|
+
break
|
|
220
|
+
case "YZX":
|
|
221
|
+
z = Math.asin(clamp1(m21))
|
|
222
|
+
if (Math.abs(m21) < LIM) { x = Math.atan2(-m23, m22); y = Math.atan2(-m31, m11) }
|
|
223
|
+
else { x = 0; y = Math.atan2(m13, m33) }
|
|
224
|
+
break
|
|
225
|
+
case "XZY":
|
|
226
|
+
z = Math.asin(-clamp1(m12))
|
|
227
|
+
if (Math.abs(m12) < LIM) { x = Math.atan2(m32, m22); y = Math.atan2(m13, m11) }
|
|
228
|
+
else { x = Math.atan2(-m23, m33); y = 0 }
|
|
229
|
+
break
|
|
230
|
+
}
|
|
231
|
+
return new Vec3(x * RAD2DEG, y * RAD2DEG, z * RAD2DEG)
|
|
232
|
+
}
|