【原创】《矩阵的史诗级玩法》连载六:创建我们的矩阵类

        在偏向计算机领域的博客上讲纯理论的东西始终有点不接地气,那就还是用代码实战一下吧。此时我的语言选择困难症开始发作,不知道用啥语言好,用我最熟悉的AS吧,会被喷,用我不熟悉的C++吧,也会被喷。C#,java啥的,对环境的依赖有点大。对于这种纯算法的教程,我不希望开发环境成为大家前进的绊脚石。所以最终就如上文所言,选择了目前很火但是开发环境依赖又很低的H5(也就是JS啦),不介意没代码提示的甚至可以用记事本敲完然后扔浏览器跑,很方便了有木有!反正吧,想用其他语言的,移植过去也不难,除非你用的是机器或者汇编。

        先把上篇用到的图片放出来,我们照着实现。

        

        ps:最后一行固定为0,0,1,理论上不予修改,但对于3D变换矩阵,最后一行是可修改的,它要控制齐次坐标,实现透视变换等功能的计算。

        其中,cx和cy具有典型的平移特征,只有平移的时候用到,所以一般改名为tx和ty,意为translateX和translateY,接着只剩4个变量了,用ax,ay,bx,by似乎跟业界有点不吻合,那我也照着他们的做法改成a,b,c,d

        问题来了,ax和by都有典型的缩放特征啊,为什么不改成sx和sy呢?这是因为旋转也用到了这两个变量:

        

        最后,变量名就改成这样:

        

        现在,我们就用比较恶心的function法来创建矩阵类了。

function Matrix(a, b, c, d, tx, ty) {
	this.a = isNaN(a) ? 1 : a;
	this.b = isNaN(b) ? 0 : b;
	this.c = isNaN(c) ? 0 : c;
	this.d = isNaN(d) ? 1 : d;
	this.tx = isNaN(tx) ? 0 : tx;
	this.ty = isNaN(ty) ? 0 : ty;
}

        这个看起来是方法的东西实际上可以当作类来使用,可以new它出来。可选参数的做法没有,只能用这种判断的方式实现。

        有没发现,矩阵中的默认值有的是0,有的是1,这是怎么来的呢?下面让我们把矩阵乘法的结果套进去一下看看。

        

        按照人类的思维习惯,一个初始被创建的矩阵应该不包含任何变换,也就是说,变换前后的结果要相等,即:

        

        ax+cy+tx=x

        bx+dy+ty=y

        可见,a=1,c=0,tx=0,b=0,d=1,ty=0即可让以上两等式恒成立。

        

        1x+0y+0=x

        0x+1y+0=y

        也就是说,初始的矩阵长这个样子。

        

        这个矩阵的特点是行数和列数相等(这样的矩阵叫方阵),并且左上到右下对角线上的元素等于1,其它元素为0。事实上,对于任意大小的方阵,主对角线元素为1,其它元素为0都可以使变换前后的结果一致,这样的矩阵称作单位矩阵。在研究变换的时候,我们可以在单位矩阵的基础上,通过跟单位矩阵的差异进行分析。

        知道了单位矩阵和初始数值以后,我们就来着手实现最常用的变换——乘法了。

        乘法的运算公式如下:

        

        写到代码上就是这个样子:

/**
 * 跟指定矩阵相乘,并返回一个新矩阵
 * @param matrix 要跟当前矩阵相乘的矩阵对象
 * @return 相乘后的结果
 *
 **/
function multiply(matrix) {
	var newMatrix = new Matrix();
	newMatrix.a = matrix.a * this.a + matrix.c * this.b;	
	newMatrix.b = matrix.b * this.a + matrix.d * this.b;
	newMatrix.c = matrix.a * this.c + matrix.c * this.d;
	newMatrix.d = matrix.b * this.c + matrix.d * this.d;
	newMatrix.tx = matrix.a * this.tx + matrix.c * this.ty + matrix.tx;
	newMatrix.ty = matrix.b * this.tx + matrix.d * this.ty + matrix.ty;
	return newMatrix;
}
this.multiply = multiply;

        以上代码请写入到function matrix的函数体内,本文下面的代码都按此操作。this.multiply=multiply一句是把类中的方法公有化的一个手段,有点奇葩。另外,js不支持运算符重载也是一个蛋疼之处。

        大家有没发现,参数被我放到了公式左边的矩阵上,而当前矩阵则在右边。这是矩阵乘法的规矩,默认用的是左乘,而且变换一个点也是把矩阵放在点的左边。

        此法生成一个新的矩阵,并没有对原矩阵的数据进行修改,实际应用中可能需要直接修改原始矩阵,那我们可以这样:

        myMatrix = myMatrix.multiply(matrix); //左乘

        myMatrix = matrix.multiply(myMatrix); //右乘

        此法可以实现创建副本和修改数据的切换,不好的地方是myMatrix的引用被更改了,为此,我们再封装两个方法:

/**
 * 矩阵左乘,会修改当前矩阵的数据
 * @param matrix 要跟当前矩阵相乘的矩阵对象
 *
 **/
function append(matrix)	{
	var newMatrix = multiply(matrix);
	this.copyFrom(newMatrix);
}
this.append = append;
	
/**
 * 矩阵左乘,会修改当前矩阵的数据
 * @param matrix 要跟当前矩阵相乘的矩阵对象
 *
 **/
function prepend(matrix) {
	var newMatrix = matrix.multiply(this);
	this.copyFrom(newMatrix);
}
this.prepend = prepend;

/**
 * 从指定矩阵中复制数据
 * @param matrix 被复制的源
 *
 **/
function copyFrom(matrix) {
	this.a = matrix.a;
	this.b = matrix.b;
	this.c = matrix.c;
	this.d = matrix.d;
	this.tx = matrix.tx;
	this.ty = matrix.ty;
}
this.copyFrom = copyFrom;
	this.a = matrix.a;
	this.b = matrix.b;
	this.c = matrix.c;
	this.d = matrix.d;
	this.tx = matrix.tx;
	this.ty = matrix.ty;
}
this.copyFrom = copyFrom;

        由于要复制的变量较多,所以我封装了个copyFrom的方法。

        有复制进来也应该有个复制出去的方法,似乎大家都喜欢用clone的方式,那我也照着这套路来。

        

/**
 * 返回当前矩阵的一个副本
 *
 **/
function clone() {
	return new Matrix(this.a, this.b, this.c, this.d, this.tx, this.ty);
}
this.clone = clone;

        有了矩阵合并以后,下一个就是矩阵与点的相乘。

/**
 * 当前矩阵对一个点进行转换,并返回新的点对象
 * @param 要被转换的点
 * @return 转换后的结果
 **/
function transformPoint(p) {
	var newPoint = new Point();
	newPoint.x = this.a * p.x + this.c * p.y + this.tx;
	newPoint.y = this.b * p.x + this.d * p.y + this.ty;
	return newPoint;
}
this.transformPoint = transformPoint;

        这地方我建立了一个Point对象,对于js来说,换成Object也无所谓,不过为了可读性,我们还是乖乖地建一个Point类吧。

        这个代码就不要放到function matrix的函数体内哦。

function Point(x, y) {
	this.x = isNaN(x) ? 0 : x;
	this.y = isNaN(y) ? 0 : y;
}

        至此,矩阵最基本的功能算是完成了,但是好像没有缩放旋转平移这些功能啊。没错,这些已经是针对具体应用场合的功能了,而非最底层,最抽象的算法。这样下来,大家是不是觉得矩阵其实很简单呢?

        看到这里,不知道有没人觉得被骗了,想吐槽我。这明明是最基础的矩阵数学知识,何来什么史诗级玩法?!屎诗级玩法还差不多。如果你真有此等感受,那我先得灰常感谢你,至少你坚持读到了最后,没有读到中途甚至是刚打开就关掉了这篇文章。这个系列连载到第6篇了,其套路是先从轻量的史诗级玩法(直线椭圆相交判断)切入,然后拓展出矩阵的所有基础知识,最后等这套体系完善了再推出重度的史诗级玩法,这几篇仅仅是个过渡,谁能跨过这道坎,谁将是最后的赢家!

        这篇写完,下篇补上常用变换后,就可以实现45度地图的效果,届时大家就能看到实实在在的视觉元素了。

猜你喜欢

转载自blog.csdn.net/iloveas2014/article/details/79516728
今日推荐