1/*
2 * Copyright (C) 2009 Apple Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 // J3DI (Jedi) - A support library for WebGL.
27
28/*
29    J3DI Math Classes. Currently includes:
30
31        J3DIMatrix4 - A 4x4 Matrix
32*/
33
34/*
35    J3DIMatrix4 class
36
37    This class implements a 4x4 matrix. It has functions which duplicate the
38    functionality of the OpenGL matrix stack and glut functions. On browsers
39    that support it, CSSMatrix is used to accelerate operations.
40
41    IDL:
42
43    [
44        Constructor(in J3DIMatrix4 matrix),                 // copy passed matrix into new J3DIMatrix4
45        Constructor(in sequence<float> array)               // create new J3DIMatrix4 with 16 floats (row major)
46        Constructor()                                       // create new J3DIMatrix4 with identity matrix
47    ]
48    interface J3DIMatrix4 {
49        void load(in J3DIMatrix4 matrix);                   // copy the values from the passed matrix
50        void load(in sequence<float> array);                // copy 16 floats into the matrix
51        sequence<float> getAsArray();                       // return the matrix as an array of 16 floats
52        Float32Array getAsFloat32Array();             // return the matrix as a Float32Array with 16 values
53        void setUniform(in WebGLRenderingContext ctx,       // Send the matrix to the passed uniform location in the passed context
54                        in WebGLUniformLocation loc,
55                        in boolean transpose);
56        void makeIdentity();                                // replace the matrix with identity
57        void transpose();                                   // replace the matrix with its transpose
58        void invert();                                      // replace the matrix with its inverse
59
60        void translate(in float x, in float y, in float z); // multiply the matrix by passed translation values on the right
61        void translate(in J3DVector3 v);                    // multiply the matrix by passed translation values on the right
62        void scale(in float x, in float y, in float z);     // multiply the matrix by passed scale values on the right
63        void scale(in J3DVector3 v);                        // multiply the matrix by passed scale values on the right
64        void rotate(in float angle,                         // multiply the matrix by passed rotation values on the right
65                    in float x, in float y, in float z);    // (angle is in degrees)
66        void rotate(in float angle, in J3DVector3 v);       // multiply the matrix by passed rotation values on the right
67                                                            // (angle is in degrees)
68        void multiply(in CanvasMatrix matrix);              // multiply the matrix by the passed matrix on the right
69        void divide(in float divisor);                      // divide the matrix by the passed divisor
70        void ortho(in float left, in float right,           // multiply the matrix by the passed ortho values on the right
71                   in float bottom, in float top,
72                   in float near, in float far);
73        void frustum(in float left, in float right,         // multiply the matrix by the passed frustum values on the right
74                     in float bottom, in float top,
75                     in float near, in float far);
76        void perspective(in float fovy, in float aspect,    // multiply the matrix by the passed perspective values on the right
77                         in float zNear, in float zFar);
78        void lookat(in J3DVector3 eye,                      // multiply the matrix by the passed lookat
79                in J3DVector3 center,  in J3DVector3 up);   // values on the right
80         bool decompose(in J3DVector3 translate,            // decompose the matrix into the passed vector
81                        in J3DVector3 rotate,
82                        in J3DVector3 scale,
83                        in J3DVector3 skew,
84                        in sequence<float> perspective);
85    }
86
87    [
88        Constructor(in J3DVector3 vector),                  // copy passed vector into new J3DVector3
89        Constructor(in sequence<float> array)               // create new J3DVector3 with 3 floats from array
90        Constructor(in float x, in float y, in float z)     // create new J3DVector3 with 3 floats
91        Constructor()                                       // create new J3DVector3 with (0,0,0)
92    ]
93    interface J3DVector3 {
94        void load(in J3DVector3 vector);                    // copy the values from the passed vector
95        void load(in sequence<float> array);                // copy 3 floats into the vector from array
96        void load(in float x, in float y, in float z);      // copy 3 floats into the vector
97        sequence<float> getAsArray();                       // return the vector as an array of 3 floats
98        Float32Array getAsFloat32Array();             // return the matrix as a Float32Array with 16 values
99        void multMatrix(in J3DIMatrix4 matrix);             // multiply the vector by the passed matrix (on the right)
100        float vectorLength();                               // return the length of the vector
101        float dot();                                        // return the dot product of the vector
102        void cross(in J3DVector3 v);                        // replace the vector with vector x v
103        void divide(in float divisor);                      // divide the vector by the passed divisor
104    }
105*/
106
107J3DIHasCSSMatrix = false;
108J3DIHasCSSMatrixCopy = false;
109/*
110if ("WebKitCSSMatrix" in window && ("media" in window && window.media.matchMedium("(-webkit-transform-3d)")) ||
111                                   ("styleMedia" in window && window.styleMedia.matchMedium("(-webkit-transform-3d)"))) {
112    J3DIHasCSSMatrix = true;
113    if ("copy" in WebKitCSSMatrix.prototype)
114        J3DIHasCSSMatrixCopy = true;
115}
116*/
117
118//  console.log("J3DIHasCSSMatrix="+J3DIHasCSSMatrix);
119//  console.log("J3DIHasCSSMatrixCopy="+J3DIHasCSSMatrixCopy);
120
121//
122// J3DIMatrix4
123//
124J3DIMatrix4 = function(m)
125{
126    if (J3DIHasCSSMatrix)
127        this.$matrix = new WebKitCSSMatrix;
128    else
129        this.$matrix = new Object;
130
131    if (typeof m == 'object') {
132        if ("length" in m && m.length >= 16) {
133            this.load(m);
134            return;
135        }
136        else if (m instanceof J3DIMatrix4) {
137            this.load(m);
138            return;
139        }
140    }
141    this.makeIdentity();
142}
143
144J3DIMatrix4.prototype.load = function()
145{
146    if (arguments.length == 1 && typeof arguments[0] == 'object') {
147        var matrix;
148
149        if (arguments[0] instanceof J3DIMatrix4) {
150            matrix = arguments[0].$matrix;
151
152            this.$matrix.m11 = matrix.m11;
153            this.$matrix.m12 = matrix.m12;
154            this.$matrix.m13 = matrix.m13;
155            this.$matrix.m14 = matrix.m14;
156
157            this.$matrix.m21 = matrix.m21;
158            this.$matrix.m22 = matrix.m22;
159            this.$matrix.m23 = matrix.m23;
160            this.$matrix.m24 = matrix.m24;
161
162            this.$matrix.m31 = matrix.m31;
163            this.$matrix.m32 = matrix.m32;
164            this.$matrix.m33 = matrix.m33;
165            this.$matrix.m34 = matrix.m34;
166
167            this.$matrix.m41 = matrix.m41;
168            this.$matrix.m42 = matrix.m42;
169            this.$matrix.m43 = matrix.m43;
170            this.$matrix.m44 = matrix.m44;
171            return;
172        }
173        else
174            matrix = arguments[0];
175
176        if ("length" in matrix && matrix.length >= 16) {
177            this.$matrix.m11 = matrix[0];
178            this.$matrix.m12 = matrix[1];
179            this.$matrix.m13 = matrix[2];
180            this.$matrix.m14 = matrix[3];
181
182            this.$matrix.m21 = matrix[4];
183            this.$matrix.m22 = matrix[5];
184            this.$matrix.m23 = matrix[6];
185            this.$matrix.m24 = matrix[7];
186
187            this.$matrix.m31 = matrix[8];
188            this.$matrix.m32 = matrix[9];
189            this.$matrix.m33 = matrix[10];
190            this.$matrix.m34 = matrix[11];
191
192            this.$matrix.m41 = matrix[12];
193            this.$matrix.m42 = matrix[13];
194            this.$matrix.m43 = matrix[14];
195            this.$matrix.m44 = matrix[15];
196            return;
197        }
198    }
199
200    this.makeIdentity();
201}
202
203J3DIMatrix4.prototype.getAsArray = function()
204{
205    return [
206        this.$matrix.m11, this.$matrix.m12, this.$matrix.m13, this.$matrix.m14,
207        this.$matrix.m21, this.$matrix.m22, this.$matrix.m23, this.$matrix.m24,
208        this.$matrix.m31, this.$matrix.m32, this.$matrix.m33, this.$matrix.m34,
209        this.$matrix.m41, this.$matrix.m42, this.$matrix.m43, this.$matrix.m44
210    ];
211}
212
213J3DIMatrix4.prototype.getAsFloat32Array = function()
214{
215    if (J3DIHasCSSMatrixCopy) {
216        var array = new Float32Array(16);
217        this.$matrix.copy(array);
218        return array;
219    }
220    return new Float32Array(this.getAsArray());
221}
222
223J3DIMatrix4.prototype.setUniform = function(ctx, loc, transpose)
224{
225    if (J3DIMatrix4.setUniformArray == undefined) {
226        J3DIMatrix4.setUniformWebGLArray = new Float32Array(16);
227        J3DIMatrix4.setUniformArray = new Array(16);
228    }
229
230    if (J3DIHasCSSMatrixCopy)
231        this.$matrix.copy(J3DIMatrix4.setUniformWebGLArray);
232    else {
233        J3DIMatrix4.setUniformArray[0] = this.$matrix.m11;
234        J3DIMatrix4.setUniformArray[1] = this.$matrix.m12;
235        J3DIMatrix4.setUniformArray[2] = this.$matrix.m13;
236        J3DIMatrix4.setUniformArray[3] = this.$matrix.m14;
237        J3DIMatrix4.setUniformArray[4] = this.$matrix.m21;
238        J3DIMatrix4.setUniformArray[5] = this.$matrix.m22;
239        J3DIMatrix4.setUniformArray[6] = this.$matrix.m23;
240        J3DIMatrix4.setUniformArray[7] = this.$matrix.m24;
241        J3DIMatrix4.setUniformArray[8] = this.$matrix.m31;
242        J3DIMatrix4.setUniformArray[9] = this.$matrix.m32;
243        J3DIMatrix4.setUniformArray[10] = this.$matrix.m33;
244        J3DIMatrix4.setUniformArray[11] = this.$matrix.m34;
245        J3DIMatrix4.setUniformArray[12] = this.$matrix.m41;
246        J3DIMatrix4.setUniformArray[13] = this.$matrix.m42;
247        J3DIMatrix4.setUniformArray[14] = this.$matrix.m43;
248        J3DIMatrix4.setUniformArray[15] = this.$matrix.m44;
249
250        J3DIMatrix4.setUniformWebGLArray.set(J3DIMatrix4.setUniformArray);
251    }
252
253    ctx.uniformMatrix4fv(loc, transpose, J3DIMatrix4.setUniformWebGLArray);
254}
255
256J3DIMatrix4.prototype.makeIdentity = function()
257{
258    this.$matrix.m11 = 1;
259    this.$matrix.m12 = 0;
260    this.$matrix.m13 = 0;
261    this.$matrix.m14 = 0;
262
263    this.$matrix.m21 = 0;
264    this.$matrix.m22 = 1;
265    this.$matrix.m23 = 0;
266    this.$matrix.m24 = 0;
267
268    this.$matrix.m31 = 0;
269    this.$matrix.m32 = 0;
270    this.$matrix.m33 = 1;
271    this.$matrix.m34 = 0;
272
273    this.$matrix.m41 = 0;
274    this.$matrix.m42 = 0;
275    this.$matrix.m43 = 0;
276    this.$matrix.m44 = 1;
277}
278
279J3DIMatrix4.prototype.transpose = function()
280{
281    var tmp = this.$matrix.m12;
282    this.$matrix.m12 = this.$matrix.m21;
283    this.$matrix.m21 = tmp;
284
285    tmp = this.$matrix.m13;
286    this.$matrix.m13 = this.$matrix.m31;
287    this.$matrix.m31 = tmp;
288
289    tmp = this.$matrix.m14;
290    this.$matrix.m14 = this.$matrix.m41;
291    this.$matrix.m41 = tmp;
292
293    tmp = this.$matrix.m23;
294    this.$matrix.m23 = this.$matrix.m32;
295    this.$matrix.m32 = tmp;
296
297    tmp = this.$matrix.m24;
298    this.$matrix.m24 = this.$matrix.m42;
299    this.$matrix.m42 = tmp;
300
301    tmp = this.$matrix.m34;
302    this.$matrix.m34 = this.$matrix.m43;
303    this.$matrix.m43 = tmp;
304}
305
306J3DIMatrix4.prototype.invert = function()
307{
308    if (J3DIHasCSSMatrix) {
309        this.$matrix = this.$matrix.inverse();
310        return;
311    }
312
313    // Calculate the 4x4 determinant
314    // If the determinant is zero,
315    // then the inverse matrix is not unique.
316    var det = this._determinant4x4();
317
318    if (Math.abs(det) < 1e-8)
319        return null;
320
321    this._makeAdjoint();
322
323    // Scale the adjoint matrix to get the inverse
324    this.$matrix.m11 /= det;
325    this.$matrix.m12 /= det;
326    this.$matrix.m13 /= det;
327    this.$matrix.m14 /= det;
328
329    this.$matrix.m21 /= det;
330    this.$matrix.m22 /= det;
331    this.$matrix.m23 /= det;
332    this.$matrix.m24 /= det;
333
334    this.$matrix.m31 /= det;
335    this.$matrix.m32 /= det;
336    this.$matrix.m33 /= det;
337    this.$matrix.m34 /= det;
338
339    this.$matrix.m41 /= det;
340    this.$matrix.m42 /= det;
341    this.$matrix.m43 /= det;
342    this.$matrix.m44 /= det;
343}
344
345J3DIMatrix4.prototype.translate = function(x,y,z)
346{
347    if (typeof x == 'object' && "length" in x) {
348        var t = x;
349        x = t[0];
350        y = t[1];
351        z = t[2];
352    }
353    else {
354        if (x == undefined)
355            x = 0;
356        if (y == undefined)
357            y = 0;
358        if (z == undefined)
359            z = 0;
360    }
361
362    if (J3DIHasCSSMatrix) {
363        this.$matrix = this.$matrix.translate(x, y, z);
364        return;
365    }
366
367    var matrix = new J3DIMatrix4();
368    matrix.$matrix.m41 = x;
369    matrix.$matrix.m42 = y;
370    matrix.$matrix.m43 = z;
371
372    this.multiply(matrix);
373}
374
375J3DIMatrix4.prototype.scale = function(x,y,z)
376{
377    if (typeof x == 'object' && "length" in x) {
378        var t = x;
379        x = t[0];
380        y = t[1];
381        z = t[2];
382    }
383    else {
384        if (x == undefined)
385            x = 1;
386        if (z == undefined) {
387            if (y == undefined) {
388                y = x;
389                z = x;
390            }
391            else
392                z = 1;
393        }
394        else if (y == undefined)
395            y = x;
396    }
397
398    if (J3DIHasCSSMatrix) {
399        this.$matrix = this.$matrix.scale(x, y, z);
400        return;
401    }
402
403    var matrix = new J3DIMatrix4();
404    matrix.$matrix.m11 = x;
405    matrix.$matrix.m22 = y;
406    matrix.$matrix.m33 = z;
407
408    this.multiply(matrix);
409}
410
411J3DIMatrix4.prototype.rotate = function(angle,x,y,z)
412{
413    // Forms are (angle, x,y,z), (angle,vector), (angleX, angleY, angleZ), (angle)
414    if (typeof x == 'object' && "length" in x) {
415        var t = x;
416        x = t[0];
417        y = t[1];
418        z = t[2];
419    }
420    else {
421        if (arguments.length == 1) {
422            x = 0;
423            y = 0;
424            z = 1;
425        }
426        else if (arguments.length == 3) {
427            this.rotate(angle, 1,0,0); // about X axis
428            this.rotate(x, 0,1,0); // about Y axis
429            this.rotate(y, 0,0,1); // about Z axis
430            return;
431        }
432    }
433
434    if (J3DIHasCSSMatrix) {
435        this.$matrix = this.$matrix.rotateAxisAngle(x, y, z, angle);
436        return;
437    }
438
439    // angles are in degrees. Switch to radians
440    angle = angle / 180 * Math.PI;
441
442    angle /= 2;
443    var sinA = Math.sin(angle);
444    var cosA = Math.cos(angle);
445    var sinA2 = sinA * sinA;
446
447    // normalize
448    var len = Math.sqrt(x * x + y * y + z * z);
449    if (len == 0) {
450        // bad vector, just use something reasonable
451        x = 0;
452        y = 0;
453        z = 1;
454    } else if (len != 1) {
455        x /= len;
456        y /= len;
457        z /= len;
458    }
459
460    var mat = new J3DIMatrix4();
461
462    // optimize case where axis is along major axis
463    if (x == 1 && y == 0 && z == 0) {
464        mat.$matrix.m11 = 1;
465        mat.$matrix.m12 = 0;
466        mat.$matrix.m13 = 0;
467        mat.$matrix.m21 = 0;
468        mat.$matrix.m22 = 1 - 2 * sinA2;
469        mat.$matrix.m23 = 2 * sinA * cosA;
470        mat.$matrix.m31 = 0;
471        mat.$matrix.m32 = -2 * sinA * cosA;
472        mat.$matrix.m33 = 1 - 2 * sinA2;
473        mat.$matrix.m14 = mat.$matrix.m24 = mat.$matrix.m34 = 0;
474        mat.$matrix.m41 = mat.$matrix.m42 = mat.$matrix.m43 = 0;
475        mat.$matrix.m44 = 1;
476    } else if (x == 0 && y == 1 && z == 0) {
477        mat.$matrix.m11 = 1 - 2 * sinA2;
478        mat.$matrix.m12 = 0;
479        mat.$matrix.m13 = -2 * sinA * cosA;
480        mat.$matrix.m21 = 0;
481        mat.$matrix.m22 = 1;
482        mat.$matrix.m23 = 0;
483        mat.$matrix.m31 = 2 * sinA * cosA;
484        mat.$matrix.m32 = 0;
485        mat.$matrix.m33 = 1 - 2 * sinA2;
486        mat.$matrix.m14 = mat.$matrix.m24 = mat.$matrix.m34 = 0;
487        mat.$matrix.m41 = mat.$matrix.m42 = mat.$matrix.m43 = 0;
488        mat.$matrix.m44 = 1;
489    } else if (x == 0 && y == 0 && z == 1) {
490        mat.$matrix.m11 = 1 - 2 * sinA2;
491        mat.$matrix.m12 = 2 * sinA * cosA;
492        mat.$matrix.m13 = 0;
493        mat.$matrix.m21 = -2 * sinA * cosA;
494        mat.$matrix.m22 = 1 - 2 * sinA2;
495        mat.$matrix.m23 = 0;
496        mat.$matrix.m31 = 0;
497        mat.$matrix.m32 = 0;
498        mat.$matrix.m33 = 1;
499        mat.$matrix.m14 = mat.$matrix.m24 = mat.$matrix.m34 = 0;
500        mat.$matrix.m41 = mat.$matrix.m42 = mat.$matrix.m43 = 0;
501        mat.$matrix.m44 = 1;
502    } else {
503        var x2 = x*x;
504        var y2 = y*y;
505        var z2 = z*z;
506
507        mat.$matrix.m11 = 1 - 2 * (y2 + z2) * sinA2;
508        mat.$matrix.m12 = 2 * (x * y * sinA2 + z * sinA * cosA);
509        mat.$matrix.m13 = 2 * (x * z * sinA2 - y * sinA * cosA);
510        mat.$matrix.m21 = 2 * (y * x * sinA2 - z * sinA * cosA);
511        mat.$matrix.m22 = 1 - 2 * (z2 + x2) * sinA2;
512        mat.$matrix.m23 = 2 * (y * z * sinA2 + x * sinA * cosA);
513        mat.$matrix.m31 = 2 * (z * x * sinA2 + y * sinA * cosA);
514        mat.$matrix.m32 = 2 * (z * y * sinA2 - x * sinA * cosA);
515        mat.$matrix.m33 = 1 - 2 * (x2 + y2) * sinA2;
516        mat.$matrix.m14 = mat.$matrix.m24 = mat.$matrix.m34 = 0;
517        mat.$matrix.m41 = mat.$matrix.m42 = mat.$matrix.m43 = 0;
518        mat.$matrix.m44 = 1;
519    }
520    this.multiply(mat);
521}
522
523J3DIMatrix4.prototype.multiply = function(mat)
524{
525    if (J3DIHasCSSMatrix) {
526        this.$matrix = this.$matrix.multiply(mat.$matrix);
527        return;
528    }
529
530    var m11 = (mat.$matrix.m11 * this.$matrix.m11 + mat.$matrix.m12 * this.$matrix.m21
531               + mat.$matrix.m13 * this.$matrix.m31 + mat.$matrix.m14 * this.$matrix.m41);
532    var m12 = (mat.$matrix.m11 * this.$matrix.m12 + mat.$matrix.m12 * this.$matrix.m22
533               + mat.$matrix.m13 * this.$matrix.m32 + mat.$matrix.m14 * this.$matrix.m42);
534    var m13 = (mat.$matrix.m11 * this.$matrix.m13 + mat.$matrix.m12 * this.$matrix.m23
535               + mat.$matrix.m13 * this.$matrix.m33 + mat.$matrix.m14 * this.$matrix.m43);
536    var m14 = (mat.$matrix.m11 * this.$matrix.m14 + mat.$matrix.m12 * this.$matrix.m24
537               + mat.$matrix.m13 * this.$matrix.m34 + mat.$matrix.m14 * this.$matrix.m44);
538
539    var m21 = (mat.$matrix.m21 * this.$matrix.m11 + mat.$matrix.m22 * this.$matrix.m21
540               + mat.$matrix.m23 * this.$matrix.m31 + mat.$matrix.m24 * this.$matrix.m41);
541    var m22 = (mat.$matrix.m21 * this.$matrix.m12 + mat.$matrix.m22 * this.$matrix.m22
542               + mat.$matrix.m23 * this.$matrix.m32 + mat.$matrix.m24 * this.$matrix.m42);
543    var m23 = (mat.$matrix.m21 * this.$matrix.m13 + mat.$matrix.m22 * this.$matrix.m23
544               + mat.$matrix.m23 * this.$matrix.m33 + mat.$matrix.m24 * this.$matrix.m43);
545    var m24 = (mat.$matrix.m21 * this.$matrix.m14 + mat.$matrix.m22 * this.$matrix.m24
546               + mat.$matrix.m23 * this.$matrix.m34 + mat.$matrix.m24 * this.$matrix.m44);
547
548    var m31 = (mat.$matrix.m31 * this.$matrix.m11 + mat.$matrix.m32 * this.$matrix.m21
549               + mat.$matrix.m33 * this.$matrix.m31 + mat.$matrix.m34 * this.$matrix.m41);
550    var m32 = (mat.$matrix.m31 * this.$matrix.m12 + mat.$matrix.m32 * this.$matrix.m22
551               + mat.$matrix.m33 * this.$matrix.m32 + mat.$matrix.m34 * this.$matrix.m42);
552    var m33 = (mat.$matrix.m31 * this.$matrix.m13 + mat.$matrix.m32 * this.$matrix.m23
553               + mat.$matrix.m33 * this.$matrix.m33 + mat.$matrix.m34 * this.$matrix.m43);
554    var m34 = (mat.$matrix.m31 * this.$matrix.m14 + mat.$matrix.m32 * this.$matrix.m24
555               + mat.$matrix.m33 * this.$matrix.m34 + mat.$matrix.m34 * this.$matrix.m44);
556
557    var m41 = (mat.$matrix.m41 * this.$matrix.m11 + mat.$matrix.m42 * this.$matrix.m21
558               + mat.$matrix.m43 * this.$matrix.m31 + mat.$matrix.m44 * this.$matrix.m41);
559    var m42 = (mat.$matrix.m41 * this.$matrix.m12 + mat.$matrix.m42 * this.$matrix.m22
560               + mat.$matrix.m43 * this.$matrix.m32 + mat.$matrix.m44 * this.$matrix.m42);
561    var m43 = (mat.$matrix.m41 * this.$matrix.m13 + mat.$matrix.m42 * this.$matrix.m23
562               + mat.$matrix.m43 * this.$matrix.m33 + mat.$matrix.m44 * this.$matrix.m43);
563    var m44 = (mat.$matrix.m41 * this.$matrix.m14 + mat.$matrix.m42 * this.$matrix.m24
564               + mat.$matrix.m43 * this.$matrix.m34 + mat.$matrix.m44 * this.$matrix.m44);
565
566    this.$matrix.m11 = m11;
567    this.$matrix.m12 = m12;
568    this.$matrix.m13 = m13;
569    this.$matrix.m14 = m14;
570
571    this.$matrix.m21 = m21;
572    this.$matrix.m22 = m22;
573    this.$matrix.m23 = m23;
574    this.$matrix.m24 = m24;
575
576    this.$matrix.m31 = m31;
577    this.$matrix.m32 = m32;
578    this.$matrix.m33 = m33;
579    this.$matrix.m34 = m34;
580
581    this.$matrix.m41 = m41;
582    this.$matrix.m42 = m42;
583    this.$matrix.m43 = m43;
584    this.$matrix.m44 = m44;
585}
586
587J3DIMatrix4.prototype.divide = function(divisor)
588{
589    this.$matrix.m11 /= divisor;
590    this.$matrix.m12 /= divisor;
591    this.$matrix.m13 /= divisor;
592    this.$matrix.m14 /= divisor;
593
594    this.$matrix.m21 /= divisor;
595    this.$matrix.m22 /= divisor;
596    this.$matrix.m23 /= divisor;
597    this.$matrix.m24 /= divisor;
598
599    this.$matrix.m31 /= divisor;
600    this.$matrix.m32 /= divisor;
601    this.$matrix.m33 /= divisor;
602    this.$matrix.m34 /= divisor;
603
604    this.$matrix.m41 /= divisor;
605    this.$matrix.m42 /= divisor;
606    this.$matrix.m43 /= divisor;
607    this.$matrix.m44 /= divisor;
608
609}
610
611J3DIMatrix4.prototype.ortho = function(left, right, bottom, top, near, far)
612{
613    var tx = (left + right) / (left - right);
614    var ty = (top + bottom) / (top - bottom);
615    var tz = (far + near) / (far - near);
616
617    var matrix = new J3DIMatrix4();
618    matrix.$matrix.m11 = 2 / (left - right);
619    matrix.$matrix.m12 = 0;
620    matrix.$matrix.m13 = 0;
621    matrix.$matrix.m14 = 0;
622    matrix.$matrix.m21 = 0;
623    matrix.$matrix.m22 = 2 / (top - bottom);
624    matrix.$matrix.m23 = 0;
625    matrix.$matrix.m24 = 0;
626    matrix.$matrix.m31 = 0;
627    matrix.$matrix.m32 = 0;
628    matrix.$matrix.m33 = -2 / (far - near);
629    matrix.$matrix.m34 = 0;
630    matrix.$matrix.m41 = tx;
631    matrix.$matrix.m42 = ty;
632    matrix.$matrix.m43 = tz;
633    matrix.$matrix.m44 = 1;
634
635    this.multiply(matrix);
636}
637
638J3DIMatrix4.prototype.frustum = function(left, right, bottom, top, near, far)
639{
640    var matrix = new J3DIMatrix4();
641    var A = (right + left) / (right - left);
642    var B = (top + bottom) / (top - bottom);
643    var C = -(far + near) / (far - near);
644    var D = -(2 * far * near) / (far - near);
645
646    matrix.$matrix.m11 = (2 * near) / (right - left);
647    matrix.$matrix.m12 = 0;
648    matrix.$matrix.m13 = 0;
649    matrix.$matrix.m14 = 0;
650
651    matrix.$matrix.m21 = 0;
652    matrix.$matrix.m22 = 2 * near / (top - bottom);
653    matrix.$matrix.m23 = 0;
654    matrix.$matrix.m24 = 0;
655
656    matrix.$matrix.m31 = A;
657    matrix.$matrix.m32 = B;
658    matrix.$matrix.m33 = C;
659    matrix.$matrix.m34 = -1;
660
661    matrix.$matrix.m41 = 0;
662    matrix.$matrix.m42 = 0;
663    matrix.$matrix.m43 = D;
664    matrix.$matrix.m44 = 0;
665
666    this.multiply(matrix);
667}
668
669J3DIMatrix4.prototype.perspective = function(fovy, aspect, zNear, zFar)
670{
671    var top = Math.tan(fovy * Math.PI / 360) * zNear;
672    var bottom = -top;
673    var left = aspect * bottom;
674    var right = aspect * top;
675    this.frustum(left, right, bottom, top, zNear, zFar);
676}
677
678J3DIMatrix4.prototype.lookat = function(eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz)
679{
680    if (typeof eyez == 'object' && "length" in eyez) {
681        var t = eyez;
682        upx = t[0];
683        upy = t[1];
684        upz = t[2];
685
686        t = eyey;
687        centerx = t[0];
688        centery = t[1];
689        centerz = t[2];
690
691        t = eyex;
692        eyex = t[0];
693        eyey = t[1];
694        eyez = t[2];
695    }
696
697    var matrix = new J3DIMatrix4();
698
699    // Make rotation matrix
700
701    // Z vector
702    var zx = eyex - centerx;
703    var zy = eyey - centery;
704    var zz = eyez - centerz;
705    var mag = Math.sqrt(zx * zx + zy * zy + zz * zz);
706    if (mag) {
707        zx /= mag;
708        zy /= mag;
709        zz /= mag;
710    }
711
712    // Y vector
713    var yx = upx;
714    var yy = upy;
715    var yz = upz;
716
717    // X vector = Y cross Z
718    xx =  yy * zz - yz * zy;
719    xy = -yx * zz + yz * zx;
720    xz =  yx * zy - yy * zx;
721
722    // Recompute Y = Z cross X
723    yx = zy * xz - zz * xy;
724    yy = -zx * xz + zz * xx;
725    yx = zx * xy - zy * xx;
726
727    // cross product gives area of parallelogram, which is < 1.0 for
728    // non-perpendicular unit-length vectors; so normalize x, y here
729
730    mag = Math.sqrt(xx * xx + xy * xy + xz * xz);
731    if (mag) {
732        xx /= mag;
733        xy /= mag;
734        xz /= mag;
735    }
736
737    mag = Math.sqrt(yx * yx + yy * yy + yz * yz);
738    if (mag) {
739        yx /= mag;
740        yy /= mag;
741        yz /= mag;
742    }
743
744    matrix.$matrix.m11 = xx;
745    matrix.$matrix.m12 = xy;
746    matrix.$matrix.m13 = xz;
747    matrix.$matrix.m14 = 0;
748
749    matrix.$matrix.m21 = yx;
750    matrix.$matrix.m22 = yy;
751    matrix.$matrix.m23 = yz;
752    matrix.$matrix.m24 = 0;
753
754    matrix.$matrix.m31 = zx;
755    matrix.$matrix.m32 = zy;
756    matrix.$matrix.m33 = zz;
757    matrix.$matrix.m34 = 0;
758
759    matrix.$matrix.m41 = 0;
760    matrix.$matrix.m42 = 0;
761    matrix.$matrix.m43 = 0;
762    matrix.$matrix.m44 = 1;
763    matrix.translate(-eyex, -eyey, -eyez);
764
765    this.multiply(matrix);
766}
767
768// Returns true on success, false otherwise. All params are Array objects
769J3DIMatrix4.prototype.decompose = function(_translate, _rotate, _scale, _skew, _perspective)
770{
771    // Normalize the matrix.
772    if (this.$matrix.m44 == 0)
773        return false;
774
775    // Gather the params
776    var translate, rotate, scale, skew, perspective;
777
778    var translate = (_translate == undefined || !("length" in _translate)) ? new J3DIVector3 : _translate;
779    var rotate = (_rotate == undefined || !("length" in _rotate)) ? new J3DIVector3 : _rotate;
780    var scale = (_scale == undefined || !("length" in _scale)) ? new J3DIVector3 : _scale;
781    var skew = (_skew == undefined || !("length" in _skew)) ? new J3DIVector3 : _skew;
782    var perspective = (_perspective == undefined || !("length" in _perspective)) ? new Array(4) : _perspective;
783
784    var matrix = new J3DIMatrix4(this);
785
786    matrix.divide(matrix.$matrix.m44);
787
788    // perspectiveMatrix is used to solve for perspective, but it also provides
789    // an easy way to test for singularity of the upper 3x3 component.
790    var perspectiveMatrix = new J3DIMatrix4(matrix);
791
792    perspectiveMatrix.$matrix.m14 = 0;
793    perspectiveMatrix.$matrix.m24 = 0;
794    perspectiveMatrix.$matrix.m34 = 0;
795    perspectiveMatrix.$matrix.m44 = 1;
796
797    if (perspectiveMatrix._determinant4x4() == 0)
798        return false;
799
800    // First, isolate perspective.
801    if (matrix.$matrix.m14 != 0 || matrix.$matrix.m24 != 0 || matrix.$matrix.m34 != 0) {
802        // rightHandSide is the right hand side of the equation.
803        var rightHandSide = [ matrix.$matrix.m14, matrix.$matrix.m24, matrix.$matrix.m34, matrix.$matrix.m44 ];
804
805        // Solve the equation by inverting perspectiveMatrix and multiplying
806        // rightHandSide by the inverse.
807        var inversePerspectiveMatrix = new J3DIMatrix4(perspectiveMatrix);
808        inversePerspectiveMatrix.invert();
809        var transposedInversePerspectiveMatrix = new J3DIMatrix4(inversePerspectiveMatrix);
810        transposedInversePerspectiveMatrix.transpose();
811        transposedInversePerspectiveMatrix.multVecMatrix(perspective, rightHandSide);
812
813        // Clear the perspective partition
814        matrix.$matrix.m14 = matrix.$matrix.m24 = matrix.$matrix.m34 = 0
815        matrix.$matrix.m44 = 1;
816    }
817    else {
818        // No perspective.
819        perspective[0] = perspective[1] = perspective[2] = 0;
820        perspective[3] = 1;
821    }
822
823    // Next take care of translation
824    translate[0] = matrix.$matrix.m41
825    matrix.$matrix.m41 = 0
826    translate[1] = matrix.$matrix.m42
827    matrix.$matrix.m42 = 0
828    translate[2] = matrix.$matrix.m43
829    matrix.$matrix.m43 = 0
830
831    // Now get scale and shear. 'row' is a 3 element array of 3 component vectors
832    var row0 = new J3DIVector3(matrix.$matrix.m11, matrix.$matrix.m12, matrix.$matrix.m13);
833    var row1 = new J3DIVector3(matrix.$matrix.m21, matrix.$matrix.m22, matrix.$matrix.m23);
834    var row2 = new J3DIVector3(matrix.$matrix.m31, matrix.$matrix.m32, matrix.$matrix.m33);
835
836    // Compute X scale factor and normalize first row.
837    scale[0] = row0.vectorLength();
838    row0.divide(scale[0]);
839
840    // Compute XY shear factor and make 2nd row orthogonal to 1st.
841    skew[0] = row0.dot(row1);
842    row1.combine(row0, 1.0, -skew[0]);
843
844    // Now, compute Y scale and normalize 2nd row.
845    scale[1] = row1.vectorLength();
846    row1.divide(scale[1]);
847    skew[0] /= scale[1];
848
849    // Compute XZ and YZ shears, orthogonalize 3rd row
850    skew[1] = row1.dot(row2);
851    row2.combine(row0, 1.0, -skew[1]);
852    skew[2] = row1.dot(row2);
853    row2.combine(row1, 1.0, -skew[2]);
854
855    // Next, get Z scale and normalize 3rd row.
856    scale[2] = row2.vectorLength();
857    row2.divide(scale[2]);
858    skew[1] /= scale[2];
859    skew[2] /= scale[2];
860
861    // At this point, the matrix (in rows) is orthonormal.
862    // Check for a coordinate system flip.  If the determinant
863    // is -1, then negate the matrix and the scaling factors.
864    var pdum3 = new J3DIVector3(row1);
865    pdum3.cross(row2);
866    if (row0.dot(pdum3) < 0) {
867        for (i = 0; i < 3; i++) {
868            scale[i] *= -1;
869            row[0][i] *= -1;
870            row[1][i] *= -1;
871            row[2][i] *= -1;
872        }
873    }
874
875    // Now, get the rotations out
876    rotate[1] = Math.asin(-row0[2]);
877    if (Math.cos(rotate[1]) != 0) {
878        rotate[0] = Math.atan2(row1[2], row2[2]);
879        rotate[2] = Math.atan2(row0[1], row0[0]);
880    }
881    else {
882        rotate[0] = Math.atan2(-row2[0], row1[1]);
883        rotate[2] = 0;
884    }
885
886    // Convert rotations to degrees
887    var rad2deg = 180 / Math.PI;
888    rotate[0] *= rad2deg;
889    rotate[1] *= rad2deg;
890    rotate[2] *= rad2deg;
891
892    return true;
893}
894
895J3DIMatrix4.prototype._determinant2x2 = function(a, b, c, d)
896{
897    return a * d - b * c;
898}
899
900J3DIMatrix4.prototype._determinant3x3 = function(a1, a2, a3, b1, b2, b3, c1, c2, c3)
901{
902    return a1 * this._determinant2x2(b2, b3, c2, c3)
903         - b1 * this._determinant2x2(a2, a3, c2, c3)
904         + c1 * this._determinant2x2(a2, a3, b2, b3);
905}
906
907J3DIMatrix4.prototype._determinant4x4 = function()
908{
909    var a1 = this.$matrix.m11;
910    var b1 = this.$matrix.m12;
911    var c1 = this.$matrix.m13;
912    var d1 = this.$matrix.m14;
913
914    var a2 = this.$matrix.m21;
915    var b2 = this.$matrix.m22;
916    var c2 = this.$matrix.m23;
917    var d2 = this.$matrix.m24;
918
919    var a3 = this.$matrix.m31;
920    var b3 = this.$matrix.m32;
921    var c3 = this.$matrix.m33;
922    var d3 = this.$matrix.m34;
923
924    var a4 = this.$matrix.m41;
925    var b4 = this.$matrix.m42;
926    var c4 = this.$matrix.m43;
927    var d4 = this.$matrix.m44;
928
929    return a1 * this._determinant3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4)
930         - b1 * this._determinant3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4)
931         + c1 * this._determinant3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4)
932         - d1 * this._determinant3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4);
933}
934
935J3DIMatrix4.prototype._makeAdjoint = function()
936{
937    var a1 = this.$matrix.m11;
938    var b1 = this.$matrix.m12;
939    var c1 = this.$matrix.m13;
940    var d1 = this.$matrix.m14;
941
942    var a2 = this.$matrix.m21;
943    var b2 = this.$matrix.m22;
944    var c2 = this.$matrix.m23;
945    var d2 = this.$matrix.m24;
946
947    var a3 = this.$matrix.m31;
948    var b3 = this.$matrix.m32;
949    var c3 = this.$matrix.m33;
950    var d3 = this.$matrix.m34;
951
952    var a4 = this.$matrix.m41;
953    var b4 = this.$matrix.m42;
954    var c4 = this.$matrix.m43;
955    var d4 = this.$matrix.m44;
956
957    // Row column labeling reversed since we transpose rows & columns
958    this.$matrix.m11  =   this._determinant3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4);
959    this.$matrix.m21  = - this._determinant3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4);
960    this.$matrix.m31  =   this._determinant3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4);
961    this.$matrix.m41  = - this._determinant3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4);
962
963    this.$matrix.m12  = - this._determinant3x3(b1, b3, b4, c1, c3, c4, d1, d3, d4);
964    this.$matrix.m22  =   this._determinant3x3(a1, a3, a4, c1, c3, c4, d1, d3, d4);
965    this.$matrix.m32  = - this._determinant3x3(a1, a3, a4, b1, b3, b4, d1, d3, d4);
966    this.$matrix.m42  =   this._determinant3x3(a1, a3, a4, b1, b3, b4, c1, c3, c4);
967
968    this.$matrix.m13  =   this._determinant3x3(b1, b2, b4, c1, c2, c4, d1, d2, d4);
969    this.$matrix.m23  = - this._determinant3x3(a1, a2, a4, c1, c2, c4, d1, d2, d4);
970    this.$matrix.m33  =   this._determinant3x3(a1, a2, a4, b1, b2, b4, d1, d2, d4);
971    this.$matrix.m43  = - this._determinant3x3(a1, a2, a4, b1, b2, b4, c1, c2, c4);
972
973    this.$matrix.m14  = - this._determinant3x3(b1, b2, b3, c1, c2, c3, d1, d2, d3);
974    this.$matrix.m24  =   this._determinant3x3(a1, a2, a3, c1, c2, c3, d1, d2, d3);
975    this.$matrix.m34  = - this._determinant3x3(a1, a2, a3, b1, b2, b3, d1, d2, d3);
976    this.$matrix.m44  =   this._determinant3x3(a1, a2, a3, b1, b2, b3, c1, c2, c3);
977}
978
979//
980// J3DIVector3
981//
982J3DIVector3 = function(x,y,z)
983{
984    this.load(x,y,z);
985}
986
987J3DIVector3.prototype.load = function(x,y,z)
988{
989    if (typeof x == 'object' && "length" in x) {
990        this[0] = x[0];
991        this[1] = x[1];
992        this[2] = x[2];
993    }
994    else if (typeof x == 'number') {
995        this[0] = x;
996        this[1] = y;
997        this[2] = z;
998    }
999    else {
1000        this[0] = 0;
1001        this[1] = 0;
1002        this[2] = 0;
1003    }
1004}
1005
1006J3DIVector3.prototype.getAsArray = function()
1007{
1008    return [ this[0], this[1], this[2] ];
1009}
1010
1011J3DIVector3.prototype.getAsFloat32Array = function()
1012{
1013    return new Float32Array(this.getAsArray());
1014}
1015
1016J3DIVector3.prototype.vectorLength = function()
1017{
1018    return Math.sqrt(this[0] * this[0] + this[1] * this[1] + this[2] * this[2]);
1019}
1020
1021J3DIVector3.prototype.divide = function(divisor)
1022{
1023    this[0] /= divisor; this[1] /= divisor; this[2] /= divisor;
1024}
1025
1026J3DIVector3.prototype.cross = function(v)
1027{
1028    this[0] =  this[1] * v[2] - this[2] * v[1];
1029    this[1] = -this[0] * v[2] + this[2] * v[0];
1030    this[2] =  this[0] * v[1] - this[1] * v[0];
1031}
1032
1033J3DIVector3.prototype.dot = function(v)
1034{
1035    return this[0] * v[0] + this[1] * v[1] + this[2] * v[2];
1036}
1037
1038J3DIVector3.prototype.combine = function(v, ascl, bscl)
1039{
1040    this[0] = (ascl * this[0]) + (bscl * v[0]);
1041    this[1] = (ascl * this[1]) + (bscl * v[1]);
1042    this[2] = (ascl * this[2]) + (bscl * v[2]);
1043}
1044
1045J3DIVector3.prototype.multVecMatrix = function(matrix)
1046{
1047    var x = this[0];
1048    var y = this[1];
1049    var z = this[2];
1050
1051    this[0] = matrix.$matrix.m41 + x * matrix.$matrix.m11 + y * matrix.$matrix.m21 + z * matrix.$matrix.m31;
1052    this[1] = matrix.$matrix.m42 + x * matrix.$matrix.m12 + y * matrix.$matrix.m22 + z * matrix.$matrix.m32;
1053    this[2] = matrix.$matrix.m43 + x * matrix.$matrix.m13 + y * matrix.$matrix.m23 + z * matrix.$matrix.m33;
1054    var w = matrix.$matrix.m44 + x * matrix.$matrix.m14 + y * matrix.$matrix.m24 + z * matrix.$matrix.m34;
1055    if (w != 1 && w != 0) {
1056        this[0] /= w;
1057        this[1] /= w;
1058        this[2] /= w;
1059    }
1060}
1061
1062J3DIVector3.prototype.toString = function()
1063{
1064    return "["+this[0]+","+this[1]+","+this[2]+"]";
1065}
1066