[从头学数学] 第152节 旋转

剧情提要:

[机器小伟]在[工程师阿伟]的陪同下进入了筑基后期的修炼,

这次要修炼的目标是[旋转]。

正剧开始:

星历2016年03月26日 16:11:34, 银河系厄尔斯星球中华帝国江南行省。

[工程师阿伟]正在和[机器小伟]一起研究[旋转]。

<span style="font-size:18px;">//旋转
		var r = 20;
		var r0 = 5*r;
        config.setSector(1,1,1,1);
        config.graphPaper2D(0, 0, r);
        config.axis2D(0, 0, 180, 1);    

		var a = shape.nEdge(100, 100, r0, 3, 0);

		var transform = new Transform();

		var b = transform.rotate(a, Math.PI);

		shape.strokeDraw(a, 'red');
		shape.strokeDraw(b, 'blue');
</span>

<span style="font-size:18px;">//三角形的旋转
		var r = 20;
		var r0 = 5*r;
        config.setSector(1,1,1,1);
        config.graphPaper2D(0, 0, r);
        config.axis2D(0, 0, 180, 1);    

		var triangle = new Triangle();
		var transform = new Transform();

		var a = transform.translate(triangle.know2edges([100, 50]), -100, 0);
		var b = transform.rotate(a, Math.PI/2);

		shape.strokeDraw(a, 'red');
		shape.strokeDraw(b, 'blue');</span>

<span style="font-size:18px;">	//五角星旋转72度后重合
		var r = 20;
		var r0 = 5*r;
        config.setSector(1,1,1,1);
        config.graphPaper2D(0, 0, r);  

		config.setSector(1,2,1,1);
        config.axis2D(0, 0, 180, 1);    

		var triangle = new Triangle();
		var transform = new Transform();

		var a = shape.nStar(0, 0, r0, 5, -Math.PI/2);
		shape.strokeDraw([].concat(a), 'red');

		config.setSector(1,2,1,2);
        config.axis2D(0, 0, 180, 1);
		var b = transform.rotate(a, Math.PI*0.4);
		shape.strokeDraw([].concat(b), 'blue');

	//正三角形旋转120度后重合
		var r = 20;
		var r0 = 5*r;
        config.setSector(1,1,1,1);
        config.graphPaper2D(0, 0, r);  

		config.setSector(1,2,1,1);
        config.axis2D(0, 0, 180, 1);    

		var triangle = new Triangle();
		var transform = new Transform();

		var a = shape.nEdge(0, 0, r0, 3, Math.PI);
		shape.strokeDraw([].concat(a), 'red');

		config.setSector(1,2,1,2);
        config.axis2D(0, 0, 180, 1);
		var b = transform.rotate(a, Math.PI/3*2);
		shape.strokeDraw([].concat(b), 'blue');</span>

<span style="font-size:18px;">	//三角形的中心对称
		var r = 20;
		var r0 = 5*r;
        config.setSector(1,1,1,1);
        config.graphPaper2D(0, 0, r);
		config.axis2D(0, 0, 180);

		var triangle = new Triangle();
		var transform = new Transform();

		var a = transform.scale(triangle.know3edges([4, 5, 6]), 30, 30);
		shape.strokeDraw([].concat(a), 'red');

		var b = transform.rotate(a, Math.PI);
		shape.strokeDraw([].concat(b), 'blue');</span>

<span style="font-size:18px;">	//点的中心对称
		var r = 20;
		var r0 = 5*r;
        config.setSector(1,1,1,1);
        config.graphPaper2D(0, 0, r);
		config.axis2D(0, 0, 180);

		var triangle = new Triangle();
		var transform = new Transform();

		var points = [[4,0],[0,-3],[2,1],[-1,2],[-3,-4]];
		var a = transform.scale(points, r, r);
		shape.pointDraw([].concat(a), 'red');

		var b = transform.flipXY(a);
		shape.pointDraw([].concat(b), 'blue');</span>

<span style="font-size:18px;">	//例2 三角形的中心对称
		var r = 50;
		var r0 = 5*r;
        config.setSector(1,1,1,1);
        config.graphPaper2D(0, 0, r);
		config.axis2D(0, 0, 180);

		var triangle = new Triangle();
		var transform = new Transform();

		var points = [[-4,1],[-1,-1],[-3,2]];
		var a = transform.scale(points, r, r);
		shape.pointDraw([].concat(a), 'red');
		shape.strokeDraw([].concat(a), 'pink');

		var b = transform.flipXY(a);
		shape.pointDraw([].concat(b), 'blue');
		shape.strokeDraw([].concat(b), '#0033AA');</span>

本节用到的工具:

/**
* @usage   常用形状类
* @author  mw
* @date    2015年11月29日  星期日  10:21:18
* @param
* @return
*
*/

var shape = function Shape() {

	//以给定点为中点的矩形
	this.strokeRect = function(x, y, w, h) {
		w = Math.abs(w);
		h = Math.abs(h);
		return plot.strokeRect(x-w/2, y-h/2, w, h);
	}

	//以给定点为中点的矩形
	this.fillRect = function(x, y, w, h) {
		w = Math.abs(w);
		h = Math.abs(h);
		return plot.fillRect(x-w/2, y-h/2, w, h);
	}

/**
* @usage   绘制点阵列
* @author  mw
* @date    2016年02月21日  星期日  15:16:47
* @param
* @return
*
*/
	this.pointDraw = function(array, style, scale, lable, showLable) {
		//已经考虑到y轴坐标的取反问题,只需传入原始坐标数组即可
		style = style ? style : 'black';
		scale = scale ? scale : 1;

		lable = lable ? lable : 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
		showLable = showLable ? showLable : 1;
		var x = y = index = 0;

		plot.save()
			.setFillStyle(style);

		var a = new Array();
		a = array[0];
		//y坐标取反是因为canvas中y坐标以向下为正,与笛卡尔坐标系相反
		if (a.length != 2) {
			//坐标是流水模式,既x1, y1, x2, y2,...
			while (array.length > 0) {
				x = array.shift()*scale;
				y = -array.shift()*scale;
				shape.fillCircle(x, y, 5);
				plot.fillText(lable[index++], x+5, y+10, 20);
			}
		}
		else {
			//坐标是有序对模式,即[x1, y1], [x2, y2], ...
			while (array.length > 0) {
				a = array.shift();
				x = a[0]*scale;
				y = -a[1]*scale;

				shape.fillCircle(x, y, 5);
				plot.fillText(lable[index++], x+5, y+10, 20);

			}
		}

		plot.restore();

	}

//连接成折线
	this.multiLineDraw = function(array,style, scale) {
		//已经考虑到y轴坐标的取反问题,只需传入原始坐标数组即可
		style = style ? style : 'black';
		scale = scale ? scale : 1;

		plot.save()
			.setStrokeStyle(style);

		var a = new Array();
		a = array[0];
		if (a.length != 2) {

			if (array.length > 2 && array.length % 2 == 0) {
				plot.beginPath()
					.moveTo(array.shift()*scale, -array.shift()*scale);
				while (array.length > 2) {
					plot.lineTo(array.shift()*scale, -array.shift()*scale);
				}
				plot.lineTo(array[0]*scale, -array[1]*scale)
					.moveTo(array[0]*scale, -array[1]*scale);
				plot.closePath()
					.stroke();
			}  

		}
		else {
			if (array.length > 2) {
				a = array.shift();
				plot.beginPath()
					.moveTo(a[0]*scale, -a[1]*scale);  

				while (array.length > 0) {
					a = array.shift();
					plot.lineTo(a[0]*scale, -a[1]*scale);
				}
				plot.moveTo(a[0]*scale, -a[1]*scale);
				plot.closePath()
					.stroke();
			}
			else {
				var a = array.shift();
				var b = array.shift();
				plot.beginPath()
					.moveTo(a[0]*scale, -a[1]*scale)
					.lineTo(b[0]*scale, -b[1]*scale)
					.closePath()
					.stroke();
			}

		}

		plot.restore();

	}

	this.fillDraw = function(array, style, scale) {
		//已经考虑到y轴坐标的取反问题,只需传入原始坐标数组即可
		style = style ? style : 'black';
		scale = scale ? scale : 1;

		plot.save()
			.setFillStyle(style);

		var a = array[0];
		if (a.length != 2) {

			if (array.length > 2 && array.length % 2 == 0) {
				plot.beginPath()
					.moveTo(array.shift()*scale, -array.shift()*scale);
				while (array.length > 0) {
					plot.lineTo(array.shift()*scale, -array.shift()*scale);
				}
				plot.closePath()
					.fill();
			}
		}
		else {
			if (array.length > 2) {
				a = array.shift();
				plot.beginPath()
					.moveTo(a[0]*scale, -a[1]*scale);  

				while (array.length > 0) {
					a = array.shift();
					plot.lineTo(a[0]*scale, -a[1]*scale);
				}
				plot.closePath()
					.fill();
			}

		}

		plot.restore();

	}

	this.strokeDraw = function(array,style, scale) {
		//已经考虑到y轴坐标的取反问题,只需传入原始坐标数组即可
		style = style ? style : 'black';
		scale = scale ? scale : 1;

		plot.save()
			.setStrokeStyle(style);

		var a = array[0];
		if (a.length != 2) {

			if (array.length > 2 && array.length % 2 == 0) {
				plot.beginPath()
					.moveTo(array.shift()*scale, -array.shift()*scale);
				while (array.length > 0) {
					plot.lineTo(array.shift()*scale, -array.shift()*scale);
				}
				plot.closePath()
					.stroke();
			}
		}
		else {
			if (array.length > 2) {
				a = array.shift();
				plot.beginPath()
					.moveTo(a[0]*scale, -a[1]*scale);  

				while (array.length > 0) {
					a = array.shift();
					plot.lineTo(a[0]*scale, -a[1]*scale);
				}
				plot.closePath()
					.stroke();
			}

		}

		plot.restore();

	}

this.angleDraw = function(array, style, scale, vertexLabel) {
	//vertexLabel是顶点编号顺序字符串 ABC,...
	style = style ? style : 'black';
	//array是一个存放二维坐标点序列的数组
	var a0 = new Array();
	a0 = [].concat(array);

	scale = scale ? scale : 1;
	var len = a0.length;

	if (scale != 1 && scale > 0) {
		for (var i = 0; i < len; i++) {
			for (var j = 0; j < 2; j++) {
				a0[i][j]*=scale;
			}
		}
	}

	//进行环状排序,这样传入的array就可以任意顺序放置坐标点。
	var a = this.angularSort(a0);	

	//分两次绘点和连线
	var tmp = [].concat(a);
	this.pointDraw(tmp, style);
	tmp = [].concat(a);
	this.strokeDraw(tmp, style);

	var d1, d2, d3, angle;
	var x1,y1, x2, y2, x3, y3;
	var s;
	//坐标点编号
	var s0 = vertexLabel ? vertexLabel : 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';

	//标记边的长度
	var edgeLong = 0;
	var measure = 0;
	//为每个点利用余弦定理求角
	for (var i = 0; i < len; i++) {
		if (i == 0) {
			x1 = a[len-1][0];
			y1 = a[len-1][1];
			x3 = a[i+1][0];
			y3 = a[i+1][1];
		}
		else if (i == len-1) {
			x1 = a[i-1][0];
			y1 = a[i-1][1];
			x3 = a[0][0];
			y3 = a[0][1];
		}
		else {
			x1 = a[i-1][0];
			y1 = a[i-1][1];
			x3 = a[i+1][0];
			y3 = a[i+1][1];
		}
		x2 = a[i][0];
		y2 = a[i][1];

		d1 = (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
		d2 = (x2-x3)*(x2-x3)+(y2-y3)*(y2-y3);
		d3 = (x1-x3)*(x1-x3)+(y1-y3)*(y1-y3);

		angle = Math.acos((d1+d2-d3)/(2*Math.sqrt(d1*d2)))/Math.PI*180;

		s = angle.toFixed(2)+'°';

		//document.write(s+'<p>');
		//标注角度和顶点编号
		plot.setFillStyle('purple');
		plot.fillText(s, x2, -y2-5, 100);
		plot.setFillStyle(style);
		plot.fillText(s0[i], x2, -y2+20, 20);

		edgeLong = (Math.sqrt(d1)/scale).toFixed(2);
		measure = plot.measureText(edgeLong);

		plot.setFillStyle('blue');
		plot.fillText(edgeLong, (x1+x2-measure)/2, -(y1+y2)/2+20, measure);

	}	

	//由于处理会改变原矩阵,看以下操作能否恢复原矩阵
	if (scale != 1 && scale > 0) {
		for (var i = 0; i < len; i++) {
			for (var j = 0; j < 2; j++) {
				a0[i][j]/=scale;
			}
		}
	}

}

	/**
	* @usage  以顶点递推方式绘制正多边形 #1
	* @author  mw
	* @date    2015年12月01日  星期二  09:42:33
	* @param  (x, y)图形中心坐标,r 外接圆半径 edge 边数
	* @return
	*
	*/

	this.nEdge = function(x, y, r, edge, angle0) {
		edge = edge ? edge : 5;
		angle0 = angle0 ? angle0 : 0;

		var retArray = new Array();

		var perAngle = Math.PI * 2 / edge;

		var a = r * Math.sin(perAngle / 2);
		var angle = -angle0;
		var xOffset = r * Math.sin(perAngle / 2 - angle0);
		var yOffset = r * Math.cos(perAngle / 2 - angle0);

		var x1 = x-xOffset;
		var y1 = y+yOffset;		

		for (var i=0; i < edge; i++) {
			retArray.push([x1, y1]);
			x1 = x1 + 2 * a * Math.cos(angle);
			y1 = y1 + 2 * a * Math.sin(angle);
			angle -= perAngle;

		}

		return retArray;

	}

	/**
	* @usage   空心星形   #2 #201 #202
	* @author  mw
	* @date    2015年12月01日  星期二  10:06:13
	* @param
	* @return
	*
	*/
	this.nStar = function(x, y, r, edge, angle0, arg1, arg0) {
		edge = edge ? edge : 5;
		angle0 = angle0 ? angle0 : Math.PI/2;

		var retArray=new Array();

		var perAngle = Math.PI * 2 / edge;

		var r0 = arg0 ? arg0 * r : r / (2 * (1 + Math.cos(perAngle)));
		var scale = arg1 ? arg1 : 0.5;
		var angle = 0.5 * perAngle - angle0 * scale / 0.5;
		var xOffset = x;
		var yOffset = y;

		for (var i =0; i< edge; i++) {
			retArray.push([r0 * Math.cos(angle) + xOffset,r0 * Math.sin(angle) + yOffset] );

			retArray.push([r * Math.cos(angle - scale * perAngle) + xOffset,
							r * Math.sin(angle - scale * perAngle) + yOffset]);

			angle -= perAngle;
		}	

		return retArray;

	}

/**
* @usage   平行线, 平行四边形, 梯形
* @author  mw
* @date    2016年01月24日  星期日  11:14:43
* @param
* @return
*
*/
/*
平行线 Parallel lines
平行四边形 Parallel quadrilateral
梯形 trapezoid
*/
	this.paraline = function(x, y, r, rot) {
		rot = rot ? -rot : 0;
		y = y ? -y : 0;
		plot.beginPath()
			.moveTo(x, y)
			.lineTo(x + r * Math.cos(rot), y + r*Math.sin(rot))
			.moveTo(x, y + r/ 10)
			.lineTo(x + r * Math.cos(rot), y+r/10 + r*Math.sin(rot))
			.closePath()
			.stroke();	

	};

	this.paraquad = function(x, y, rot, a, b, angle) {
		angle = angle ? Math.abs(angle) : 0;
		rot = rot ? rot : 0;
		//参数说明:
		//平行四边形的两条边a, b, 以及它们之间的夹角angle
		//这个平行四边形的起始点(x, y), 以及整个图形与x轴的夹角rot

		var retArray = new Array();
		retArray.push([x, -y]);
		retArray.push([x + a * Math.cos(rot), -(y + a * Math.sin(rot))]);
		retArray.push([x + a * Math.cos(rot)+ b * Math.cos(rot+angle),
					  -(y + a * Math.sin(rot)+ b * Math.sin(rot+angle))]);
		retArray.push([x + b * Math.cos(rot+angle), -(y + b * Math.sin(rot+angle))]);

		return retArray;
	}

	this.trapezoid = function(x, y, rot, a, b, angle) {
		angle = angle ? Math.abs(angle) : 0;
		rot = rot ? rot : 0;
		//参数说明:
		//等腰梯形的下底边a,腰b, 以及它们之间的夹角angle
		//假设下底 > 上底,那么上底 = (a - b * Math.cos(angle)*2)/2
		//这个平行四边形的起始点(x, y), 以及整个图形与x轴的夹角rot

		var c = (a - b * Math.cos(angle)*2)/2;

		var retArray = new Array();
		if (c < 0) {
			//说明给的条件不对
			//缺省画上底是下底一半的梯形

		}
		else {
			retArray.push([x, -y]);
			retArray.push([x + a * Math.cos(rot), -(y + a * Math.sin(rot))]);
			retArray.push([x + b * Math.cos(rot+angle)+2*c * Math.cos(rot),
						  -(y + b * Math.sin(rot+angle)+2*c*Math.sin(rot))]);

			retArray.push([x + b * Math.cos(rot+angle), -(y + b * Math.sin(rot+angle))]);
		}

		return retArray;
	}

	/**
	* @usage   绘制圆形
	* @author  mw
	* @date    2015年11月27日  星期五  12:11:38
	* @param
	* @return
	*
	*/
	this.strokeCircle = function(x, y, r) {
			plot.beginPath()
			.arc(x, y, r, 0, 2*Math.PI, true)
			.closePath()
			.stroke();
	}

	this.fillCircle = function(x, y, r) {
		plot.beginPath()
			.arc(x, y, r, 0, 2*Math.PI, true)
			.closePath()
			.fill();
	}

	//绘制椭圆
	this.strokeEllipse = function(x, y, a, b, rotate) {
		//关键是bezierCurveTo中两个控制点的设置
		//0.5和0.6是两个关键系数(在本函数中为试验而得)
		var ox = 0.5 * a,
		oy = 0.6 * b;
		var rot = rotate ? -rotate : 0;
		plot.save()
			.rotate(rot)
			.translate(x, y)
			.beginPath()
			//从椭圆纵轴下端开始逆时针方向绘制
			.moveTo(0, b)
			.bezierCurveTo(ox, b, a, oy, a, 0)
			.bezierCurveTo(a, -oy, ox, -b, 0, -b)
			.bezierCurveTo(-ox, -b, -a, -oy, -a, 0)
			.bezierCurveTo(-a, oy, -ox, b, 0, b)
			.closePath()
			.stroke()
			.restore(); 

	}
	//绘制椭圆
	this.fillEllipse = function(x, y, a, b, rotate) {
		//关键是bezierCurveTo中两个控制点的设置
		//0.5和0.6是两个关键系数(在本函数中为试验而得)
		var ox = 0.5 * a,
		oy = 0.6 * b;
		var rot = rotate ? -rotate : 0;
		plot.save()
			.rotate(rot)
			.translate(x, y)
			.beginPath()
			//从椭圆纵轴下端开始逆时针方向绘制
			.moveTo(0, b)
			.bezierCurveTo(ox, b, a, oy, a, 0)
			.bezierCurveTo(a, -oy, ox, -b, 0, -b)
			.bezierCurveTo(-ox, -b, -a, -oy, -a, 0)
			.bezierCurveTo(-a, oy, -ox, b, 0, b)
			.closePath()
			.fill()
			.restore(); 

	}

/**
* @usage   绘制正方体
* @author  mw
* @date    2016年02月01日  星期一  08:40:27
* @param
* @return
*
*/
this.drawCubic = function(x0, y0, z0, r, style, style2, style3) {
	plot.save();
	x0*=r;
	y0*=-r;
	z0*=r;
	z0 = z0 /2;
	x0 = x0 - z0*0.707;
	y0 = y0 + z0*0.707;
	z0 = 0;

	plot.translate(x0, y0);

	style = style ? style : 'black';
	style2 = style2 ? style2 : style;
	style3 = style3 ? style3 : style;
	//左下角[x0, y0,边长r
	shape.fillDraw(shape.nEdge(0, 0,0.707*r, 4, 0), style);
	//顶面
	shape.fillDraw(shape.paraquad(-0.5*r, 0.5*r, 0, r, r/2, Math.PI/4), style2);
	shape.strokeDraw(shape.paraquad(-0.5*r, 0.5*r, 0, r, r/2, Math.PI/4), 'white');
	//右侧面
	shape.fillDraw(shape.paraquad(0.5*r, -0.5*r, Math.PI/4, r/2, r, Math.PI/4), style3);
	shape.strokeDraw(shape.paraquad(0.5*r, -0.5*r, Math.PI/4, r/2, r, Math.PI/4), 'white');
	plot.restore();

}

this.point3D = function(x0, y0, z0) {
	//canvas中y轴坐标向下为正,与笛卡尔坐标系相反
	//所以此处先取反
	//
	z0 = z0 /2;
	x0 = x0 - z0*0.707;
	y0 = y0 + z0*0.707;

	return [x0, y0];
}

//左视投影,此时x坐标是无所谓的
this.pointLeft = function(x0, y0, z0) {
	return [z0, y0];
}
//右视投影
this.pointRight = function(x0, y0, z0) {
	return [-z0, y0];
}
//俯视投影
this.pointTop = function(x0, y0, z0) {
	return [x0, -z0];
}
//仰视投影
this.pointBottom = function(x0, y0, z0) {
	return [x0, z0];
}
//主视投影
this.pointFront = function(x0, y0, z0) {
	return [x0, y0];
}
//后视投影
this.pointBack = function(x0, y0, z0) {
	return [-x0, y0];
}

this.strokeCubic = function(x0, y0, z0, r, style) {
	plot.save();

	x0 *= r;
	y0 *= r;
	z0 *= r;

	r *= 0.5;
	var array = [[-r, -r], [-r, r], [r, r], [r, -r]];

	var top = [];
	var left = [];
	var front = [];
	var x, y, z;
	//存放临时点
	var p = [];

	for (var i = 0; i < 4; i++) {
		x = (x0+array[i][0]);
		y = y0+r;
		z = (z0+array[i][1]);
		p = this.point3D(x, y, z);
		top.push([p[0], -p[1]]);
	}

	for (var i = 0; i < 4; i++) {
		x = x0+r;
		y = (y0+array[i][0])+2*r;
		z = z0+array[i][1];
		p = this.point3D(x, y, z);
		left.push([p[0], -p[1]]);
	}

	for (var i = 0; i < 4; i++) {
		x = x0+array[i][0];
		y = (y0+array[i][1])+2*r;
		z = z0+r;
		p = this.point3D(x, y, z);
		front.push([p[0], -p[1]]);
	}

	var tmp = [].concat(top);
	shape.fillDraw(tmp, style);
	tmp=[].concat(top);
	shape.strokeDraw(tmp, '#cccccc');
	tmp = [].concat(left);
	shape.strokeDraw(left, 'black');
	tmp = [].concat(front);
	shape.strokeDraw(front, 'black');
	plot.restore();

}

/**
* @usage   把三维点阵列按照z, y, x优先级由小到大排列
* @author  mw
* @date    2016年02月23日  星期二  09:38:27
* @param   [[x1, y1, z1], [x2,y2, z2], ...]
* @return  排序后的[[x, y, z]...]
*
*/

this.xyzSort = function(array) {
	var arr = new Array();
	arr = array;
	arr.sort(function(a, b) {
		if (a[2] != b[2]) {
			return a[2] - b[2];
		}
		else {
			if (a[1] != b[1]) {
				return (a[1] - b[1]);
			}
			else {
				return a[0] - b[0];
			}
		}
	});

	//document.write(arr);
	return arr;
}

//把给定的坐标点阵列数组[x, y],...按照距离它们的中心点的角度进行排列
//是为了把无序排列的闭合曲线上的点进行有序排列,后续可再经过连线形成
//可填充的闭合曲线
this.angularSort = function(array) {
	var a = new Array();
	a = [].concat(array);

	var len = a.length, len1 = a[0].length;

	//不符合处理条件,不进行处理
	if (len <= 0 || len1 != 2) return array;

	//求中心点
	var xTotal = 0, yTotal = 0, xCenter = 0, yCenter = 0;

	for (var i = 0; i < len; i++) {
		xTotal += a[i][0];
		yTotal += a[i][1];
	}

	xCenter = xTotal/len;
	yCenter = yTotal/len;

	//求与中心点夹角并排序
	var b = new Array();
	var x, y, xdiff, ydiff;
	for (var i = 0; i < len; i++) {
		x = a[i][0];
		y = a[i][1];
		xdiff = x-xCenter;
		ydiff = y-yCenter;

		if (Math.abs(xdiff)<0.0001) {
			if (ydiff > 0) {
				b.push([x, y, Math.PI/2]);
			}
			else {
				b.push([x, y, Math.PI/2*3]);
			}
		}
		else if ( xdiff >= 0 && ydiff > 0) {//第一象限
			b.push([x, y, Math.atan(Math.abs(ydiff/xdiff))]);
		}
		else if (xdiff < 0 && ydiff >= 0) {//第二象限
			b.push([x, y, Math.PI-Math.atan(Math.abs(ydiff/xdiff))]);
		}
		else if (xdiff <= 0 && ydiff < 0) {//第三象限
			b.push([x, y, Math.PI+Math.atan(Math.abs(ydiff/xdiff))]);
		}
		else {//第四象限
			b.push([x, y, Math.PI*2-Math.atan(Math.abs(ydiff/xdiff))]);
		}
	}

	b.sort(function(b1, b2) {
		if (Math.abs(b1[2]-b2[2]) < 0.0001) {
			//按照与中心点的距离大小排序
			var d1 = (b1[0]-xCenter)*(b1[0]-xCenter)+
					 (b1[1]-yCenter)*(b1[1]-yCenter);
			var d2 = (b2[0]-xCenter)*(b2[0]-xCenter)+
					 (b2[1]-yCenter)*(b2[1]-yCenter);

			return -(d1-d2);
		}
		else {
			return (b1[2]-b2[2]);
		}
	});

	var retArray = new Array();
	for (var i = 0; i < len; i++) {
		//如果两个点在经过中心点的同一直线上,舍弃这个点
		//因为它表示点阵列可能不是单一环,或不是闭合曲线
		if (i > 0 && Math.abs(b[i][2]-b[i-1][2]) < 0.0001) continue;
		retArray.push([b[i][0], b[i][1]]);
	}

	return retArray;

}

/**
* @usage   三视图
* @author  mw
* @date    2016年02月23日  星期二  09:49:23
* @param
* @return
*
*/
this.threeView = function(array, style) {
	var cubic = this.xyzSort(array);

	plot.save();

	plot.setTransform(1, 0, 0, 1, 0, 0)
		.translate(300, 200);

    //三维图和三视图
    var r = 50;
    style = style ? style : 'red';
	var len = cubic.length;

    for (var i = 0; i < len; i++) {
         this.drawCubic(cubic[i][0], cubic[i][1], cubic[i][2], r, style);
    }            

    var height = 400;
    r = r/3;      

    plot.setTransform(1, 0, 0, 1, 0, 0);
    plot.fillText('左视图', 20, 20, 100);
    plot.fillText('主视图', 20, 20+1*height/3, 100);
    plot.fillText('俯视图', 20, 20+2*height/3, 100);      

    plot.setFillStyle(style)
        .setStrokeStyle('white');      

    //左视图
    plot.translate(100, 80);
    for (var i = 0; i < len; i++) {
        //y, z两坐标,z坐标变为x坐标
        this.fillRect(cubic[i][2]*r, -cubic[i][1]*r, r, r);
        this.strokeRect(cubic[i][2]*r, -cubic[i][1]*r, r, r);
    }      

    //主视图
    plot.translate(0, 130);
    for (var i = 0; i < len; i++) {
        //x, y两坐标
        this.fillRect(cubic[i][0]*r, -cubic[i][1]*r, r, r);
        this.strokeRect(cubic[i][0]*r, -cubic[i][1]*r, r, r);
    }      

    //俯视图
    plot.translate(0, 100);
    for (var i = 0; i < len; i++) {
        //x, z两坐标,z坐标变为y坐标
        this.fillRect(cubic[i][0]*r, cubic[i][2]*r, r, r);
        this.strokeRect(cubic[i][0]*r, cubic[i][2]*r, r, r);
    }  

	plot.restore();
}

//绘制球体
this.sphere = function(pos/*[x, y, z]*/, r, style) {
	plot.save();

	var x, y;
	var p = [].concat(pos);
	if (p.length == 2) {
		x = p[0];
		y = p[1];
	}
	else if (p.length == 3) {
		var p1 = shape.point3D(p[0], -p[1], p[2]);
		x = p1[0];
		y = p1[1];
	}
	var r0 = 0.1*r;

	var grd = plot.createRadialGradient(x, y, r, x+0.3*r, y-0.3*r, r0);
    grd.addColorStop(0, style);
    grd.addColorStop(1, 'white');  

    plot.setFillStyle(grd);  

    shape.fillCircle(x, y, r);
	plot.restore();

}

	return {
		fillRect:fillRect,
		strokeRect:strokeRect,
		fillCircle:fillCircle,
		strokeCircle:strokeCircle,
		strokeEllipse:strokeEllipse,
		fillEllipse:fillEllipse,

		//绘制点阵列
		pointDraw:pointDraw,
		multiLineDraw:multiLineDraw,
		strokeDraw:strokeDraw,
		fillDraw:fillDraw,
		//多边形角度标注
		angleDraw:angleDraw,

		nEdge:nEdge,
		nStar:nStar,
		paraline:paraline,
		paraquad:paraquad,
		trapezoid:trapezoid,

		//绘制立方体
		drawCubic:drawCubic,
		strokeCubic:strokeCubic,
		//绘制球体
		sphere:sphere,
		//三维点映射
		point3D:point3D,
		pointLeft:pointLeft,
		pointRight:pointRight,
		pointTop:pointTop,
		pointBottom:pointBottom,
		pointFront:pointFront,
		pointBack:pointBack,
		//三视图
		threeView:threeView,
		//顶点排序
		xyzSort:xyzSort,
		angularSort:angularSort

	};
}();
/**
* @usage   对点阵列数组进行平移,旋转,缩放,对称等变形
* @author  mw
* @date    2016年03月20日  星期日  13:24:58
* @param   传入点阵列矩阵
* @return  输出变形后的点阵列矩阵
*
*/
function Transform() {
	this.translate = function(array, xOffset, yOffset) {
		var len = array.length;

		if (len == 0) {
			return [];
		}
		else {
			var len1 = array[0].length;

			if (len1 != 2) {
				//如果不是点阵列[..., [x,y], [x1,y1], ...]的格式,暂时不加处理
				return array;
			}
			else {
				var retArray = new Array();
				var x = 0, y = 0;

				for (var i = 0; i < len; i++) {
					x = array[i][0] + xOffset;
					y = array[i][1] + yOffset;

					retArray.push([x, y]);
				}
			}
		}

		return retArray;
	}

	this.scale = function(array, xScale, yScale) {
		var len = array.length;		

		if (len == 0) {
			return [];
		}
		else {
			xScale = xScale ? xScale : 1;
			yScale = yScale ? yScale : xScale;

			var len1 = array[0].length;

			if (len1 != 2) {
				//如果不是点阵列[..., [x,y], [x1,y1], ...]的格式,暂时不加处理
				return array;
			}
			else {
				var retArray = new Array();
				var x = 0, y = 0;

				for (var i = 0; i < len; i++) {
					x = array[i][0] * xScale;
					y = array[i][1] * yScale;

					retArray.push([x, y]);
				}
			}
		}

		return retArray;
	}

	this.rotate = function(array, angle) {
		var len = array.length;

		if (len == 0) {
			return [];
		}
		else {
			var len1 = array[0].length;

			if (len1 != 2) {
				//如果不是点阵列[..., [x,y], [x1,y1], ...]的格式,暂时不加处理
				return array;
			}
			else {
				var retArray = new Array();
				var x = 0, y = 0;
				var sinA, cosA;

				for (var i = 0; i < len; i++) {
					sinA = Math.sin(angle);
					cosA = Math.cos(angle);

					x = array[i][0] * cosA - array[i][1]*sinA;
					y = array[i][0] * sinA + array[i][1]*cosA;

					retArray.push([x, y]);
				}
			}
		}

		return retArray;
	}

	this.flipX = function(array) {
		return this.flipImplement(array, 'X');
	}

	this.flipY = function(array) {
		return this.flipImplement(array, 'Y');
	}

	this.flipXY = function(array) {
		return this.flipImplement(array, 'XY');
	}

	//关于直线y=kx轴对称
	this.flip = function(array, slope) {
		//slope为斜率k
		var mode = slope.toFixed(3);

		return this.flipImplement(array, mode);
	}

	this.flipImplement = function(array, mode) {
		var len = array.length;

		if (len == 0) {
			return [];
		}
		else {
			var len1 = array[0].length;

			if (len1 != 2) {
				//如果不是点阵列[..., [x,y], [x1,y1], ...]的格式,暂时不加处理
				return array;
			}
			else {
				var retArray = new Array();
				var x = 0, y = 0;
				var sinA, cosA;
				var m = 0, n = 0;

				if (mode == 'X')  {
					for (var i = 0; i < len; i++) {
						//关于X轴对称,
						x = array[i][0];
						y = -array[i][1];
						retArray.push([x, y]);
					}
				}
				else if (mode == 'Y') {
					for (var i = 0; i < len; i++) {
						//关于Y轴对称,
						x = -array[i][0];
						y = array[i][1];
						retArray.push([x, y]);
					}
				}
				else if (mode == 'XY') {
					for (var i = 0; i < len; i++) {
						//中心对称
						x = -array[i][0];
						y = -array[i][1];
						retArray.push([x, y]);
					}

				}
				else {
					//模式为斜率 y = kx中k的字符串
					k = parseFloat(mode);

					for (var i = 0; i < len; i++) {
						//可扩展
						//此处先放大100倍再缩小是因为对于小尺寸
						//计算误差太大,而如果尺寸太大,
						//标注会占用太多地方,造成文字拥挤,无法读取
						m = array[i][0]*10000;
						n = array[i][1]*10000;  						

						//x = (m-2*k+2*k*n-m*k*k)/(1+k*k);  

						x = (1-k*k)*m+2*k*(n-1)/(1+k*k);
						//y = (-n+2*k*m+n*k*k)/(1+k*k);
						y = (2*k*m-(1-k*k)*n)/(1+k*k);
						retArray.push([x/10000, y/10000]);
					}
				}

			}
		}

		return retArray;
	}

}
function Triangle() {
	this.edges = [];
	this.angles = [];

	//已知三条边
	this.know3edges = function(edges) {
		this.edges = [];
		this.angles = [];

		this.edges = edges;
		//角度为弧度单位
		//a边对应角
		this.angles.push(Math.acos((edges[1]*edges[1] + edges[2]*edges[2]-edges[0]*edges[0])/(2*edges[1]*edges[2])));

		//b边对应角
		this.angles.push(Math.acos((edges[0]*edges[0] + edges[2]*edges[2]-edges[1]*edges[1])/(2*edges[0]*edges[2])));

		//c边对应角
		this.angles.push(Math.acos((edges[0]*edges[0] + edges[1]*edges[1]-edges[2]*edges[2])/(2*edges[0]*edges[1])));	

		var x0 = 0, y0 = 0;
		var x1 = x0 + this.edges[0], y1 = y0;
		var x2 = x0 + this.edges[1] * Math.cos(-this.angles[2]),
			y2 = y0 + this.edges[1] * Math.sin(-this.angles[2]);

		var retArray = new Array();
		retArray.push([x0, y0]);
		retArray.push([x1, y1]);
		retArray.push([x2, y2]);

		return retArray;

	}

	//已知两个角
	this.know2angles = function(angles, r) {
		this.edges = [];
		this.angles = [];

		this.angles = [angles[0]/180*Math.PI, angles[1]/180*Math.PI,
					  (180-(angles[0]+angles[1]))/180*Math.PI];

		//设其中一边长度为10
		r = r > 0 ? r : 10;
		//A边
		this.edges.push(r);

		var angleA = this.angles[0];
			angleB = this.angles[1];
			angleC = this.angles[2];

		//B边
		this.edges.push(Math.sin(angleB)/Math.sin(angleA)*r);
		//C边
		this.edges.push(Math.sin(angleC)/Math.sin(angleA)*r);

		var x0 = 0, y0 = 0;
		var x1 = x0 + this.edges[0], y1 = y0;
		var x2 = x0 + this.edges[1] * Math.cos(-this.angles[2]),
			y2 = y0 + this.edges[1] * Math.sin(-this.angles[2]);

		var retArray = new Array();
		retArray.push([x0, y0]);
		retArray.push([x1, y1]);
		retArray.push([x2, y2]);

		return retArray;
	}

	//已知2条边和夹角
	this.know2edges = function(edges, angle) {
		this.edges = [];
		this.angles = [];

		//如果没有指定两边的夹角,默认为90度
		angle = angle ? angle : 90;

		var edgeC = 0;

		if (angle == -90) {
			//设定当输入角度为-90时指已知斜边和一条直角边的直角三角形
			var swap;
			if (edges[0] < edges[1]) {
				swap = edges[0];
				edges[0] = edges[1];
				edges[1] = swap;
			}
			edgeC = Math.sqrt(edges[0]*edges[0]-edges[1]*edges[1]);
			angle = Math.asin(edgeC/edges[0]);
		}
		else {
			angle = angle ? angle/180*Math.PI : Math.PI/2;
			edgeC = Math.sqrt(edges[0]*edges[0]+edges[1]*edges[1]-2*edges[0]*edges[1]*Math.cos(angle));
		}

		this.edges = [edges[0], edges[1], edgeC];

		var edgeA = this.edges[0],
			edgeB = this.edges[1];

		//角度为弧度单位
		//a边对应角
		this.angles.push(Math.acos((edgeB*edgeB + edgeC*edgeC-edgeA*edgeA)/(2*edgeB*edgeC)));

		//b边对应角
		this.angles.push(Math.acos((edgeA*edgeA + edgeC*edgeC-edgeB*edgeB)/(2*edgeA*edgeC)));

		//c边对应角
		this.angles.push(angle);	

		var x0 = 0, y0 = 0;
		var x1 = x0 + this.edges[0], y1 = y0;
		var x2 = x0 + this.edges[1] * Math.cos(-this.angles[2]),
			y2 = y0 + this.edges[1] * Math.sin(-this.angles[2]);

		var retArray = new Array();
		retArray.push([x0, y0]);
		retArray.push([x1, y1]);
		retArray.push([x2, y2]);

		return retArray;

	}

	//返回角度和边信息的字符串
	this.info = function() {
		var angleLabel = ['C', 'A', 'B'];
		var edgeLabel = ['ab', 'bc', 'ac'];
		var s = '';

		for (var i = 0; i < 3; i++) {
			s += edgeLabel[i]+' : ';
			s += this.edges[i].toFixed(2)+' ; ';
		}

		for (var i = 0; i < 3; i++) {
			s += angleLabel[i]+' : ';
			s += (this.angles[i]*180/Math.PI).toFixed(2) + ' ; ';
		}

		return s;
	}

}

当然只是用了其中的一小部分, 为了防止遗漏,在此全部贴出。

本节到此结束,欲知后事如何,请看下回分解。

时间: 2024-08-05 08:05:52

[从头学数学] 第152节 旋转的相关文章

[从头学数学] 第153节 旋转 小结与复习题

剧情提要: [机器小伟]在[工程师阿伟]的陪同下进入了筑基后期的修炼, 这次要修炼的目标是[旋转 小结与复习题]. 正剧开始: 星历2016年03月26日 16:27:19, 银河系厄尔斯星球中华帝国江南行省. [工程师阿伟]正在和[机器小伟]一起研究[旋转 小结与复习题]. <span style="font-size:18px;"> //第1题 三角形顺时针旋转30度 var r = 20; var r0 = 5*r; config.setSector(1,1,1,1)

[从头学数学] 第174节 算法初步

剧情提要: [机器小伟]在[工程师阿伟]的陪同下进入了结丹中期的修炼, 这次要修炼的目标是[算法初步]. 正剧开始: 星历2016年04月12日 08:54:58, 银河系厄尔斯星球中华帝国江南行省. [工程师阿伟]正在和[机器小伟]一起研究[算法初步]. [人叫板老师]指点小伟说:"这金丹要想大成,顺利进入元婴期,就必须进行九转培炼. 这什么是九转培炼法门呢?就是要先快速的修炼[天地人正册]进入后期,不要管各种辅修 功法,然后从头游历[天地人列国],在游历中增长见闻,精炼神通,最后再修炼[术.

[从头学数学] 第223节 带着计算机去高考(十五)

剧情提要: [机器小伟]在[工程师阿伟]的陪同下进入了[九转金丹]之第八转的修炼.设想一个场景: 如果允许你带一台不连网的计算机去参加高考,你会放弃选择一个手拿计算器和草稿本吗 ?阿伟决定和小伟来尝试一下用计算机算高考题会是怎样的感觉. 正剧开始: 星历2016年05月26日 10:23:46, 银河系厄尔斯星球中华帝国江南行省. [工程师阿伟]正在和[机器小伟]一起做着2014年的江苏省数学高考题]. 这一年的题和上一年一样的难,阿伟决定再交一次白卷. 好,卷子贴完,下面进入这次的主题. 这是

[从头学数学] 第172节 直线与方程

剧情提要: [机器小伟]在[project师阿伟]的陪同下进入了结丹初期的修炼. 这次要修炼的目标是[直线与方程]. 正剧開始: 星历2016年04月11日 09:30:00, 银河系厄尔斯星球中华帝国江南行省. [project师阿伟]正在和[机器小伟]一起研究[直线与方程]. 開始今天的修炼之前,小伟先整理了一下这件法器: <span style="font-size:18px;"> if (1) { var r = 20; config.setSector(1,1,1

[从头学数学] 第215节 带着计算机去高考(七)

剧情提要: [机器小伟]在[工程师阿伟]的陪同下进入了[九转金丹]之第八转的修炼.设想一个场景: 如果允许你带一台不连网的计算机去参加高考,你会放弃选择一个手拿计算器和草稿本吗 ?阿伟决定和小伟来尝试一下用计算机算高考题会是怎样的感觉. 正剧开始: 星历2016年05月20日 17:13:35, 银河系厄尔斯星球中华帝国江南行省. [工程师阿伟]正在和[机器小伟]一起做着2006年的江苏省数学高考题]. 这一年,江苏重新使用了全国卷,并且这张试卷的难度也比较高,可以说, 也是打了考生一个措手不及

[从头学数学] 第192节 导数及其应用

剧情提要: [机器小伟]在[工程师阿伟]的陪同下进入了[九转金丹]之第五转的修炼. 这次要研究的是[导数及其应用]. 正剧开始: 星历2016年04月23日 16:32:36, 银河系厄尔斯星球中华帝国江南行省. [工程师阿伟]正在和[机器小伟]一起研究[导数及其应用]. <span style="font-size:18px;">>>> [-3.000001001396413, -2.999998999442255] [4.999998999721811

[从头学数学] 第214节 带着计算机去高考(六)

剧情提要: [机器小伟]在[工程师阿伟]的陪同下进入了[九转金丹]之第八转的修炼.设想一个场景: 如果允许你带一台不连网的计算机去参加高考,你会放弃选择一个手拿计算器和草稿本吗 ?阿伟决定和小伟来尝试一下用计算机算高考题会是怎样的感觉. 正剧开始: 星历2016年05月20日 11:40:58, 银河系厄尔斯星球中华帝国江南行省. [工程师阿伟]正在和[机器小伟]一起做着2005年的江苏省数学高考题]. 总体来说,这次的难度和上一年持平,都是很厚道的那种, 不过上一年的好多题都像闹着玩似的,这次

[从头学数学] 第220节 带着计算机去高考(十二)

剧情提要: [机器小伟]在[工程师阿伟]的陪同下进入了[九转金丹]之第八转的修炼.设想一个场景: 如果允许你带一台不连网的计算机去参加高考,你会放弃选择一个手拿计算器和草稿本吗 ?阿伟决定和小伟来尝试一下用计算机算高考题会是怎样的感觉. 正剧开始: 星历2016年05月24日 17:11:11, 银河系厄尔斯星球中华帝国江南行省. [工程师阿伟]正在和[机器小伟]一起做着2011年的江苏省数学高考题]. 2011年的卷子,难度比上一年的稍小一点,但阿伟觉得也达到了5.5环的难度. 这次的特色,是

[从头学数学] 第179节 三角初等变换

剧情提要: [机器小伟]在[工程师阿伟]的陪同下进入了结丹中期的修炼, 这次要修炼的目标是[三角初等变换]. 正剧开始: 星历2016年04月15日 15:32:35, 银河系厄尔斯星球中华帝国江南行省. [工程师阿伟]正在和[机器小伟]一起研究[三角初等变换]. <span style="font-size:18px;"> if (1) { var mathText = new MathText(); var s = [ '和角.差角公式', 'sin(A+B) = si