three.js源码阅读笔记二

2019-10-15  本文已影响0人  magicLiuyuan

本篇文章开始阅读three.js里面的Matrix4的实现。
three.js的对象的位置、旋转、缩放等变换信息都在这个矩阵里,因此它还是很关键的。

首先是一个Matrix4的构造函数

/**
 * @author mrdoob / http://mrdoob.com/
 * @author supereggbert / http://www.paulbrunt.co.uk/
 * @author philogb / http://blog.thejit.org/
 * @author jordi_ros / http://plattsoft.com
 * @author D1plo1d / http://github.com/D1plo1d
 * @author alteredq / http://alteredqualia.com/
 * @author mikael emtinger / http://gomo.se/
 * @author timknip / http://www.floorplanner.com/
 * @author bhouston / http://clara.io
 * @author WestLangley / http://github.com/WestLangley
 */

function Matrix4() {

    this.elements = [

        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1

    ];

    if ( arguments.length > 0 ) {

        console.error( 'THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.' );

    }

}

从实现过程里可以看到,这个构造函数的数据属性只有elements一个属性,它初始化了一个长度为16的数组,用来表示一个4*4的单位矩阵。下面的参数长度判断是为了提示现在版本的初始化一个matrix对象不能传参了,默认就是单位矩阵,要想设置值,得用它下面定义的set()方法。

下面它用Object.assign方法,将一个属性全是函数的对象复制到Matrix4的原型对象上,用来实现在Matrix4的原型上添加方法属性。

第一个属性是用来判断用此构造函数构造出来的对象是否是Matrix4

isMatrix4: true,

下面就是给这个矩阵Matrix4赋值的函数

    set: function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) {

        var te = this.elements;

        te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14;
        te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24;
        te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34;
        te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44;

        return this;

    },

这里需要按照先行后列的顺序进行传值,而有趣的是matrix4的elements属性却是按照先列后行的顺序依次存入到数组里面去的。也就是在matrix4的elements数组里,前4个值是第一列,后面4个是第二列,后面类推,这样做是方便它后面的取值赋值等计算。

接下来是一个单位化该4*4矩阵的函数

    identity: function () {

        this.set(

            1, 0, 0, 0,
            0, 1, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1

        );

        return this;

    },

该函数将此matrix4设置为单位4*4矩阵。

下面是一个克隆函数,即返回一个与此matrix4一模一样的值。

    clone: function () {

        return new Matrix4().fromArray( this.elements );

    },

这里它上面用到了fromArray函数,这个函数是从传入的array里设置该matrix4的值。它的实现过程

    fromArray: function ( array, offset ) {

        if ( offset === undefined ) offset = 0;

        for ( var i = 0; i < 16; i ++ ) {

            this.elements[ i ] = array[ i + offset ];

        }

        return this;

    },

可以看到它接收一个数组和一个偏移量参数,偏移量就是指从这个数组的哪一个位置开始是要设置的matrix4的值,偏移量开始后面是16个连续的值作为matrix4的值,默认偏移量是0。这里要注意array里面值的顺序是要按照先列后行的顺序来存储,因为elements数组里存储的矩阵顺序就是先列后行的顺序。

然后接下来是一个复制函数,即该matrix4从传入的matrix4参数来复制,并且设置为自己的值。

    copy: function ( m ) {

        var te = this.elements;
        var me = m.elements;

        te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ];
        te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ];
        te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ];
        te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ];

        return this;

    },

可以看到它就是将该matrix4的值的elements的值设置成和传入的matrix4的elements值一样。

下面是一个只复制位置的函数

    copyPosition: function ( m ) {

        var te = this.elements, me = m.elements;

        te[ 12 ] = me[ 12 ];
        te[ 13 ] = me[ 13 ];
        te[ 14 ] = me[ 14 ];

        return this;

    },

就是将传入参数matrix4的elements的倒数2,3,4个元素复制到该matrix4的对应位置,该位置的即44矩阵里的1行4列,2行4列,3行4列,的位置的值。那为什么这个位置数字表示位置呢?我们这里来分析一下。
我们用4
4矩阵来表示一个三维坐标下的点的平移旋转缩放信息(用4*4矩阵的原因后面分析),那么我们假设点P原来位置为(x,y,z),需要平移的量分别是(tx,ty,tz),那么平移后是x+tx,y+ty,z+tz,我们用矩阵来表示就是

矩阵乘法

计算一下这个矩阵,就会发现确实是x'=x+tx,y'=y+ty,z'=z+tz。

接下来是从矩阵的值里取值x,y,z轴的基础值

    extractBasis: function ( xAxis, yAxis, zAxis ) {

        xAxis.setFromMatrixColumn( this, 0 );
        yAxis.setFromMatrixColumn( this, 1 );
        zAxis.setFromMatrixColumn( this, 2 );

        return this;

    },

这里用到了Vector3的一个方法setFromMatrixColumn,那么这个方法我们也看一下

    setFromMatrixColumn: function ( m, index ) {

        return this.fromArray( m.elements, index * 4 );

    },

可以看到它又调用了自己的fromArray方法

    fromArray: function ( array, offset ) {

        if ( offset === undefined ) offset = 0;

        this.x = array[ offset ];
        this.y = array[ offset + 1 ];
        this.z = array[ offset + 2 ];

        return this;

    },

那么根据这几个函数,我们可以推测出,一开始的extractBasis方法,是将此matrix4的矩阵里elements数组的值,依次把它的第0,1,2个值赋值给传的vector3的x,y,z。后面依次是4,5,6赋值给yAxis,8,9,10赋值给zAxis。其实对应于矩阵就是该矩阵的第1列的1,2,3行是xAxis,第2列的1,2,3行是yAxis,第三列的1,2,3行是zAxis。


extractBasis函数意义

至于它代表的数学意义,现在我还有点不是很明白,后面再研究。

接下来是一个与上面相反的函数,是通过传入的xAxis, yAxis, zAxis来将此matrix4的值的矩阵的第1列的1,2,3行设置成xAxis,第2列的1,2,3行设置成yAxis,第三列的1,2,3行设置成zAxis。

    makeBasis: function ( xAxis, yAxis, zAxis ) {

        this.set(
            xAxis.x, yAxis.x, zAxis.x, 0,
            xAxis.y, yAxis.y, zAxis.y, 0,
            xAxis.z, yAxis.z, zAxis.z, 0,
            0, 0, 0, 1
        );

        return this;

    },

接下来是将传入的矩阵的旋转分量赋值给此矩阵的旋转分量

    extractRotation: function () {

        var v1 = new Vector3();

        return function extractRotation( m ) {

            // this method does not support reflection matrices

            var te = this.elements;
            var me = m.elements;

            var scaleX = 1 / v1.setFromMatrixColumn( m, 0 ).length();
            var scaleY = 1 / v1.setFromMatrixColumn( m, 1 ).length();
            var scaleZ = 1 / v1.setFromMatrixColumn( m, 2 ).length();

            te[ 0 ] = me[ 0 ] * scaleX;
            te[ 1 ] = me[ 1 ] * scaleX;
            te[ 2 ] = me[ 2 ] * scaleX;
            te[ 3 ] = 0;

            te[ 4 ] = me[ 4 ] * scaleY;
            te[ 5 ] = me[ 5 ] * scaleY;
            te[ 6 ] = me[ 6 ] * scaleY;
            te[ 7 ] = 0;

            te[ 8 ] = me[ 8 ] * scaleZ;
            te[ 9 ] = me[ 9 ] * scaleZ;
            te[ 10 ] = me[ 10 ] * scaleZ;
            te[ 11 ] = 0;

            te[ 12 ] = 0;
            te[ 13 ] = 0;
            te[ 14 ] = 0;
            te[ 15 ] = 1;

            return this;

        };

    }(),

它的实现过程是首先拿到传入参数的matrix4对应的点xAxis, yAxis, zAxis的长度,然后将此matrix4的对应位置都除以此长度。

接下来是将此矩阵通过给定的欧拉角进行旋转转换,将此矩阵的左上角的3*3的矩阵进行更新。

    makeRotationFromEuler: function ( euler ) {

        if ( ! ( euler && euler.isEuler ) ) {

            console.error( 'THREE.Matrix4: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.' );

        }

        var te = this.elements;

        var x = euler.x, y = euler.y, z = euler.z;
        var a = Math.cos( x ), b = Math.sin( x );
        var c = Math.cos( y ), d = Math.sin( y );
        var e = Math.cos( z ), f = Math.sin( z );

        if ( euler.order === 'XYZ' ) {

            var ae = a * e, af = a * f, be = b * e, bf = b * f;

            te[ 0 ] = c * e;
            te[ 4 ] = - c * f;
            te[ 8 ] = d;

            te[ 1 ] = af + be * d;
            te[ 5 ] = ae - bf * d;
            te[ 9 ] = - b * c;

            te[ 2 ] = bf - ae * d;
            te[ 6 ] = be + af * d;
            te[ 10 ] = a * c;

        } else if ( euler.order === 'YXZ' ) {

            var ce = c * e, cf = c * f, de = d * e, df = d * f;

            te[ 0 ] = ce + df * b;
            te[ 4 ] = de * b - cf;
            te[ 8 ] = a * d;

            te[ 1 ] = a * f;
            te[ 5 ] = a * e;
            te[ 9 ] = - b;

            te[ 2 ] = cf * b - de;
            te[ 6 ] = df + ce * b;
            te[ 10 ] = a * c;

        } else if ( euler.order === 'ZXY' ) {

            var ce = c * e, cf = c * f, de = d * e, df = d * f;

            te[ 0 ] = ce - df * b;
            te[ 4 ] = - a * f;
            te[ 8 ] = de + cf * b;

            te[ 1 ] = cf + de * b;
            te[ 5 ] = a * e;
            te[ 9 ] = df - ce * b;

            te[ 2 ] = - a * d;
            te[ 6 ] = b;
            te[ 10 ] = a * c;

        } else if ( euler.order === 'ZYX' ) {

            var ae = a * e, af = a * f, be = b * e, bf = b * f;

            te[ 0 ] = c * e;
            te[ 4 ] = be * d - af;
            te[ 8 ] = ae * d + bf;

            te[ 1 ] = c * f;
            te[ 5 ] = bf * d + ae;
            te[ 9 ] = af * d - be;

            te[ 2 ] = - d;
            te[ 6 ] = b * c;
            te[ 10 ] = a * c;

        } else if ( euler.order === 'YZX' ) {

            var ac = a * c, ad = a * d, bc = b * c, bd = b * d;

            te[ 0 ] = c * e;
            te[ 4 ] = bd - ac * f;
            te[ 8 ] = bc * f + ad;

            te[ 1 ] = f;
            te[ 5 ] = a * e;
            te[ 9 ] = - b * e;

            te[ 2 ] = - d * e;
            te[ 6 ] = ad * f + bc;
            te[ 10 ] = ac - bd * f;

        } else if ( euler.order === 'XZY' ) {

            var ac = a * c, ad = a * d, bc = b * c, bd = b * d;

            te[ 0 ] = c * e;
            te[ 4 ] = - f;
            te[ 8 ] = d * e;

            te[ 1 ] = ac * f + bd;
            te[ 5 ] = a * e;
            te[ 9 ] = ad * f - bc;

            te[ 2 ] = bc * f - ad;
            te[ 6 ] = b * e;
            te[ 10 ] = bd * f + ac;

        }

        // bottom row
        te[ 3 ] = 0;
        te[ 7 ] = 0;
        te[ 11 ] = 0;

        // last column
        te[ 12 ] = 0;
        te[ 13 ] = 0;
        te[ 14 ] = 0;
        te[ 15 ] = 1;

        return this;

    },

详细推理请看 维基百科 欧拉角, 关于欧拉角已经万向节死锁,还有另一篇博文参考万向节死锁,不过此处的实现正是给定了12种情况,在这12种情况里,是不会发生万向节死锁的问题的。

接下来是通过四元数进行旋转

    makeRotationFromQuaternion: function () {

        var zero = new Vector3( 0, 0, 0 );
        var one = new Vector3( 1, 1, 1 );

        return function makeRotationFromQuaternion( q ) {

            return this.compose( zero, q, one );

        };

    }(),

可以看到它是将Vector3( 0, 0, 0 ), q, Vector3( 1, 1, 1 )作为参数传入compose函数,那么我们看一下compose函数是什么

compose: function ( position, quaternion, scale ) {

        var te = this.elements;

        var x = quaternion._x, y = quaternion._y, z = quaternion._z, w = quaternion._w;
        var x2 = x + x, y2 = y + y, z2 = z + z;
        var xx = x * x2, xy = x * y2, xz = x * z2;
        var yy = y * y2, yz = y * z2, zz = z * z2;
        var wx = w * x2, wy = w * y2, wz = w * z2;

        var sx = scale.x, sy = scale.y, sz = scale.z;

            te[ 0 ] = ( 1 - ( yy + zz ) ) * sx;
            te[ 1 ] = ( xy + wz ) * sx;
            te[ 2 ] = ( xz - wy ) * sx;
            te[ 3 ] = 0;

            te[ 4 ] = ( xy - wz ) * sy;
            te[ 5 ] = ( 1 - ( xx + zz ) ) * sy;
            te[ 6 ] = ( yz + wx ) * sy;
            te[ 7 ] = 0;

            te[ 8 ] = ( xz + wy ) * sz;
            te[ 9 ] = ( yz - wx ) * sz;
            te[ 10 ] = ( 1 - ( xx + yy ) ) * sz;
            te[ 11 ] = 0;

            te[ 12 ] = position.x;
            te[ 13 ] = position.y;
            te[ 14 ] = position.z;
            te[ 15 ] = 1;

            return this;

    },

此方法是传入位置、旋转、缩放参数,然后根据这三个参数计算更新后的矩阵变换。其中旋转除了上面的欧拉角可以定义,还可以用四元数表示。关于四元数的介绍,可以参考 如何形象的理解四元数

接下来是三个按照指定轴旋转特定角度的matrix4的更新

    makeRotationX: function ( theta ) {

        var c = Math.cos( theta ), s = Math.sin( theta );

        this.set(

            1, 0, 0, 0,
            0, c, - s, 0,
            0, s, c, 0,
            0, 0, 0, 1

        );

        return this;

    },

    makeRotationY: function ( theta ) {

        var c = Math.cos( theta ), s = Math.sin( theta );

        this.set(

             c, 0, s, 0,
             0, 1, 0, 0,
            - s, 0, c, 0,
             0, 0, 0, 1

        );

        return this;

    },

    makeRotationZ: function ( theta ) {

        var c = Math.cos( theta ), s = Math.sin( theta );

        this.set(

            c, - s, 0, 0,
            s, c, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1

        );

        return this;

    },

这里的数学推导可以自己去推算一下。
下面是一个缩放的函数,将此矩阵按照x,y,z的值进行缩放计算

    makeScale: function ( x, y, z ) {

        this.set(

            x, 0, 0, 0,
            0, y, 0, 0,
            0, 0, z, 0,
            0, 0, 0, 1

        );

        return this;

    },

下面是一个剪切面计算,就是按照x,y,z轴上的裁剪量进行裁剪计算

    makeShear: function ( x, y, z ) {

        this.set(

            1, y, z, 0,
            x, 1, z, 0,
            x, y, 1, 0,
            0, 0, 0, 1

        );

        return this;

    },

下面这个方法是将此matrix4的位置、旋转、缩放分量赋值给传入的对应参数

    decompose: function () {

        var vector = new Vector3();
        var matrix = new Matrix4();

        return function decompose( position, quaternion, scale ) {

            var te = this.elements;

            var sx = vector.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length();
            var sy = vector.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length();
            var sz = vector.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length();

            // if determine is negative, we need to invert one scale
            var det = this.determinant();
            if ( det < 0 ) sx = - sx;

            position.x = te[ 12 ];
            position.y = te[ 13 ];
            position.z = te[ 14 ];

            // scale the rotation part
            matrix.copy( this );

            var invSX = 1 / sx;
            var invSY = 1 / sy;
            var invSZ = 1 / sz;

            matrix.elements[ 0 ] *= invSX;
            matrix.elements[ 1 ] *= invSX;
            matrix.elements[ 2 ] *= invSX;

            matrix.elements[ 4 ] *= invSY;
            matrix.elements[ 5 ] *= invSY;
            matrix.elements[ 6 ] *= invSY;

            matrix.elements[ 8 ] *= invSZ;
            matrix.elements[ 9 ] *= invSZ;
            matrix.elements[ 10 ] *= invSZ;

            quaternion.setFromRotationMatrix( matrix );

            scale.x = sx;
            scale.y = sy;
            scale.z = sz;

            return this;

        };

    }(),

下面是两个关于相机的方法,这两个方法分别由透视相机和正交相机的内部更新相机矩阵使用

makePerspective: function ( left, right, top, bottom, near, far ) {

        if ( far === undefined ) {

            console.warn( 'THREE.Matrix4: .makePerspective() has been redefined and has a new signature. Please check the docs.' );

        }

        var te = this.elements;
        var x = 2 * near / ( right - left );
        var y = 2 * near / ( top - bottom );

        var a = ( right + left ) / ( right - left );
        var b = ( top + bottom ) / ( top - bottom );
        var c = - ( far + near ) / ( far - near );
        var d = - 2 * far * near / ( far - near );

        te[ 0 ] = x;    te[ 4 ] = 0;    te[ 8 ] = a;    te[ 12 ] = 0;
        te[ 1 ] = 0;    te[ 5 ] = y;    te[ 9 ] = b;    te[ 13 ] = 0;
        te[ 2 ] = 0;    te[ 6 ] = 0;    te[ 10 ] = c;   te[ 14 ] = d;
        te[ 3 ] = 0;    te[ 7 ] = 0;    te[ 11 ] = - 1; te[ 15 ] = 0;

        return this;

    },

    makeOrthographic: function ( left, right, top, bottom, near, far ) {

        var te = this.elements;
        var w = 1.0 / ( right - left );
        var h = 1.0 / ( top - bottom );
        var p = 1.0 / ( far - near );

        var x = ( right + left ) * w;
        var y = ( top + bottom ) * h;
        var z = ( far + near ) * p;

        te[ 0 ] = 2 * w;    te[ 4 ] = 0;    te[ 8 ] = 0;    te[ 12 ] = - x;
        te[ 1 ] = 0;    te[ 5 ] = 2 * h;    te[ 9 ] = 0;    te[ 13 ] = - y;
        te[ 2 ] = 0;    te[ 6 ] = 0;    te[ 10 ] = - 2 * p; te[ 14 ] = - z;
        te[ 3 ] = 0;    te[ 7 ] = 0;    te[ 11 ] = 0;   te[ 15 ] = 1;

        return this;

    },

接下来是判定给定的matrix4与此matrix4是否相等的方法

equals: function ( matrix ) {

        var te = this.elements;
        var me = matrix.elements;

        for ( var i = 0; i < 16; i ++ ) {

            if ( te[ i ] !== me[ i ] ) return false;

        }

        return true;

    },

可以看到它的对比过程是直接比对元素elements里面的每一个元素是否一样。

然后是将矩阵的值设置为传入的数组里的值的方法,即将此矩阵的元素赋值给定的数组里。

    toArray: function ( array, offset ) {

        if ( array === undefined ) array = [];
        if ( offset === undefined ) offset = 0;

        var te = this.elements;

        array[ offset ] = te[ 0 ];
        array[ offset + 1 ] = te[ 1 ];
        array[ offset + 2 ] = te[ 2 ];
        array[ offset + 3 ] = te[ 3 ];

        array[ offset + 4 ] = te[ 4 ];
        array[ offset + 5 ] = te[ 5 ];
        array[ offset + 6 ] = te[ 6 ];
        array[ offset + 7 ] = te[ 7 ];

        array[ offset + 8 ] = te[ 8 ];
        array[ offset + 9 ] = te[ 9 ];
        array[ offset + 10 ] = te[ 10 ];
        array[ offset + 11 ] = te[ 11 ];

        array[ offset + 12 ] = te[ 12 ];
        array[ offset + 13 ] = te[ 13 ];
        array[ offset + 14 ] = te[ 14 ];
        array[ offset + 15 ] = te[ 15 ];

        return array;

    }

后面还有一个构造一个旋转矩阵的方法,将该矩阵变为从传入的三个参数eye, center,up,里,从eye的位置去看center的位置,方向是up。

    lookAt: function () {

        var x = new Vector3();
        var y = new Vector3();
        var z = new Vector3();

        return function lookAt( eye, target, up ) {

            var te = this.elements;

            z.subVectors( eye, target );

            if ( z.lengthSq() === 0 ) {

                // eye and target are in the same position

                z.z = 1;

            }

            z.normalize();
            x.crossVectors( up, z );

            if ( x.lengthSq() === 0 ) {

                // up and z are parallel

                if ( Math.abs( up.z ) === 1 ) {

                    z.x += 0.0001;

                } else {

                    z.z += 0.0001;

                }

                z.normalize();
                x.crossVectors( up, z );

            }

            x.normalize();
            y.crossVectors( z, x );

            te[ 0 ] = x.x; te[ 4 ] = y.x; te[ 8 ] = z.x;
            te[ 1 ] = x.y; te[ 5 ] = y.y; te[ 9 ] = z.y;
            te[ 2 ] = x.z; te[ 6 ] = y.z; te[ 10 ] = z.z;

            return this;

        };

    }(),

然后是矩阵与矩阵之间的运算的方法

    multiply: function ( m, n ) {

        if ( n !== undefined ) {

            console.warn( 'THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.' );
            return this.multiplyMatrices( m, n );

        }

        return this.multiplyMatrices( this, m );

    },

该函数是返回此矩阵左乘传入的m矩阵,并且将结果赋值给此矩阵,它的实现是调用了下面的这个矩阵的左乘函数

    multiplyMatrices: function ( a, b ) {

        var ae = a.elements;
        var be = b.elements;
        var te = this.elements;

        var a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ];
        var a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ];
        var a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ];
        var a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ];

        var b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ];
        var b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ];
        var b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ];
        var b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ];

        te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
        te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
        te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
        te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;

        te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
        te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
        te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
        te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;

        te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
        te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
        te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
        te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;

        te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
        te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
        te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
        te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;

        return this;

    },

此方法是将接受的两个参数矩阵a,b,用a左乘b,然后将结果赋值给此矩阵。
下面是一个参数左乘此矩阵并把结果赋值给此矩阵的方法

    premultiply: function ( m ) {

        return this.multiplyMatrices( m, this );

    },

下面是将矩阵里的每一个值都缩放传入的参数的倍数

    multiplyScalar: function ( s ) {

        var te = this.elements;

        te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s;
        te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s;
        te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s;
        te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s;

        return this;

    },

接下来的这个方法是传入一个BufferAtteibute属性,然后将此矩阵应用与此属性参数里的每一个3d向量

    applyToBufferAttribute: function () {

        var v1 = new Vector3();

        return function applyToBufferAttribute( attribute ) {

            for ( var i = 0, l = attribute.count; i < l; i ++ ) {

                v1.x = attribute.getX( i );
                v1.y = attribute.getY( i );
                v1.z = attribute.getZ( i );

                v1.applyMatrix4( this );

                attribute.setXYZ( i, v1.x, v1.y, v1.z );

            }

            return attribute;

        };

    }(),

实现过程就是获取参数BufferAtteibute里的每一个顶点的x,y,z,然后将此点做此矩阵的转换,最后再将结果设置回BufferAtteibute对应的位置的点。

下面这个函数返回此矩阵的行列式

    determinant: function () {

        var te = this.elements;

        var n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ];
        var n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ];
        var n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ];
        var n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ];

        //TODO: make this more efficient
        //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm )

        return (
            n41 * (
                + n14 * n23 * n32
                 - n13 * n24 * n32
                 - n14 * n22 * n33
                 + n12 * n24 * n33
                 + n13 * n22 * n34
                 - n12 * n23 * n34
            ) +
            n42 * (
                + n11 * n23 * n34
                 - n11 * n24 * n33
                 + n14 * n21 * n33
                 - n13 * n21 * n34
                 + n13 * n24 * n31
                 - n14 * n23 * n31
            ) +
            n43 * (
                + n11 * n24 * n32
                 - n11 * n22 * n34
                 - n14 * n21 * n32
                 + n12 * n21 * n34
                 + n14 * n22 * n31
                 - n12 * n24 * n31
            ) +
            n44 * (
                - n13 * n22 * n31
                 - n11 * n23 * n32
                 + n11 * n22 * n33
                 + n13 * n21 * n32
                 - n12 * n21 * n33
                 + n12 * n23 * n31
            )

        );

    },

下面这个方法是转置该矩阵

    transpose: function () {

        var te = this.elements;
        var tmp;

        tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp;
        tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp;
        tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp;

        tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp;
        tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp;
        tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp;

        return this;

    },

接下来是传入一个vector3,根据此vector3的值更新此矩阵的位置

    setPosition: function ( v ) {

        var te = this.elements;

        te[ 12 ] = v.x;
        te[ 13 ] = v.y;
        te[ 14 ] = v.z;

        return this;

    },

下面这个方式是将此矩阵设置为传入的参数矩阵的逆矩阵,如果传入第二个参数为true,则在第一个参数不可逆的时候抛出一个错误,否则就将此矩阵设置为4*4的单位矩阵

    getInverse: function ( m, throwOnDegenerate ) {

        // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
        var te = this.elements,
            me = m.elements,

            n11 = me[ 0 ], n21 = me[ 1 ], n31 = me[ 2 ], n41 = me[ 3 ],
            n12 = me[ 4 ], n22 = me[ 5 ], n32 = me[ 6 ], n42 = me[ 7 ],
            n13 = me[ 8 ], n23 = me[ 9 ], n33 = me[ 10 ], n43 = me[ 11 ],
            n14 = me[ 12 ], n24 = me[ 13 ], n34 = me[ 14 ], n44 = me[ 15 ],

            t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44,
            t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44,
            t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44,
            t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;

        var det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;

        if ( det === 0 ) {

            var msg = "THREE.Matrix4: .getInverse() can't invert matrix, determinant is 0";

            if ( throwOnDegenerate === true ) {

                throw new Error( msg );

            } else {

                console.warn( msg );

            }

            return this.identity();

        }

        var detInv = 1 / det;

        te[ 0 ] = t11 * detInv;
        te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv;
        te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv;
        te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv;

        te[ 4 ] = t12 * detInv;
        te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv;
        te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv;
        te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv;

        te[ 8 ] = t13 * detInv;
        te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv;
        te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv;
        te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv;

        te[ 12 ] = t14 * detInv;
        te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv;
        te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv;
        te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv;

        return this;

    },

下面这个scale方法接收一个vector3作为参数,并且将此矩阵的第一列乘以该vector3的x,第二列乘以y,第三列乘以z。

    scale: function ( v ) {

        var te = this.elements;
        var x = v.x, y = v.y, z = v.z;

        te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z;
        te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z;
        te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z;
        te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z;

        return this;

    },

下面这个方法是返回此矩阵在x,y,z轴上的最大的值

    getMaxScaleOnAxis: function () {

        var te = this.elements;

        var scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ];
        var scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ];
        var scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ];

        return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) );

    },

最后这个方法是接受x,y,z,将此矩阵的位置属性更新为传入的这三个值

    makeTranslation: function ( x, y, z ) {

        this.set(

            1, 0, 0, x,
            0, 1, 0, y,
            0, 0, 1, z,
            0, 0, 0, 1

        );

        return this;

    },

矩阵的属性方法就分析完了,基本都是44矩阵的一些数学运算。关于44矩阵具体代表一个点的什么样的位置变换,后面找机会再做一次专门的分析。

上一篇下一篇

猜你喜欢

热点阅读