glmaths 0.0.1
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 +952 -0
- package/benchmark.js +747 -0
- package/dist/cjs/glmaths.d.ts +12053 -0
- package/dist/cjs/glmaths.js +6496 -0
- package/dist/cjs/glmaths.js.map +1 -0
- package/dist/cjs/glmaths.min.js +2 -0
- package/dist/cjs/glmaths.min.js.map +1 -0
- package/dist/esm/glmaths.d.ts +12053 -0
- package/dist/esm/glmaths.js +6453 -0
- package/dist/esm/glmaths.js.map +1 -0
- package/dist/esm/glmaths.min.js +2 -0
- package/dist/esm/glmaths.min.js.map +1 -0
- package/dist/glmaths.d.ts +12053 -0
- package/dist/glmaths.js +6501 -0
- package/dist/glmaths.js.map +1 -0
- package/dist/glmaths.min.js +2 -0
- package/dist/glmaths.min.js.map +1 -0
- package/docs.js +64 -0
- package/package.json +37 -0
- package/rollup.config.js +70 -0
- package/src/index.ts +19 -0
- package/src/internalUtils.ts +78 -0
- package/src/mat2.ts +324 -0
- package/src/mat2x3.ts +328 -0
- package/src/mat3.ts +629 -0
- package/src/mat4.ts +1319 -0
- package/src/quat.ts +819 -0
- package/src/quat2.ts +412 -0
- package/src/utils.ts +248 -0
- package/src/vec2.ts +798 -0
- package/src/vec3.ts +1069 -0
- package/src/vec4.ts +810 -0
- package/tests/mat2.test.ts +277 -0
- package/tests/mat2x3.test.ts +217 -0
- package/tests/mat3.test.ts +306 -0
- package/tests/mat4.test.ts +586 -0
- package/tests/quat.test.ts +418 -0
- package/tests/quat2.test.ts +222 -0
- package/tests/utils.test.ts +115 -0
- package/tests/vec2.test.ts +617 -0
- package/tests/vec3.test.ts +649 -0
- package/tests/vec4.test.ts +390 -0
- package/tsconfig.json +17 -0
- package/tsconfig.test.json +8 -0
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import glmaths, { Mat4, mat4, Vec3, Vec4, Quat } from '../dist/esm/glmaths'
|
|
4
|
+
|
|
5
|
+
function closeTo(actual: number, expected: number, numDigits = 5) {
|
|
6
|
+
const pass = Math.abs(actual - expected) < Math.pow(10, -numDigits) / 2
|
|
7
|
+
assert.ok(pass, `expected ${actual} to be close to ${expected}`)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('Mat4', () => {
|
|
11
|
+
describe('constructor', () => {
|
|
12
|
+
it('creates zero matrix by default', () => {
|
|
13
|
+
const m = new Mat4()
|
|
14
|
+
for (let i = 0; i < 16; i++) assert.strictEqual(m[i], 0)
|
|
15
|
+
})
|
|
16
|
+
it('creates with given values', () => {
|
|
17
|
+
const m = Mat4.identity
|
|
18
|
+
assert.strictEqual(m[0], 1)
|
|
19
|
+
assert.strictEqual(m[5], 1)
|
|
20
|
+
assert.strictEqual(m[10], 1)
|
|
21
|
+
assert.strictEqual(m[15], 1)
|
|
22
|
+
})
|
|
23
|
+
it('extends Float32Array with length 16', () => {
|
|
24
|
+
assert.strictEqual(new Mat4().length, 16)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
describe('identity', () => {
|
|
29
|
+
it('is correct', () => {
|
|
30
|
+
const m = Mat4.identity
|
|
31
|
+
for (let i = 0; i < 16; i++) {
|
|
32
|
+
assert.strictEqual(m[i], i % 5 === 0 ? 1 : 0)
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('clone', () => {
|
|
38
|
+
it('clones independently', () => {
|
|
39
|
+
const a = Mat4.identity
|
|
40
|
+
const b = a.clone()
|
|
41
|
+
b[0] = 99
|
|
42
|
+
assert.strictEqual(a[0], 1)
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('transpose', () => {
|
|
47
|
+
it('transposes in-place', () => {
|
|
48
|
+
const m = new Mat4(
|
|
49
|
+
1, 2, 3, 4,
|
|
50
|
+
5, 6, 7, 8,
|
|
51
|
+
9, 10, 11, 12,
|
|
52
|
+
13, 14, 15, 16
|
|
53
|
+
)
|
|
54
|
+
const r = m.transpose()
|
|
55
|
+
assert.strictEqual(r[1], 5)
|
|
56
|
+
assert.strictEqual(r[4], 2)
|
|
57
|
+
assert.strictEqual(r[2], 9)
|
|
58
|
+
assert.strictEqual(r[8], 3)
|
|
59
|
+
})
|
|
60
|
+
it('transposes to out', () => {
|
|
61
|
+
const m = Mat4.identity
|
|
62
|
+
const out = new Mat4()
|
|
63
|
+
m.transpose(out)
|
|
64
|
+
assert.strictEqual(out.exactEquals(Mat4.identity), true)
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
describe('invert', () => {
|
|
69
|
+
it('inverts identity to identity', () => {
|
|
70
|
+
const out = new Mat4()
|
|
71
|
+
Mat4.identity.invert(out)
|
|
72
|
+
for (let i = 0; i < 16; i++) {
|
|
73
|
+
closeTo(out[i], i % 5 === 0 ? 1 : 0)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
it('m * m^-1 = identity', () => {
|
|
77
|
+
const m = Mat4.fromRotationTranslationScale(
|
|
78
|
+
Quat.fromAxisAngle(new Vec3(0, 1, 0), Math.PI / 4),
|
|
79
|
+
new Vec3(1, 2, 3),
|
|
80
|
+
new Vec3(1, 1, 1)
|
|
81
|
+
)
|
|
82
|
+
const inv = new Mat4()
|
|
83
|
+
m.invert(inv)
|
|
84
|
+
const result = new Mat4()
|
|
85
|
+
m.multiply(inv!, result)
|
|
86
|
+
for (let i = 0; i < 16; i++) {
|
|
87
|
+
closeTo(result[i], i % 5 === 0 ? 1 : 0, 4)
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
it('returns null for singular matrix', () => {
|
|
91
|
+
const m = new Mat4()
|
|
92
|
+
assert.strictEqual(m.invert(new Mat4()), null)
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
describe('determinant', () => {
|
|
97
|
+
it('identity determinant is 1', () => {
|
|
98
|
+
closeTo(Mat4.identity.determinant(), 1)
|
|
99
|
+
})
|
|
100
|
+
it('scaling matrix determinant is product of scales', () => {
|
|
101
|
+
const m = Mat4.fromScaling(new Vec3(2, 3, 4))
|
|
102
|
+
closeTo(m.determinant(), 24)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe('multiply', () => {
|
|
107
|
+
it('identity * A = A', () => {
|
|
108
|
+
const a = Mat4.fromTranslation(new Vec3(1, 2, 3))
|
|
109
|
+
const out = new Mat4()
|
|
110
|
+
Mat4.identity.multiply(a, out)
|
|
111
|
+
for (let i = 0; i < 16; i++) closeTo(out[i], a[i])
|
|
112
|
+
})
|
|
113
|
+
it('mul alias works', () => {
|
|
114
|
+
const m = Mat4.identity
|
|
115
|
+
const out = new Mat4()
|
|
116
|
+
m.mul(Mat4.identity, out)
|
|
117
|
+
closeTo(out[0], 1)
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
describe('translate', () => {
|
|
122
|
+
it('translates identity', () => {
|
|
123
|
+
const m = Mat4.identity
|
|
124
|
+
const r = m.translate(new Vec3(5, 10, 15))
|
|
125
|
+
assert.strictEqual(r[12], 5)
|
|
126
|
+
assert.strictEqual(r[13], 10)
|
|
127
|
+
assert.strictEqual(r[14], 15)
|
|
128
|
+
})
|
|
129
|
+
it('translates to out', () => {
|
|
130
|
+
const m = Mat4.identity
|
|
131
|
+
const out = new Mat4()
|
|
132
|
+
m.translate(new Vec3(5, 10, 15), out)
|
|
133
|
+
assert.strictEqual(out[12], 5)
|
|
134
|
+
assert.strictEqual(out[13], 10)
|
|
135
|
+
assert.strictEqual(out[14], 15)
|
|
136
|
+
assert.strictEqual(m[12], 0)
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe('scale', () => {
|
|
141
|
+
it('scales identity', () => {
|
|
142
|
+
const m = Mat4.identity
|
|
143
|
+
const r = m.scale(new Vec3(2, 3, 4))
|
|
144
|
+
assert.strictEqual(r[0], 2)
|
|
145
|
+
assert.strictEqual(r[5], 3)
|
|
146
|
+
assert.strictEqual(r[10], 4)
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
describe('rotate', () => {
|
|
151
|
+
it('rotates identity around Y axis', () => {
|
|
152
|
+
const m = Mat4.identity
|
|
153
|
+
const r = m.rotate(Math.PI / 2, new Vec3(0, 1, 0))
|
|
154
|
+
closeTo(r![0], 0)
|
|
155
|
+
closeTo(r![8], 1)
|
|
156
|
+
})
|
|
157
|
+
it('returns null for zero axis', () => {
|
|
158
|
+
const m = Mat4.identity
|
|
159
|
+
assert.strictEqual(m.rotate(Math.PI / 2, new Vec3(0, 0, 0)), null)
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
describe('rotateX / rotateY / rotateZ', () => {
|
|
164
|
+
it('rotateX by PI/2', () => {
|
|
165
|
+
const m = Mat4.identity
|
|
166
|
+
const r = m.rotateX(Math.PI / 2)
|
|
167
|
+
closeTo(r[5], 0)
|
|
168
|
+
closeTo(r[6], 1)
|
|
169
|
+
closeTo(r[9], -1)
|
|
170
|
+
closeTo(r[10], 0)
|
|
171
|
+
})
|
|
172
|
+
it('rotateY by PI/2', () => {
|
|
173
|
+
const m = Mat4.identity
|
|
174
|
+
const r = m.rotateY(Math.PI / 2)
|
|
175
|
+
closeTo(r[0], 0)
|
|
176
|
+
closeTo(r[2], -1)
|
|
177
|
+
closeTo(r[8], 1)
|
|
178
|
+
closeTo(r[10], 0)
|
|
179
|
+
})
|
|
180
|
+
it('rotateZ by PI/2', () => {
|
|
181
|
+
const m = Mat4.identity
|
|
182
|
+
const r = m.rotateZ(Math.PI / 2)
|
|
183
|
+
closeTo(r[0], 0)
|
|
184
|
+
closeTo(r[1], 1)
|
|
185
|
+
closeTo(r[4], -1)
|
|
186
|
+
closeTo(r[5], 0)
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
describe('getTranslation', () => {
|
|
191
|
+
it('extracts translation', () => {
|
|
192
|
+
const m = Mat4.fromTranslation(new Vec3(5, 10, 15))
|
|
193
|
+
const t = m.getTranslation()
|
|
194
|
+
assert.strictEqual(t[0], 5)
|
|
195
|
+
assert.strictEqual(t[1], 10)
|
|
196
|
+
assert.strictEqual(t[2], 15)
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
describe('getScaling', () => {
|
|
201
|
+
it('extracts scaling', () => {
|
|
202
|
+
const m = Mat4.fromScaling(new Vec3(2, 3, 4))
|
|
203
|
+
const s = m.getScaling()
|
|
204
|
+
closeTo(s[0], 2)
|
|
205
|
+
closeTo(s[1], 3)
|
|
206
|
+
closeTo(s[2], 4)
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
describe('getRotation', () => {
|
|
211
|
+
it('extracts rotation from identity', () => {
|
|
212
|
+
const q = Mat4.identity.getRotation()
|
|
213
|
+
closeTo(q[0], 0)
|
|
214
|
+
closeTo(q[1], 0)
|
|
215
|
+
closeTo(q[2], 0)
|
|
216
|
+
closeTo(q[3], 1)
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
describe('decompose', () => {
|
|
221
|
+
it('decomposes a TRS matrix', () => {
|
|
222
|
+
const q = Quat.fromAxisAngle(new Vec3(0, 1, 0), Math.PI / 4)
|
|
223
|
+
const t = new Vec3(1, 2, 3)
|
|
224
|
+
const s = new Vec3(2, 3, 4)
|
|
225
|
+
const m = Mat4.fromRotationTranslationScale(q, t, s)
|
|
226
|
+
|
|
227
|
+
const outQ = new Quat()
|
|
228
|
+
const outT = new Vec3()
|
|
229
|
+
const outS = new Vec3()
|
|
230
|
+
m.decompose(outQ, outT, outS)
|
|
231
|
+
|
|
232
|
+
closeTo(outT[0], 1)
|
|
233
|
+
closeTo(outT[1], 2)
|
|
234
|
+
closeTo(outT[2], 3)
|
|
235
|
+
closeTo(outS[0], 2)
|
|
236
|
+
closeTo(outS[1], 3)
|
|
237
|
+
closeTo(outS[2], 4)
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
describe('static fromTranslation', () => {
|
|
242
|
+
it('creates translation matrix', () => {
|
|
243
|
+
const m = Mat4.fromTranslation(new Vec3(1, 2, 3))
|
|
244
|
+
assert.strictEqual(m[12], 1)
|
|
245
|
+
assert.strictEqual(m[13], 2)
|
|
246
|
+
assert.strictEqual(m[14], 3)
|
|
247
|
+
assert.strictEqual(m[0], 1)
|
|
248
|
+
assert.strictEqual(m[15], 1)
|
|
249
|
+
})
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
describe('static fromScaling', () => {
|
|
253
|
+
it('creates scaling matrix', () => {
|
|
254
|
+
const m = Mat4.fromScaling(new Vec3(2, 3, 4))
|
|
255
|
+
assert.strictEqual(m[0], 2)
|
|
256
|
+
assert.strictEqual(m[5], 3)
|
|
257
|
+
assert.strictEqual(m[10], 4)
|
|
258
|
+
assert.strictEqual(m[15], 1)
|
|
259
|
+
})
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
describe('static fromRotation', () => {
|
|
263
|
+
it('creates rotation matrix', () => {
|
|
264
|
+
const m = Mat4.fromRotation(Math.PI / 2, new Vec3(0, 1, 0))
|
|
265
|
+
assert.notStrictEqual(m, null)
|
|
266
|
+
closeTo(m![0], 0)
|
|
267
|
+
assert.strictEqual(m![15], 1)
|
|
268
|
+
})
|
|
269
|
+
it('returns null for zero axis', () => {
|
|
270
|
+
assert.strictEqual(Mat4.fromRotation(Math.PI, new Vec3(0, 0, 0)), null)
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
describe('static axis rotation', () => {
|
|
275
|
+
it('fromXRotation', () => {
|
|
276
|
+
const m = Mat4.fromXRotation(Math.PI / 2)
|
|
277
|
+
closeTo(m[5], 0)
|
|
278
|
+
closeTo(m[6], 1)
|
|
279
|
+
})
|
|
280
|
+
it('fromYRotation', () => {
|
|
281
|
+
const m = Mat4.fromYRotation(Math.PI / 2)
|
|
282
|
+
closeTo(m[0], 0)
|
|
283
|
+
closeTo(m[8], 1)
|
|
284
|
+
})
|
|
285
|
+
it('fromZRotation', () => {
|
|
286
|
+
const m = Mat4.fromZRotation(Math.PI / 2)
|
|
287
|
+
closeTo(m[0], 0)
|
|
288
|
+
closeTo(m[1], 1)
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
describe('static fromRotationTranslation', () => {
|
|
293
|
+
it('creates combined matrix', () => {
|
|
294
|
+
const q = Quat.identity
|
|
295
|
+
const v = new Vec3(1, 2, 3)
|
|
296
|
+
const m = Mat4.fromRotationTranslation(q, v)
|
|
297
|
+
assert.strictEqual(m[12], 1)
|
|
298
|
+
assert.strictEqual(m[13], 2)
|
|
299
|
+
assert.strictEqual(m[14], 3)
|
|
300
|
+
closeTo(m[0], 1)
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
describe('static fromRotationTranslationScale', () => {
|
|
305
|
+
it('creates TRS matrix', () => {
|
|
306
|
+
const m = Mat4.fromRotationTranslationScale(
|
|
307
|
+
Quat.identity,
|
|
308
|
+
new Vec3(1, 2, 3),
|
|
309
|
+
new Vec3(2, 3, 4)
|
|
310
|
+
)
|
|
311
|
+
assert.strictEqual(m[12], 1)
|
|
312
|
+
closeTo(m[0], 2)
|
|
313
|
+
closeTo(m[5], 3)
|
|
314
|
+
closeTo(m[10], 4)
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
describe('static fromQuat', () => {
|
|
319
|
+
it('identity quat gives identity matrix', () => {
|
|
320
|
+
const m = Mat4.fromQuat(Quat.identity)
|
|
321
|
+
closeTo(m[0], 1)
|
|
322
|
+
closeTo(m[5], 1)
|
|
323
|
+
closeTo(m[10], 1)
|
|
324
|
+
assert.strictEqual(m[15], 1)
|
|
325
|
+
})
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
describe('static perspectiveNO', () => {
|
|
329
|
+
it('creates perspective matrix with correct structure', () => {
|
|
330
|
+
const fovy = Math.PI / 4, aspect = 16 / 9, near = 0.1, far = 100
|
|
331
|
+
const m = Mat4.perspectiveNO(fovy, aspect, near, far)
|
|
332
|
+
const f = 1.0 / Math.tan(fovy / 2)
|
|
333
|
+
const nf = 1 / (near - far)
|
|
334
|
+
closeTo(m[0], f / aspect)
|
|
335
|
+
closeTo(m[5], f)
|
|
336
|
+
closeTo(m[10], (far + near) * nf)
|
|
337
|
+
assert.strictEqual(m[11], -1)
|
|
338
|
+
closeTo(m[14], 2 * far * near * nf)
|
|
339
|
+
assert.strictEqual(m[15], 0)
|
|
340
|
+
assert.strictEqual(m[1], 0)
|
|
341
|
+
assert.strictEqual(m[2], 0)
|
|
342
|
+
assert.strictEqual(m[3], 0)
|
|
343
|
+
assert.strictEqual(m[4], 0)
|
|
344
|
+
assert.strictEqual(m[6], 0)
|
|
345
|
+
assert.strictEqual(m[7], 0)
|
|
346
|
+
assert.strictEqual(m[8], 0)
|
|
347
|
+
assert.strictEqual(m[9], 0)
|
|
348
|
+
assert.strictEqual(m[12], 0)
|
|
349
|
+
assert.strictEqual(m[13], 0)
|
|
350
|
+
})
|
|
351
|
+
it('handles infinite far plane', () => {
|
|
352
|
+
const fovy = Math.PI / 4, aspect = 16 / 9, near = 0.1
|
|
353
|
+
const m = Mat4.perspectiveNO(fovy, aspect, near, Infinity)
|
|
354
|
+
const f = 1.0 / Math.tan(fovy / 2)
|
|
355
|
+
closeTo(m[0], f / aspect)
|
|
356
|
+
closeTo(m[5], f)
|
|
357
|
+
assert.strictEqual(m[10], -1)
|
|
358
|
+
assert.strictEqual(m[11], -1)
|
|
359
|
+
closeTo(m[14], -2 * near)
|
|
360
|
+
assert.strictEqual(m[15], 0)
|
|
361
|
+
})
|
|
362
|
+
it('handles null far plane same as infinite', () => {
|
|
363
|
+
const fovy = Math.PI / 4, aspect = 16 / 9, near = 0.1
|
|
364
|
+
const m = Mat4.perspectiveNO(fovy, aspect, near, null)
|
|
365
|
+
assert.strictEqual(m[10], -1)
|
|
366
|
+
closeTo(m[14], -2 * near)
|
|
367
|
+
})
|
|
368
|
+
it('perspective is alias for perspectiveNO', () => {
|
|
369
|
+
assert.strictEqual(Mat4.perspective, Mat4.perspectiveNO)
|
|
370
|
+
})
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
describe('static perspectiveZO', () => {
|
|
374
|
+
it('creates perspective matrix with [0,1] depth', () => {
|
|
375
|
+
const fovy = Math.PI / 4, aspect = 16 / 9, near = 0.1, far = 100
|
|
376
|
+
const m = Mat4.perspectiveZO(fovy, aspect, near, far)
|
|
377
|
+
const f = 1.0 / Math.tan(fovy / 2)
|
|
378
|
+
const nf = 1 / (near - far)
|
|
379
|
+
closeTo(m[0], f / aspect)
|
|
380
|
+
closeTo(m[5], f)
|
|
381
|
+
closeTo(m[10], far * nf)
|
|
382
|
+
assert.strictEqual(m[11], -1)
|
|
383
|
+
closeTo(m[14], far * near * nf)
|
|
384
|
+
assert.strictEqual(m[15], 0)
|
|
385
|
+
})
|
|
386
|
+
it('handles infinite far plane', () => {
|
|
387
|
+
const m = Mat4.perspectiveZO(Math.PI / 4, 16 / 9, 0.1, Infinity)
|
|
388
|
+
assert.strictEqual(m[10], -1)
|
|
389
|
+
closeTo(m[14], -0.1)
|
|
390
|
+
})
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
describe('static orthoNO', () => {
|
|
394
|
+
it('creates orthographic matrix with correct values', () => {
|
|
395
|
+
const left = -2, right = 2, bottom = -1, top = 1, near = 0.1, far = 100
|
|
396
|
+
const m = Mat4.orthoNO(left, right, bottom, top, near, far)
|
|
397
|
+
const lr = 1 / (left - right)
|
|
398
|
+
const bt = 1 / (bottom - top)
|
|
399
|
+
const nf = 1 / (near - far)
|
|
400
|
+
closeTo(m[0], -2 * lr)
|
|
401
|
+
closeTo(m[5], -2 * bt)
|
|
402
|
+
closeTo(m[10], 2 * nf)
|
|
403
|
+
closeTo(m[12], (left + right) * lr)
|
|
404
|
+
closeTo(m[13], (top + bottom) * bt)
|
|
405
|
+
closeTo(m[14], (far + near) * nf)
|
|
406
|
+
assert.strictEqual(m[15], 1)
|
|
407
|
+
assert.strictEqual(m[3], 0)
|
|
408
|
+
assert.strictEqual(m[7], 0)
|
|
409
|
+
assert.strictEqual(m[11], 0)
|
|
410
|
+
})
|
|
411
|
+
it('ortho is alias for orthoNO', () => {
|
|
412
|
+
assert.strictEqual(Mat4.ortho, Mat4.orthoNO)
|
|
413
|
+
})
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
describe('static orthoZO', () => {
|
|
417
|
+
it('creates orthographic matrix with [0,1] depth', () => {
|
|
418
|
+
const left = -1, right = 1, bottom = -1, top = 1, near = 0.1, far = 100
|
|
419
|
+
const m = Mat4.orthoZO(left, right, bottom, top, near, far)
|
|
420
|
+
const nf = 1 / (near - far)
|
|
421
|
+
closeTo(m[0], 1)
|
|
422
|
+
closeTo(m[5], 1)
|
|
423
|
+
closeTo(m[10], nf)
|
|
424
|
+
closeTo(m[14], near * nf)
|
|
425
|
+
assert.strictEqual(m[15], 1)
|
|
426
|
+
assert.strictEqual(m[11], 0)
|
|
427
|
+
})
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
describe('static frustum', () => {
|
|
431
|
+
it('creates frustum matrix with correct values', () => {
|
|
432
|
+
const left = -1, right = 1, bottom = -1, top = 1, near = 1, far = 100
|
|
433
|
+
const m = Mat4.frustum(left, right, bottom, top, near, far)
|
|
434
|
+
const rl = 1 / (right - left)
|
|
435
|
+
const tb = 1 / (top - bottom)
|
|
436
|
+
const nf = 1 / (near - far)
|
|
437
|
+
closeTo(m[0], near * 2 * rl)
|
|
438
|
+
closeTo(m[5], near * 2 * tb)
|
|
439
|
+
closeTo(m[8], (right + left) * rl)
|
|
440
|
+
closeTo(m[9], (top + bottom) * tb)
|
|
441
|
+
closeTo(m[10], (far + near) * nf)
|
|
442
|
+
assert.strictEqual(m[11], -1)
|
|
443
|
+
closeTo(m[14], far * near * 2 * nf)
|
|
444
|
+
assert.strictEqual(m[15], 0)
|
|
445
|
+
})
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
describe('static lookAt', () => {
|
|
449
|
+
it('looking along -Z from origin', () => {
|
|
450
|
+
const m = Mat4.lookAt(new Vec3(0, 0, 0), new Vec3(0, 0, -1), new Vec3(0, 1, 0))
|
|
451
|
+
closeTo(m[0], 1)
|
|
452
|
+
closeTo(m[5], 1)
|
|
453
|
+
closeTo(m[10], 1)
|
|
454
|
+
})
|
|
455
|
+
it('returns identity when eye equals center', () => {
|
|
456
|
+
const m = Mat4.lookAt(new Vec3(0, 0, 0), new Vec3(0, 0, 0), new Vec3(0, 1, 0))
|
|
457
|
+
assert.strictEqual(m[0], 1)
|
|
458
|
+
assert.strictEqual(m[5], 1)
|
|
459
|
+
assert.strictEqual(m[10], 1)
|
|
460
|
+
assert.strictEqual(m[15], 1)
|
|
461
|
+
})
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
describe('static targetTo', () => {
|
|
465
|
+
it('creates targeting matrix', () => {
|
|
466
|
+
const m = Mat4.targetTo(new Vec3(0, 0, 5), new Vec3(0, 0, 0), new Vec3(0, 1, 0))
|
|
467
|
+
assert.strictEqual(m[12], 0)
|
|
468
|
+
assert.strictEqual(m[13], 0)
|
|
469
|
+
assert.strictEqual(m[14], 5)
|
|
470
|
+
})
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
describe('plus / minus / scaleScalar', () => {
|
|
474
|
+
it('adds', () => {
|
|
475
|
+
const a = Mat4.identity
|
|
476
|
+
const b = Mat4.identity
|
|
477
|
+
const out = new Mat4()
|
|
478
|
+
a.plus(b, out)
|
|
479
|
+
assert.strictEqual(out[0], 2)
|
|
480
|
+
})
|
|
481
|
+
it('subtracts', () => {
|
|
482
|
+
const out = new Mat4()
|
|
483
|
+
Mat4.identity.minus(Mat4.identity, out)
|
|
484
|
+
for (let i = 0; i < 16; i++) assert.strictEqual(out[i], 0)
|
|
485
|
+
})
|
|
486
|
+
it('scaleScalar', () => {
|
|
487
|
+
const m = Mat4.identity
|
|
488
|
+
const r = m.scaleScalar(3)
|
|
489
|
+
assert.strictEqual(r[0], 3)
|
|
490
|
+
assert.strictEqual(r[5], 3)
|
|
491
|
+
})
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
describe('frob', () => {
|
|
495
|
+
it('frobenius norm of identity', () => {
|
|
496
|
+
closeTo(Mat4.identity.frob(), 2)
|
|
497
|
+
})
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
describe('equals / exactEquals', () => {
|
|
501
|
+
it('exactEquals', () => {
|
|
502
|
+
assert.strictEqual(Mat4.identity.exactEquals(Mat4.identity), true)
|
|
503
|
+
})
|
|
504
|
+
it('equals with epsilon', () => {
|
|
505
|
+
const a = Mat4.identity
|
|
506
|
+
const b = Mat4.identity
|
|
507
|
+
b[0] = 1 + glmaths.EPSILON * 0.1
|
|
508
|
+
assert.strictEqual(a.equals(b), true)
|
|
509
|
+
})
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
describe('toString', () => {
|
|
513
|
+
it('returns string', () => {
|
|
514
|
+
assert.ok(Mat4.identity.toString().includes('mat4'))
|
|
515
|
+
})
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
describe('infinitePerspective', () => {
|
|
519
|
+
it('creates perspective matrix with far at infinity', () => {
|
|
520
|
+
const m = Mat4.infinitePerspective(Math.PI / 4, 16 / 9, 0.1)
|
|
521
|
+
closeTo(m[10], -1)
|
|
522
|
+
closeTo(m[11], -1)
|
|
523
|
+
closeTo(m[14], -0.2)
|
|
524
|
+
})
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
describe('project / unProject', () => {
|
|
528
|
+
it('round-trips through project and unProject', () => {
|
|
529
|
+
const model = Mat4.identity
|
|
530
|
+
const proj = Mat4.perspective(Math.PI / 4, 1, 0.1, 100)
|
|
531
|
+
const viewport = new Vec4(0, 0, 800, 600)
|
|
532
|
+
const point = new Vec3(1, 2, -5)
|
|
533
|
+
const win = Mat4.project(point, model, proj, viewport)
|
|
534
|
+
const back = Mat4.unProject(win, model, proj, viewport)
|
|
535
|
+
assert.notStrictEqual(back, null)
|
|
536
|
+
closeTo(back![0], point[0], 3)
|
|
537
|
+
closeTo(back![1], point[1], 3)
|
|
538
|
+
closeTo(back![2], point[2], 3)
|
|
539
|
+
})
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
describe('factory function', () => {
|
|
543
|
+
it('creates Mat4', () => {
|
|
544
|
+
const m = mat4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
|
|
545
|
+
assert.ok(m instanceof Mat4)
|
|
546
|
+
})
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
describe('mat * vec operators', () => {
|
|
550
|
+
it('Mat4 * Vec4 scaling + rotation', () => {
|
|
551
|
+
const m = Mat4.fromScaling(new Vec3(2, 3, 4))
|
|
552
|
+
m.rotateX(Math.PI / 3)
|
|
553
|
+
const v = new Vec4(1, 1, 1, 1)
|
|
554
|
+
const expected = v.transformMat4(m, new Vec4())
|
|
555
|
+
const r = m * v
|
|
556
|
+
assert.ok(r instanceof Vec4)
|
|
557
|
+
closeTo(r.x, expected.x)
|
|
558
|
+
closeTo(r.y, expected.y)
|
|
559
|
+
closeTo(r.z, expected.z)
|
|
560
|
+
closeTo(r.w, expected.w)
|
|
561
|
+
})
|
|
562
|
+
it('Mat4 * Vec3 full transform', () => {
|
|
563
|
+
const m = Mat4.identity
|
|
564
|
+
m.scale(new Vec3(2, 2, 2))
|
|
565
|
+
m.rotateZ(Math.PI / 3)
|
|
566
|
+
m.translate(new Vec3(5, 0, 0))
|
|
567
|
+
const v = new Vec3(1, 0, 0)
|
|
568
|
+
const expected = v.transformMat4(m, new Vec3())
|
|
569
|
+
const r = m * v
|
|
570
|
+
assert.ok(r instanceof Vec3)
|
|
571
|
+
closeTo(r.x, expected.x)
|
|
572
|
+
closeTo(r.y, expected.y)
|
|
573
|
+
closeTo(r.z, expected.z)
|
|
574
|
+
})
|
|
575
|
+
it('Mat4 * Vec4 perspective', () => {
|
|
576
|
+
const m = Mat4.perspective(Math.PI / 4, 1, 0.1, 100)
|
|
577
|
+
const v = new Vec4(1, 2, -5, 1)
|
|
578
|
+
const expected = v.transformMat4(m, new Vec4())
|
|
579
|
+
const r = m * v
|
|
580
|
+
closeTo(r.x, expected.x)
|
|
581
|
+
closeTo(r.y, expected.y)
|
|
582
|
+
closeTo(r.z, expected.z)
|
|
583
|
+
closeTo(r.w, expected.w)
|
|
584
|
+
})
|
|
585
|
+
})
|
|
586
|
+
})
|