1/* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
2
3Redistribution and use in source and binary forms, with or without modification,
4are permitted provided that the following conditions are met:
5
6  * Redistributions of source code must retain the above copyright notice, this
7    list of conditions and the following disclaimer.
8  * Redistributions in binary form must reproduce the above copyright notice,
9    this list of conditions and the following disclaimer in the documentation
10    and/or other materials provided with the distribution.
11
12THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
16ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
19ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22
23describe("mat4", function() {
24    var out, matA, matB, identity, result;
25
26    beforeEach(function() {
27        // Attempting to portray a semi-realistic transform matrix
28        matA = [1, 0, 0, 0,
29                0, 1, 0, 0,
30                0, 0, 1, 0,
31                1, 2, 3, 1];
32
33        matB = [1, 0, 0, 0,
34                0, 1, 0, 0,
35                0, 0, 1, 0,
36                4, 5, 6, 1];
37
38        out =  [0, 0, 0, 0,
39                0, 0, 0, 0,
40                0, 0, 0, 0,
41                0, 0, 0, 0];
42
43        identity = [1, 0, 0, 0,
44                    0, 1, 0, 0,
45                    0, 0, 1, 0,
46                    0, 0, 0, 1];
47    });
48
49    describe("create", function() {
50        beforeEach(function() { result = mat4.create(); });
51        it("should return a 16 element array initialized to a 4x4 identity matrix", function() { expect(result).toBeEqualish(identity); });
52    });
53
54    describe("clone", function() {
55        beforeEach(function() { result = mat4.clone(matA); });
56        it("should return a 16 element array initialized to the values in matA", function() { expect(result).toBeEqualish(matA); });
57    });
58
59    describe("copy", function() {
60        beforeEach(function() { result = mat4.copy(out, matA); });
61        it("should place values into out", function() { expect(out).toBeEqualish(matA); });
62        it("should return out", function() { expect(result).toBe(out); });
63    });
64
65    describe("identity", function() {
66        beforeEach(function() { result = mat4.identity(out); });
67        it("should place values into out", function() { expect(result).toBeEqualish(identity); });
68        it("should return out", function() { expect(result).toBe(out); });
69    });
70
71    describe("transpose", function() {
72        describe("with a separate output matrix", function() {
73            beforeEach(function() { result = mat4.transpose(out, matA); });
74
75            it("should place values into out", function() {
76                expect(out).toBeEqualish([
77                    1, 0, 0, 1,
78                    0, 1, 0, 2,
79                    0, 0, 1, 3,
80                    0, 0, 0, 1
81                ]);
82            });
83            it("should return out", function() { expect(result).toBe(out); });
84            it("should not modify matA", function() {
85                expect(matA).toBeEqualish([
86                    1, 0, 0, 0,
87                    0, 1, 0, 0,
88                    0, 0, 1, 0,
89                    1, 2, 3, 1
90                ]);
91            });
92        });
93
94        describe("when matA is the output matrix", function() {
95            beforeEach(function() { result = mat4.transpose(matA, matA); });
96
97            it("should place values into matA", function() {
98                expect(matA).toBeEqualish([
99                    1, 0, 0, 1,
100                    0, 1, 0, 2,
101                    0, 0, 1, 3,
102                    0, 0, 0, 1
103                ]);
104            });
105            it("should return matA", function() { expect(result).toBe(matA); });
106        });
107    });
108
109    describe("invert", function() {
110        describe("with a separate output matrix", function() {
111            beforeEach(function() { result = mat4.invert(out, matA); });
112
113            it("should place values into out", function() {
114                expect(out).toBeEqualish([
115                    1, 0, 0, 0,
116                    0, 1, 0, 0,
117                    0, 0, 1, 0,
118                    -1, -2, -3, 1
119                ]);
120            });
121            it("should return out", function() { expect(result).toBe(out); });
122            it("should not modify matA", function() {
123                expect(matA).toBeEqualish([
124                    1, 0, 0, 0,
125                    0, 1, 0, 0,
126                    0, 0, 1, 0,
127                    1, 2, 3, 1
128                ]);
129            });
130        });
131
132        describe("when matA is the output matrix", function() {
133            beforeEach(function() { result = mat4.invert(matA, matA); });
134
135            it("should place values into matA", function() {
136                expect(matA).toBeEqualish([
137                    1, 0, 0, 0,
138                    0, 1, 0, 0,
139                    0, 0, 1, 0,
140                    -1, -2, -3, 1
141                ]);
142            });
143            it("should return matA", function() { expect(result).toBe(matA); });
144        });
145    });
146
147    describe("adjoint", function() {
148        describe("with a separate output matrix", function() {
149            beforeEach(function() { result = mat4.adjoint(out, matA); });
150
151            it("should place values into out", function() {
152                expect(out).toBeEqualish([
153                    1, 0, 0, 0,
154                    0, 1, 0, 0,
155                    0, 0, 1, 0,
156                    -1, -2, -3, 1
157                ]);
158            });
159            it("should return out", function() { expect(result).toBe(out); });
160            it("should not modify matA", function() {
161                expect(matA).toBeEqualish([
162                    1, 0, 0, 0,
163                    0, 1, 0, 0,
164                    0, 0, 1, 0,
165                    1, 2, 3, 1
166                ]);
167            });
168        });
169
170        describe("when matA is the output matrix", function() {
171            beforeEach(function() { result = mat4.adjoint(matA, matA); });
172
173            it("should place values into matA", function() {
174                expect(matA).toBeEqualish([
175                    1, 0, 0, 0,
176                    0, 1, 0, 0,
177                    0, 0, 1, 0,
178                    -1, -2, -3, 1
179                ]);
180            });
181            it("should return matA", function() { expect(result).toBe(matA); });
182        });
183    });
184
185    describe("determinant", function() {
186        beforeEach(function() { result = mat4.determinant(matA); });
187
188        it("should return the determinant", function() { expect(result).toEqual(1); });
189    });
190
191    describe("multiply", function() {
192        it("should have an alias called 'mul'", function() { expect(mat4.mul).toEqual(mat4.multiply); });
193
194        describe("with a separate output matrix", function() {
195            beforeEach(function() { result = mat4.multiply(out, matA, matB); });
196
197            it("should place values into out", function() {
198                expect(out).toBeEqualish([
199                    1, 0, 0, 0,
200                    0, 1, 0, 0,
201                    0, 0, 1, 0,
202                    5, 7, 9, 1
203                ]);
204            });
205            it("should return out", function() { expect(result).toBe(out); });
206            it("should not modify matA", function() {
207                expect(matA).toBeEqualish([
208                    1, 0, 0, 0,
209                    0, 1, 0, 0,
210                    0, 0, 1, 0,
211                    1, 2, 3, 1
212                ]);
213            });
214            it("should not modify matB", function() {
215                expect(matB).toBeEqualish([
216                    1, 0, 0, 0,
217                    0, 1, 0, 0,
218                    0, 0, 1, 0,
219                    4, 5, 6, 1
220                ]);
221            });
222        });
223
224        describe("when matA is the output matrix", function() {
225            beforeEach(function() { result = mat4.multiply(matA, matA, matB); });
226
227            it("should place values into matA", function() {
228                expect(matA).toBeEqualish([
229                    1, 0, 0, 0,
230                    0, 1, 0, 0,
231                    0, 0, 1, 0,
232                    5, 7, 9, 1
233                ]);
234            });
235            it("should return matA", function() { expect(result).toBe(matA); });
236            it("should not modify matB", function() {
237                expect(matB).toBeEqualish([
238                    1, 0, 0, 0,
239                    0, 1, 0, 0,
240                    0, 0, 1, 0,
241                    4, 5, 6, 1
242                ]);
243            });
244        });
245
246        describe("when matB is the output matrix", function() {
247            beforeEach(function() { result = mat4.multiply(matB, matA, matB); });
248
249            it("should place values into matB", function() {
250                expect(matB).toBeEqualish([
251                    1, 0, 0, 0,
252                    0, 1, 0, 0,
253                    0, 0, 1, 0,
254                    5, 7, 9, 1
255                ]);
256            });
257            it("should return matB", function() { expect(result).toBe(matB); });
258            it("should not modify matA", function() {
259                expect(matA).toBeEqualish([
260                    1, 0, 0, 0,
261                    0, 1, 0, 0,
262                    0, 0, 1, 0,
263                    1, 2, 3, 1
264                ]);
265            });
266        });
267    });
268
269    describe("translate", function() {
270        describe("with a separate output matrix", function() {
271            beforeEach(function() { result = mat4.translate(out, matA, [4, 5, 6]); });
272
273            it("should place values into out", function() {
274                expect(out).toBeEqualish([
275                    1, 0, 0, 0,
276                    0, 1, 0, 0,
277                    0, 0, 1, 0,
278                    5, 7, 9, 1
279                ]);
280            });
281            it("should return out", function() { expect(result).toBe(out); });
282            it("should not modify matA", function() {
283                expect(matA).toBeEqualish([
284                    1, 0, 0, 0,
285                    0, 1, 0, 0,
286                    0, 0, 1, 0,
287                    1, 2, 3, 1
288                ]);
289            });
290        });
291
292        describe("when matA is the output matrix", function() {
293            beforeEach(function() { result = mat4.translate(matA, matA, [4, 5, 6]); });
294
295            it("should place values into matA", function() {
296                expect(matA).toBeEqualish([
297                    1, 0, 0, 0,
298                    0, 1, 0, 0,
299                    0, 0, 1, 0,
300                    5, 7, 9, 1
301                ]);
302            });
303            it("should return matA", function() { expect(result).toBe(matA); });
304        });
305    });
306
307    describe("scale", function() {
308        describe("with a separate output matrix", function() {
309            beforeEach(function() { result = mat4.scale(out, matA, [4, 5, 6]); });
310
311            it("should place values into out", function() {
312                expect(out).toBeEqualish([
313                    4, 0, 0, 0,
314                    0, 5, 0, 0,
315                    0, 0, 6, 0,
316                    1, 2, 3, 1
317                ]);
318            });
319            it("should return out", function() { expect(result).toBe(out); });
320            it("should not modify matA", function() {
321                expect(matA).toBeEqualish([
322                    1, 0, 0, 0,
323                    0, 1, 0, 0,
324                    0, 0, 1, 0,
325                    1, 2, 3, 1
326                ]);
327            });
328        });
329
330        describe("when matA is the output matrix", function() {
331            beforeEach(function() { result = mat4.scale(matA, matA, [4, 5, 6]); });
332
333            it("should place values into matA", function() {
334                expect(matA).toBeEqualish([
335                    4, 0, 0, 0,
336                    0, 5, 0, 0,
337                    0, 0, 6, 0,
338                    1, 2, 3, 1
339                ]);
340            });
341            it("should return matA", function() { expect(result).toBe(matA); });
342        });
343    });
344
345    describe("rotate", function() {
346        var rad = Math.PI * 0.5;
347        var axis = [1, 0, 0];
348
349        describe("with a separate output matrix", function() {
350            beforeEach(function() { result = mat4.rotate(out, matA, rad, axis); });
351
352            it("should place values into out", function() {
353                expect(out).toBeEqualish([
354                    1, 0, 0, 0,
355                    0, Math.cos(rad), Math.sin(rad), 0,
356                    0, -Math.sin(rad), Math.cos(rad), 0,
357                    1, 2, 3, 1
358                ]);
359            });
360            it("should return out", function() { expect(result).toBe(out); });
361            it("should not modify matA", function() {
362                expect(matA).toBeEqualish([
363                    1, 0, 0, 0,
364                    0, 1, 0, 0,
365                    0, 0, 1, 0,
366                    1, 2, 3, 1
367                ]);
368            });
369        });
370
371        describe("when matA is the output matrix", function() {
372            beforeEach(function() { result = mat4.rotate(matA, matA, rad, axis); });
373
374            it("should place values into matA", function() {
375                expect(matA).toBeEqualish([
376                    1, 0, 0, 0,
377                    0, Math.cos(rad), Math.sin(rad), 0,
378                    0, -Math.sin(rad), Math.cos(rad), 0,
379                    1, 2, 3, 1
380                ]);
381            });
382            it("should return matA", function() { expect(result).toBe(matA); });
383        });
384    });
385
386    describe("rotateX", function() {
387        var rad = Math.PI * 0.5;
388
389        describe("with a separate output matrix", function() {
390            beforeEach(function() { result = mat4.rotateX(out, matA, rad); });
391
392            it("should place values into out", function() {
393                expect(out).toBeEqualish([
394                    1, 0, 0, 0,
395                    0, Math.cos(rad), Math.sin(rad), 0,
396                    0, -Math.sin(rad), Math.cos(rad), 0,
397                    1, 2, 3, 1
398                ]);
399            });
400            it("should return out", function() { expect(result).toBe(out); });
401            it("should not modify matA", function() {
402                expect(matA).toBeEqualish([
403                    1, 0, 0, 0,
404                    0, 1, 0, 0,
405                    0, 0, 1, 0,
406                    1, 2, 3, 1
407                ]);
408            });
409        });
410
411        describe("when matA is the output matrix", function() {
412            beforeEach(function() { result = mat4.rotateX(matA, matA, rad); });
413
414            it("should place values into matA", function() {
415                expect(matA).toBeEqualish([
416                    1, 0, 0, 0,
417                    0, Math.cos(rad), Math.sin(rad), 0,
418                    0, -Math.sin(rad), Math.cos(rad), 0,
419                    1, 2, 3, 1
420                ]);
421            });
422            it("should return matA", function() { expect(result).toBe(matA); });
423        });
424    });
425
426    describe("rotateY", function() {
427        var rad = Math.PI * 0.5;
428
429        describe("with a separate output matrix", function() {
430            beforeEach(function() { result = mat4.rotateY(out, matA, rad); });
431
432            it("should place values into out", function() {
433                expect(out).toBeEqualish([
434                    Math.cos(rad), 0, -Math.sin(rad), 0,
435                    0, 1, 0, 0,
436                    Math.sin(rad), 0, Math.cos(rad), 0,
437                    1, 2, 3, 1
438                ]);
439            });
440            it("should return out", function() { expect(result).toBe(out); });
441            it("should not modify matA", function() {
442                expect(matA).toBeEqualish([
443                    1, 0, 0, 0,
444                    0, 1, 0, 0,
445                    0, 0, 1, 0,
446                    1, 2, 3, 1
447                ]);
448            });
449        });
450
451        describe("when matA is the output matrix", function() {
452            beforeEach(function() { result = mat4.rotateY(matA, matA, rad); });
453
454            it("should place values into matA", function() {
455                expect(matA).toBeEqualish([
456                    Math.cos(rad), 0, -Math.sin(rad), 0,
457                    0, 1, 0, 0,
458                    Math.sin(rad), 0, Math.cos(rad), 0,
459                    1, 2, 3, 1
460                ]);
461            });
462            it("should return matA", function() { expect(result).toBe(matA); });
463        });
464    });
465
466    describe("rotateZ", function() {
467        var rad = Math.PI * 0.5;
468
469        describe("with a separate output matrix", function() {
470            beforeEach(function() { result = mat4.rotateZ(out, matA, rad); });
471
472            it("should place values into out", function() {
473                expect(out).toBeEqualish([
474                    Math.cos(rad), Math.sin(rad), 0, 0,
475                    -Math.sin(rad), Math.cos(rad), 0, 0,
476                    0, 0, 1, 0,
477                    1, 2, 3, 1
478                ]);
479            });
480            it("should return out", function() { expect(result).toBe(out); });
481            it("should not modify matA", function() {
482                expect(matA).toBeEqualish([
483                    1, 0, 0, 0,
484                    0, 1, 0, 0,
485                    0, 0, 1, 0,
486                    1, 2, 3, 1
487                ]);
488            });
489        });
490
491        describe("when matA is the output matrix", function() {
492            beforeEach(function() { result = mat4.rotateZ(matA, matA, rad); });
493
494            it("should place values into matA", function() {
495                expect(matA).toBeEqualish([
496                    Math.cos(rad), Math.sin(rad), 0, 0,
497                    -Math.sin(rad), Math.cos(rad), 0, 0,
498                    0, 0, 1, 0,
499                    1, 2, 3, 1
500                ]);
501            });
502            it("should return matA", function() { expect(result).toBe(matA); });
503        });
504    });
505
506    // TODO: fromRotationTranslation
507
508    describe("frustum", function() {
509        beforeEach(function() { result = mat4.frustum(out, -1, 1, -1, 1, -1, 1); });
510        it("should place values into out", function() { expect(result).toBeEqualish([
511                -1, 0, 0, 0,
512                0, -1, 0, 0,
513                0, 0, 0, -1,
514                0, 0, 1, 0
515            ]);
516        });
517        it("should return out", function() { expect(result).toBe(out); });
518    });
519
520    describe("perspective", function() {
521        var fovy = Math.PI * 0.5;
522        beforeEach(function() { result = mat4.perspective(out, fovy, 1, 0, 1); });
523        it("should place values into out", function() { expect(result).toBeEqualish([
524                1, 0, 0, 0,
525                0, 1, 0, 0,
526                0, 0, -1, -1,
527                0, 0, 0, 0
528            ]);
529        });
530        it("should return out", function() { expect(result).toBe(out); });
531    });
532
533    describe("ortho", function() {
534        beforeEach(function() { result = mat4.ortho(out, -1, 1, -1, 1, -1, 1); });
535        it("should place values into out", function() { expect(result).toBeEqualish([
536                1, 0, 0, 0,
537                0, 1, 0, 0,
538                0, 0, -1, 0,
539                0, 0, 0, 1
540            ]);
541        });
542        it("should return out", function() { expect(result).toBe(out); });
543    });
544
545    describe("lookAt", function() {
546        var eye = [0, 0, 1];
547        var center = [0, 0, -1];
548        var up = [0, 1, 0];
549
550        beforeEach(function() { result = mat4.lookAt(out, eye, center, up); });
551        it("should place values into out", function() { expect(result).toBeEqualish([
552                1, 0, 0, 0,
553                0, 1, 0, 0,
554                0, 0, 1, 0,
555                0, 0, -1, 1
556            ]);
557        });
558        it("should return out", function() { expect(result).toBe(out); });
559    });
560
561    describe("str", function() {
562        beforeEach(function() { result = mat4.str(matA); });
563
564        it("should return a string representation of the matrix", function() { expect(result).toEqual("mat4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 2, 3, 1)"); });
565    });
566});