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.
@@ -0,0 +1,306 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import glmaths, { Mat3, mat3, Mat2x3, Quat, Vec2, Vec3 } 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('Mat3', () => {
11
+ describe('constructor', () => {
12
+ it('creates zero matrix by default', () => {
13
+ const m = new Mat3()
14
+ for (let i = 0; i < 9; i++) assert.strictEqual(m[i], 0)
15
+ })
16
+ it('creates with given values', () => {
17
+ const m = new Mat3(1, 2, 3, 4, 5, 6, 7, 8, 9)
18
+ assert.strictEqual(m[0], 1)
19
+ assert.strictEqual(m[8], 9)
20
+ })
21
+ it('extends Float32Array with length 9', () => {
22
+ assert.strictEqual(new Mat3().length, 9)
23
+ })
24
+ })
25
+
26
+ describe('identity', () => {
27
+ it('creates identity', () => {
28
+ const m = Mat3.identity
29
+ assert.strictEqual(m[0], 1)
30
+ assert.strictEqual(m[4], 1)
31
+ assert.strictEqual(m[8], 1)
32
+ assert.strictEqual(m[1], 0)
33
+ assert.strictEqual(m[3], 0)
34
+ })
35
+ })
36
+
37
+ describe('clone', () => {
38
+ it('clones independently', () => {
39
+ const a = new Mat3(1, 2, 3, 4, 5, 6, 7, 8, 9)
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 Mat3(1, 2, 3, 4, 5, 6, 7, 8, 9)
49
+ const r = m.transpose()
50
+ assert.strictEqual(r[1], 4)
51
+ assert.strictEqual(r[3], 2)
52
+ assert.strictEqual(r[2], 7)
53
+ assert.strictEqual(r[6], 3)
54
+ })
55
+ it('transposes to out', () => {
56
+ const m = new Mat3(1, 2, 3, 4, 5, 6, 7, 8, 9)
57
+ const out = new Mat3()
58
+ m.transpose(out)
59
+ assert.strictEqual(out[1], 4)
60
+ assert.strictEqual(out[3], 2)
61
+ })
62
+ })
63
+
64
+ describe('invert', () => {
65
+ it('inverts identity to identity', () => {
66
+ const m = Mat3.identity
67
+ const out = new Mat3()
68
+ m.invert(out)
69
+ closeTo(out[0], 1)
70
+ closeTo(out[4], 1)
71
+ closeTo(out[8], 1)
72
+ })
73
+ it('m * m^-1 = identity', () => {
74
+ const m = new Mat3(1, 0, 0, 0, 2, 0, 0, 0, 3)
75
+ const inv = new Mat3()
76
+ m.invert(inv)
77
+ const result = new Mat3()
78
+ m.multiply(inv!, result)
79
+ closeTo(result[0], 1)
80
+ closeTo(result[4], 1)
81
+ closeTo(result[8], 1)
82
+ closeTo(result[1], 0)
83
+ })
84
+ it('returns null for singular matrix', () => {
85
+ const m = new Mat3(1, 2, 3, 4, 5, 6, 7, 8, 9)
86
+ assert.strictEqual(m.invert(new Mat3()), null)
87
+ })
88
+ })
89
+
90
+ describe('adjoint', () => {
91
+ it('adjoint of identity is identity', () => {
92
+ const out = new Mat3()
93
+ Mat3.identity.adjoint(out)
94
+ assert.strictEqual(out[0], 1)
95
+ assert.strictEqual(out[4], 1)
96
+ assert.strictEqual(out[8], 1)
97
+ })
98
+ })
99
+
100
+ describe('determinant', () => {
101
+ it('identity determinant is 1', () => {
102
+ assert.strictEqual(Mat3.identity.determinant(), 1)
103
+ })
104
+ it('diagonal matrix determinant is product of diagonal', () => {
105
+ const m = new Mat3(2, 0, 0, 0, 3, 0, 0, 0, 4)
106
+ assert.strictEqual(m.determinant(), 24)
107
+ })
108
+ it('singular matrix determinant is 0', () => {
109
+ const m = new Mat3(1, 2, 3, 4, 5, 6, 7, 8, 9)
110
+ closeTo(m.determinant(), 0)
111
+ })
112
+ })
113
+
114
+ describe('multiply', () => {
115
+ it('identity * A = A', () => {
116
+ const a = new Mat3(1, 2, 3, 4, 5, 6, 7, 8, 9)
117
+ const out = new Mat3()
118
+ Mat3.identity.multiply(a, out)
119
+ for (let i = 0; i < 9; i++) closeTo(out[i], a[i])
120
+ })
121
+ })
122
+
123
+ describe('translate', () => {
124
+ it('translates identity', () => {
125
+ const m = Mat3.identity
126
+ const r = m.translate(new Vec2(5, 10))
127
+ assert.strictEqual(r[6], 5)
128
+ assert.strictEqual(r[7], 10)
129
+ assert.strictEqual(r[8], 1)
130
+ })
131
+ })
132
+
133
+ describe('rotate', () => {
134
+ it('rotates identity by PI/2', () => {
135
+ const m = Mat3.identity
136
+ const r = m.rotate(Math.PI / 2)
137
+ closeTo(r[0], 0)
138
+ closeTo(r[1], 1)
139
+ closeTo(r[3], -1)
140
+ closeTo(r[4], 0)
141
+ })
142
+ })
143
+
144
+ describe('scale', () => {
145
+ it('scales identity', () => {
146
+ const m = Mat3.identity
147
+ const r = m.scale(new Vec2(2, 3))
148
+ assert.strictEqual(r[0], 2)
149
+ assert.strictEqual(r[4], 3)
150
+ assert.strictEqual(r[8], 1)
151
+ })
152
+ })
153
+
154
+ describe('static fromTranslation', () => {
155
+ it('creates translation matrix', () => {
156
+ const m = Mat3.fromTranslation(new Vec2(5, 10))
157
+ assert.strictEqual(m[0], 1)
158
+ assert.strictEqual(m[4], 1)
159
+ assert.strictEqual(m[6], 5)
160
+ assert.strictEqual(m[7], 10)
161
+ assert.strictEqual(m[8], 1)
162
+ })
163
+ })
164
+
165
+ describe('static fromRotation', () => {
166
+ it('creates rotation matrix', () => {
167
+ const m = Mat3.fromRotation(Math.PI / 2)
168
+ closeTo(m[0], 0)
169
+ closeTo(m[1], 1)
170
+ closeTo(m[3], -1)
171
+ closeTo(m[4], 0)
172
+ })
173
+ })
174
+
175
+ describe('static fromScaling', () => {
176
+ it('creates scaling matrix', () => {
177
+ const m = Mat3.fromScaling(new Vec2(2, 3))
178
+ assert.strictEqual(m[0], 2)
179
+ assert.strictEqual(m[4], 3)
180
+ assert.strictEqual(m[8], 1)
181
+ })
182
+ })
183
+
184
+ describe('static fromMat2x3', () => {
185
+ it('converts from Mat2x3', () => {
186
+ const a = new Mat2x3(1, 0, 0, 1, 5, 10)
187
+ const m = Mat3.fromMat2x3(a)
188
+ assert.strictEqual(m[0], 1)
189
+ assert.strictEqual(m[4], 1)
190
+ assert.strictEqual(m[6], 5)
191
+ assert.strictEqual(m[7], 10)
192
+ assert.strictEqual(m[8], 1)
193
+ })
194
+ })
195
+
196
+ describe('static fromQuat', () => {
197
+ it('identity quaternion gives identity matrix', () => {
198
+ const q = Quat.identity
199
+ const m = Mat3.fromQuat(q)
200
+ closeTo(m[0], 1)
201
+ closeTo(m[4], 1)
202
+ closeTo(m[8], 1)
203
+ closeTo(m[1], 0)
204
+ })
205
+ })
206
+
207
+ describe('static projection', () => {
208
+ it('creates 2D projection', () => {
209
+ const m = Mat3.projection(100, 200)
210
+ closeTo(m[0], 2 / 100)
211
+ closeTo(m[4], -2 / 200)
212
+ })
213
+ })
214
+
215
+ describe('plus / minus', () => {
216
+ it('adds', () => {
217
+ const a = Mat3.identity
218
+ const b = Mat3.identity
219
+ const out = new Mat3()
220
+ a.plus(b, out)
221
+ assert.strictEqual(out[0], 2)
222
+ assert.strictEqual(out[4], 2)
223
+ })
224
+ it('subtracts', () => {
225
+ const a = Mat3.identity
226
+ const b = Mat3.identity
227
+ const out = new Mat3()
228
+ a.minus(b, out)
229
+ assert.strictEqual(out[0], 0)
230
+ })
231
+ })
232
+
233
+ describe('scaleScalar', () => {
234
+ it('scales all elements', () => {
235
+ const m = Mat3.identity
236
+ const r = m.scaleScalar(3)
237
+ assert.strictEqual(r[0], 3)
238
+ assert.strictEqual(r[4], 3)
239
+ assert.strictEqual(r[8], 3)
240
+ })
241
+ })
242
+
243
+ describe('frob', () => {
244
+ it('frobenius norm of identity', () => {
245
+ closeTo(Mat3.identity.frob(), Math.sqrt(3))
246
+ })
247
+ })
248
+
249
+ describe('equals / exactEquals', () => {
250
+ it('exactEquals', () => {
251
+ assert.strictEqual(Mat3.identity.exactEquals(Mat3.identity), true)
252
+ })
253
+ it('equals with epsilon', () => {
254
+ const a = Mat3.identity
255
+ const b = Mat3.identity
256
+ b[0] = 1 + glmaths.EPSILON * 0.1
257
+ assert.strictEqual(a.equals(b), true)
258
+ })
259
+ })
260
+
261
+ describe('toString', () => {
262
+ it('returns string', () => {
263
+ assert.ok(Mat3.identity.toString().includes('mat3'))
264
+ })
265
+ })
266
+
267
+ describe('factory function', () => {
268
+ it('creates Mat3', () => {
269
+ const m = mat3(1, 0, 0, 0, 1, 0, 0, 0, 1)
270
+ assert.ok(m instanceof Mat3)
271
+ })
272
+ })
273
+
274
+ describe('mat * vec operators', () => {
275
+ it('Mat3 * Vec3 from quat rotation', () => {
276
+ const q = Quat.fromAxisAngle(new Vec3(1, 1, 0).normalize(), Math.PI / 3)
277
+ const m = Mat3.fromQuat(q)
278
+ const v = new Vec3(2, 3, 4)
279
+ const expected = v.transformMat3(m, new Vec3())
280
+ const r = m * v
281
+ assert.ok(r instanceof Vec3)
282
+ closeTo(r.x, expected.x)
283
+ closeTo(r.y, expected.y)
284
+ closeTo(r.z, expected.z)
285
+ })
286
+ it('Mat3 * Vec3 scale + rotation', () => {
287
+ const m = Mat3.fromRotation(Math.PI / 3)
288
+ m.scale(new Vec2(2, 3))
289
+ const v = new Vec3(1, 1, 1)
290
+ const expected = v.transformMat3(m, new Vec3())
291
+ const r = m * v
292
+ closeTo(r.x, expected.x)
293
+ closeTo(r.y, expected.y)
294
+ closeTo(r.z, expected.z)
295
+ })
296
+ it('Mat3 * Vec2 rotation via mat3', () => {
297
+ const m = Mat3.fromRotation(Math.PI / 4)
298
+ const v = new Vec2(5, 7)
299
+ const expected = v.transformMat3(m, new Vec2())
300
+ const r = m * v
301
+ assert.ok(r instanceof Vec2)
302
+ closeTo(r.x, expected.x)
303
+ closeTo(r.y, expected.y)
304
+ })
305
+ })
306
+ })