【原创】《矩阵的史诗级玩法》连载十三:基向量坐标变换矩阵的代码实现

 

本篇我们把上篇最后提到的矩阵实现出来并替换之前45度地图演示文件的矩阵上。

var baseX = new Point(0.87, 0.5); //ex基向量
var baseY = new Point(-0.32, 0.94); //ey基向量
var matrix = new Matrix();
matrix.a = baseX.x;	
matrix.b = baseX.y;
matrix.c = baseY.x;
matrix.d = baseY.y;
MatrixUtil.translate(matrix, 400, 0);

var matrixInvert = matrix.clone();
matrixInvert.invert();

可以发现,替换的只是初始矩阵,而它的逆变换无需改动任何代码,使用通用的求逆方法invert即可。

代码改过好几次了,现在我给出当前完整版本的html文件代码:

<!DOCTYPE html>
<html>
<head>
<title>斜铺砖块与矩阵</title>
<script src="Matrix.js"></script>
<script src="MatrixUtil.js"></script>
<script src="Point.js"></script>
</head>
<body>
<canvas width="800" height="800" id="canvas"></canvas>
</body>
<script>
	var canvas = document.getElementById("canvas");
	var context = canvas.getContext("2d");
	context.strokeStyle = "#0000cc";
	context.fillStyle = "#ccccff";
	context.lineWidth = 0.5;
	var gridNumX = 10;
	var gridNumY = 10;
	var unitSize = 40;
	
	var baseX = new Point(0.87, 0.5); //ex基向量
	var baseY = new Point(-0.32, 0.94); //ey基向量
	var matrix = new Matrix();
	matrix.a = baseX.x;	
	matrix.b = baseX.y;
	matrix.c = baseY.x;
	matrix.d = baseY.y;
	MatrixUtil.translate(matrix, 400, 0);

	var matrixInvert = matrix.clone();
	matrixInvert.invert();
	
	function draw(xIndex, yIndex)
	{
		for(var j = 0; j < gridNumY; j ++)
		{
			for(var i = 0; i < gridNumX; i ++)
			{
				var x = i * unitSize;
				var y = j * unitSize;
				//左上
				var leftTop = new Point(x, y);
				//右上
				var rightTop = new Point(x + unitSize, y);
				//右下
				var rightBottom = new Point(x + unitSize, y + unitSize);
				//左下
				var leftBottom = new Point(x, y + unitSize);
				//中间
				var center = new Point(x + unitSize * 0.5, y + unitSize * 0.5);
				//让编号对应上的砖块变成黄色
				context.fillStyle = (i == xIndex && j == yIndex) ? "#ffeecc" : "#ccccff";
				context.beginPath();
				var transformedLeftTop = matrix.transformPoint(leftTop);
				context.moveTo(transformedLeftTop.x, transformedLeftTop.y);
				var transformedRightTop = matrix.transformPoint(rightTop);
				context.lineTo(transformedRightTop.x, transformedRightTop.y);
				var transformedRightBottom = matrix.transformPoint(rightBottom);
				context.lineTo(transformedRightBottom.x, transformedRightBottom.y);
				var transformedLeftBottom = matrix.transformPoint(leftBottom);
				context.lineTo(transformedLeftBottom.x, transformedLeftBottom.y);
				context.closePath();
				context.stroke();
				context.fill();
				context.fillStyle = "#000";
				var transformedCenter = matrix.transformPoint(center);
				context.fillText(i + "," + j, transformedCenter.x - 5, transformedCenter.y + 5);//根据字号做了个粗糙的修正
			}
		}	
	}
	
	//一开始要先绘制一次,并且不让任何编号的砖块都高亮
	draw(-1, -1);
	
	canvas.addEventListener("mousemove", function(event)
	{
		//鼠标位置
		var mouseX = event.clientX-canvas.getBoundingClientRect().left;
		var mouseY = event.clientY-canvas.getBoundingClientRect().top;
		var transformedMouse = matrixInvert.transformPoint(new Point(mouseX, mouseY));
		var xIndex = Math.floor(transformedMouse.x / unitSize);
		var yIndex = Math.floor(transformedMouse.y / unitSize);
		draw(xIndex, yIndex);
	});
</script>
</html>

此处我用Point,也就是点的类来存储基向量。数学上,向量和点要严格区分,而且向量有自己的一些计算方法,但是形式上,它们却又非常相似,都是用(x,y)数对来表示(三维就是x,y,z,其它类推)。但为了后续不至于混淆,我们还是单独创建个向量类吧。

Vector2D.js代码

function Vector2D(x, y) {
	this.x = isNaN(x) ? 0 : x;
	this.y = isNaN(y) ? 0 : y;	
	
	function clone()
	{
		return new Vector2D(this.x, this.y);
	}
	this.clone = clone;
	
	/**
	 * 向量长度
	 *
	 **/
	Object.defineProperty(this, "length", {get: getLength, set: setLength});
	
	function getLength()
	{
		return Math.sqrt(x * x + y * y);
	}
	
	function setLength(value)
	{
		var length = getLength();
		var length = this.length;
		console.log(length)
		if(length > 0)
		{
			this.x *= value / length;
			this.y *= value / length;
		}
	}
	
	/**
	 * 向量单位化
	 *
	 **/
	function normalize()
	{
		setLength(1);
	}
	this.normalize = normalize;
	
	/**
	 * 向量加法
	 *
	 **/
	function add(vec)
	{
		return new Vector2D(this.x + vec.x, this.y + vec.y);
	}
	this.add = add;
	
	/**
	 * 向量减法
	 *
	 **/
	function subtract(vec)
	{
		return new Vector2D(this.x - vec.x, this.y - vec.y);
	}
	this.subtract = subtract;
	
	/**
	 * 向量数乘
	 *
	 **/
	function multiplyNumber(num)
	{
		return new Vector2D(this.x * num, this.y * num);
	}
	this.multiplyNumber = multiplyNumber;
	
	/**
	 * 向量点积
	 *
	 **/
	function dot(vec)
	{
		return this.x * vec.x + this.y * vec.y;
	}
	this.dot = dot;
	
	/**
	 * 向量叉积
	 *
	 **/
	function cross(vec)
	{
		return this.x * vec.y - this.y * vec.x;
	}
	this.cross = cross;
}

此处我把向量的基础运算实现了下,以后有可能用得到。

向量代数不是本系列教程的重点,就不展开讨论了,大家可以自行查阅资料学习。

把html代码中表示基向量的两个Point换成Vector2D,虽然效果上没有任何变化,但它的意义却非常深远。

 这篇跟上篇一样,内容不多,但已经被一些不太有营养的东西给撑长了。还有一个效果图,我贴上来之后这篇就该结束了,哈哈。

 大家可能有个疑问,怎么这些数字看起来这么刁钻,作为演示拿1,2这样的整数不更好?其实这里的向量有个特点,就是都为单位向量,即长度都等于1,大家可以分别算下两个向量的x与y的平方和。

那如果不用长度等于1的向量,效果会怎样呢?我们试试把baseX改成(2,1)看看。

超出画布范围了,把translate中的400改小点吧,比如200。

两边都越界了,不过我们不纠结这个细节。我们发现,砖块在横向上被拉长了,而使用单位向量的时候,砖块的边长并没有发生变化。

这里我直接给出一个结论:被拉长的倍数恰好等于向量的长度。推导就不推了,有兴趣的朋友可以找我单独讨论,不然又得扯远。

铺贴角度反映的仅仅是一个旋转或者斜切,为了变换的纯粹,剔除其它的干扰因素,业界都统一使用单位向量作为旋转斜切的基向量

我们发现,用基向量有很多的优点,一是通用性强,无需针对角度做特殊处理,二是运行效率较多步骤高,矩阵的每个元素都不需要计算,而且只需执行一次变换,三是数据存储方便,想想用我之前45度地图的方法,那就得整个矩阵存下来了,而且要从中反推步骤也困难,可读性差。

然而,基向量法之所以能实现得如此顺利,完全归功于我们封装好的求逆方法。有了求逆,我们在实现变换矩阵的时候才可以如此灵活,不易受到太多业务和细节的约束。从而使矩阵的应用领域更为广泛。

既然矩阵辣磨好用,那么,我们的眼光自然要放远一些,不能局限于这么一个简单的铺贴上面了。下一篇,就让我们解放双手,拥抱蓝天,向着史诗级的殿堂一起进发吧!

猜你喜欢

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