正剧开始:
星历2016年04月06日 09:19:46, 银河系厄尔斯星球中华帝国江南行省。
[工程师阿伟]正在和[机器小伟]一起研究[投影与视图]。
这是筑基期的最后一部分功法了,修炼完后,小伟就要进入结丹期了,
由于小伟是最强的天灵根属性,再加上[工程师阿伟]的指点,所以不存在任何的结丹瓶颈。
小伟试着画了一下正方体的投影:
<span style="font-size:18px;"> if (1) { var r = 20; var r0 = 2*r; var array = [[-r0, -r0], [-r0, r0], [r0, r0], [r0, -r0]]; var tmp = [], tmp2 = []; for (var i = 0; i < 4; i++) { tmp = array.shift(); array.push([tmp[0], tmp[1], -r0]); array.push([tmp[0], tmp[1], r0]); } //document.write(array); var points = array.length; //document.write(points); config.setSector(2,2,1,1); config.axis2D(0, 0, 90); var leftView = []; for (var i = 0; i < points; i++) { tmp = array[i]; tmp2 = shape.pointLeft(tmp[0], tmp[1], tmp[2]); leftView.push([tmp2[0], tmp2[1]]); } leftView = shape.angularSort(leftView); shape.angleDraw(leftView, 'red', 1); config.setSector(2,2,1,2); config.axis2D(0, 0, 90); var frontView = []; for (var i = 0; i < points; i++) { tmp = array[i]; tmp2 = shape.pointFront(tmp[0], tmp[1], tmp[2]); frontView.push([tmp2[0], tmp2[1]]); } frontView = shape.angularSort(frontView); shape.angleDraw(frontView, 'red', 1); config.setSector(2,2, 2, 1); config.axis2D(0, 0, 90); var topView = []; for (var i = 0; i < points; i++) { tmp = array[i]; tmp2 = shape.pointTop(tmp[0], tmp[1], tmp[2]); topView.push([tmp2[0], tmp2[1]]); } topView = shape.angularSort(topView); shape.angleDraw(topView, 'blue', 1); config.setSector(2,2,2,2); config.axis3D(0, 0, 0, 90); shape.strokeCubic(0, -1, 0, 2*r0, 'blue'); }</span>
像飞机这么高大上的东西的三视图,小伟现在还是没办法画出来的,不过,现在可以从简单的三角形开始。
<span style="font-size:18px;"> if (1) { var r = 20; var array = [[-2, -3, -1], [1, 3, 3], [1,-3, 2]]; var tmp = [], tmp2 = []; //document.write(array); var points = array.length; //document.write(points); config.setSector(2,2,1,1); config.axis2D(0, 0, 90); var leftView = []; for (var i = 0; i < points; i++) { tmp = array[i]; tmp2 = shape.pointLeft(tmp[0], tmp[1], tmp[2]); leftView.push([tmp2[0], tmp2[1]]); } leftView = shape.angularSort(leftView); shape.angleDraw(leftView, 'red', r); plot.fillText('左视图', -100, -50, 100); config.setSector(2,2,1,2); config.axis2D(0, 0, 90); var frontView = []; for (var i = 0; i < points; i++) { tmp = array[i]; tmp2 = shape.pointFront(tmp[0], tmp[1], tmp[2]); frontView.push([tmp2[0], tmp2[1]]); } frontView = shape.angularSort(frontView); shape.angleDraw(frontView, 'red', r); plot.fillText('主视图', -100, -50, 100); config.setSector(2,2, 2, 1); config.axis2D(0, 0, 90); var topView = []; for (var i = 0; i < points; i++) { tmp = array[i]; tmp2 = shape.pointTop(tmp[0], tmp[1], tmp[2]); topView.push([tmp2[0], tmp2[1]]); } topView = shape.angularSort(topView); shape.angleDraw(topView, 'red', r); plot.fillText('俯视图', -100, -50, 100); config.setSector(2,2,2,2); config.axis3D(0, 0, 0, 90); var _3DView = []; for (var i = 0; i < points; i++) { tmp = array[i]; tmp2 = shape.point3D(tmp[0], tmp[1], tmp[2]); _3DView.push([tmp2[0], tmp2[1]]); } _3DView = shape.angularSort(_3DView); shape.angleDraw(_3DView, 'blue', r); plot.fillText('三维图', -100, -50, 100); }</span>
这个三角形画出来后,小伟有点困惑,画得对不对呢?
于是做了很多的验证,确认是没错的。
但这个三角形到底是多大呢,这么多的视图看了着实头晕。于是:
<span style="font-size:18px;"> if (1) { var r = 20; config.setSector(1,1,1,1); config.graphPaper2D(0, 0, r); config.axis2D(0, 0, 190); //B - A - C三点,这个顺序会因为点之间距离大小的排序而不同的 var array = [[-2, -3, -1], [1, 3, 3], [1,-3, 2]]; //这个函数输入的是坐标点的阵列,可以是二维或三维的坐标点 //暂时解决的问题是求其中任意两个不同点之间的距离 //返回[[点1, 点2, 点1,2的距离],...]这样一个阵列 var result = problemSolve(array); //对于三角形来说,应该是刚好是有三条边 var edges = result.length; var edge = []; for (var i = 0; i < edges; i++) { edge.push(result[i][2]); } var triangle = new Triangle(); var transform = new Transform(); result = triangle.know3edges(edge); shape.angleDraw(transform.flipX([].concat(result)), 'red', r, 'ABC'); plot.fillText('平行视图', -100, -50, 100); }</span>
再对照一下:
再画一个试试,这次画个简单点的:
<span style="font-size:18px;">var array = [[0, 0, 4], [0, 3, 0], [5, 0, 0]];</span>
虽然小伟的工具可以画出这样的结果,但是顶点的标注还是要注意调整的。
这三维的东西想着就头晕。这个三维图其实是视觉上的效果。真正的大小是在这个平行视图里面。
如果要想知道一个图形的确切大小,肯定是要平行这个图形去看去度量的。
不过阿伟对这个'B'点没有落在Z轴上感到有点不可思议,是不是映射算法有问题呢?
这个题小伟也想解一解:
<span style="font-size:18px;">//画出三视图 if (1) { var r = 20; var array = []; var tmp = [], tmp2 = []; //圆柱 tmp = shape.nEdge(0, 0, 1, 36); var len = tmp.length; for (var i = 0; i < len; i++) { array.push([tmp[i][0], 1,tmp[i][1]]); array.push([tmp[i][0], -1,tmp[i][1]]); } //document.write(array); var points = array.length; //document.write(points); config.setSector(2,2,1,1); config.axis2D(0, 0, 90); var leftView = []; for (var i = 0; i < points; i++) { tmp = array[i]; tmp2 = shape.pointLeft(tmp[0], tmp[1], tmp[2]); leftView.push([tmp2[0], tmp2[1]]); } leftView = shape.angularSort(leftView); shape.strokeDraw(leftView, 'red', r); plot.fillText('左视图', -100, -50, 100); config.setSector(2,2,1,2); config.axis2D(0, 0, 90); var frontView = []; for (var i = 0; i < points; i++) { tmp = array[i]; tmp2 = shape.pointFront(tmp[0], tmp[1], tmp[2]); frontView.push([tmp2[0], tmp2[1]]); } frontView = shape.angularSort(frontView); shape.strokeDraw(frontView, 'red', r); plot.fillText('主视图', -100, -50, 100); config.setSector(2,2, 2, 1); config.axis2D(0, 0, 90); var topView = []; for (var i = 0; i < points; i++) { tmp = array[i]; tmp2 = shape.pointTop(tmp[0], tmp[1], tmp[2]); topView.push([tmp2[0], tmp2[1]]); } topView = shape.angularSort(topView); shape.strokeDraw(topView, 'red', r); plot.fillText('俯视图', -100, -50, 100); config.setSector(2,2,2,2); config.axis3D(0, 0, 0, 90); var _3DView = []; for (var i = 0; i < points; i++) { tmp = array[i]; tmp2 = shape.point3D(tmp[0], tmp[1], tmp[2]); _3DView.push([tmp2[0], tmp2[1]]); } _3DView = shape.angularSort(_3DView); shape.strokeDraw(_3DView, 'blue', r); plot.fillText('三维图', -100, -50, 100); }</span>
<span style="font-size:18px;"> //正三棱柱 tmp = shape.nEdge(0, 0, 1, 3); var len = tmp.length; for (var i = 0; i < len; i++) { array.push([tmp[i][0], 1,tmp[i][1]]); array.push([tmp[i][0], -1,tmp[i][1]]); }</span>
<span style="font-size:18px;"> //球 for (var i = -1; i <=1; i+=0.1) { for (var j = -1; j <=1; j+=0.1) { for (var k = -1; k <= 1; k+=0.1) { if (i*i + j*j + k*k <= 1) { array.push([i, j, k]); } } } }</span>
严格说来,这并不能算是成功的画出三视图,因为放大后看就是这样的:
所谓有心栽花花不活,无心栽花花正艳。
<span style="font-size:18px;">//一朵花 //球 for (var i = -1; i <=1; i+=0.2) { for (var j = -1; j <=1; j+=0.2) { for (var k = -1; k <= 1; k+=0.2) { if (i*i + j*j + k*k <= 1) { array.push([i*2, j*2, k*2]); } } } }</span>
<span style="font-size:18px;">//立方体的三视图 if (1) { var r = 20; config.setSector(1,1,1,1); config.graphPaper2D(0, 0, r); config.axis3D(0, 0, 0, 190); var array = [ [0,0,0],[1,0,0],[2,0,0],[3,0,0], [1,1,0],[2,1,0], [1.5,2,0] ]; array = shape.xyzSort(array); shape.threeView(array); }</span>
<span style="font-size:18px;"> if (1) { var r = 20; config.setSector(1,1,1,1); config.graphPaper2D(0, 0, r); config.axis3D(0, 0, 0, 190); var array = [ [0,0,0],[1,0,0],[0,0,1],[1,0,1],[0,0,2], [0,1,0], [0,2,0] ]; array = shape.xyzSort(array); shape.threeView(array); }</span>
好了,下面贴一下小伟用到的工具吧,作为筑基期的一个总结。
<span style="font-size:18px;">//解决某个特定问题 function problemSolve(pointArray) { //传入点阵列pointArray //格式为[[px1, py1], [px2, py2], ...] //document.write(pointArray.join(' , ')+'<br/>'); //对于pointArray中的每个点,求它与所有其它点的距离 //结果放入distanceArray //格式为[[点1序号,点2序号, 距离值]] var distanceArray = []; //点的数量 var size = pointArray.length; //临时变量 var distance = x1 = y1 = z1 = x2 = y2 = z2 = 0; var dimension = pointArray[0].length; //计算并压入距离 for (var i = 0; i < size; i++) { for (var j = i+1; j < size; j++) { x1 = pointArray[i][0]; y1 = pointArray[i][1]; x2 = pointArray[j][0]; y2 = pointArray[j][1]; if (dimension > 2) { //三维点的处理 z1 = pointArray[i][2]; z2 = pointArray[j][2]; distance = Math.sqrt(Math.pow(x1-x2, 2)+Math.pow(y1-y2, 2)+Math.pow(z1-z2, 2)); } else { distance = Math.sqrt(Math.pow(x1-x2, 2)+Math.pow(y1-y2, 2)); } //注意这里已经保证i < j //所以起始点序号必须要小于终点序号 //这是为了连接起始点和终点的直线不会重复 distanceArray.push([i, j, distance]); } } //对距离阵列排序 //排序权重:起始点序号 > 距离 > 终点序号 distanceArray.sort(function(a, b) { if (a[0] == b[0]) { if (Math.abs(a[2] - b[2]) < 0.000001) { return a[1]-b[1]; } else { return a[2]-b[2]; } } else { return a[0] - b[0]; } }); //document.write(distanceArray.join(' , ')+'<br/>'); return distanceArray; } //去除重复点 function removeDuplicatedPoint(pointArray) { var array = new Array(); var size = pointArray.length; array.push(pointArray[0]); var len = 0; for (var i = 0; i < size; i++) { len = array.length; for (var j = 0; j < len; j++) { if (pointArray[i][0] == array[j][0] && pointArray[i][1] == array[j][1]) { break; } if (j >= len-1) { array.push(pointArray[i]); } } } return array; }</span>
<span style="font-size:18px;">/** * @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, showLable, lable) { //已经考虑到y轴坐标的取反问题,只需传入原始坐标数组即可 style = style ? style : 'black'; scale = scale ? scale : 1; lable = lable ? lable : 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; lables = lable.length; showLable = showLable ? showLable : 0; 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); if (showLable) { plot.fillText(lable[index++%lables], x+5, y+10, 30); } } } 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); if (showLable) { plot.fillText(lable[index++%lables], x+5, y+10, 30); } } } 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(); var len = array.length; var len_1 = array[0].length; for (var i = 0; i < len; i++) { a0.push(array[i]); } scale = scale ? scale : 1; 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); } } /** * @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; var transform = new Transform(); //左下角[x0, y0,边长r shape.fillDraw(shape.nEdge(0, 0,0.707*r, 4, 0), style); //顶面 shape.fillDraw(transform.flipX(shape.paraquad(-0.5*r, 0.5*r, 0, r, r/2, Math.PI/4)), style2); shape.strokeDraw(transform.flipX(shape.paraquad(-0.5*r, 0.5*r, 0, r, r/2, Math.PI/4)), 'white'); //右侧面 shape.fillDraw(transform.flipX(shape.paraquad(0.5*r, -0.5*r, Math.PI/4, r/2, r, Math.PI/4)), style3); shape.strokeDraw(transform.flipX(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 }; }(); </span>
<span style="font-size:18px;">/** * @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; } }</span>
<span style="font-size:18px;">/** * @usage 数学表达式,代数式的书写 * @author mw * @date 2016年03月12日 星期六 11:05:12 * @param * @return * */ function MathText() { //上标标记形式为...^[内容]... //分数不进行处理, 根式不进行处理,都转成指数式进行 //特殊数学符号设想加\[alpha]进行转义,待续 //可以进行指数上标代数式的书写 //可扩展下标,待续 this.setNormalFont = function() { plot.setFont("normal normal normal 24px Times Lt Std"); } this.setScriptFont = function() { plot.setFont("italic normal bold 16px Dark Courier "); } this.print = function(text, xPos, yPos) { xPos = xPos ? xPos : 0; yPos = yPos ? yPos : 0; plot.save(); var s = text ? text : ''; if (s != '') { s = s.replace(/\/\//ig, '÷'); s = s.replace(/>=/ig, '≥'); s = s.replace(/<=/ig, '≤'); s = s.replace(/!=/ig, '≠'); s = s.replace(/pi/ig, 'π'); } //字符串长度 var len = s.length; //不同字体大小设置在此 var r1 = 20; //单个字符暂存处 var c; //文本显示位置 var x = xPos, y = yPos; //正常文本暂存 var s0 = ''; //字符串打印长度 var measure; //记录上一个x位置,可记录三层 var xMem = [x, x, x]; //记录每一层的左括号位置 var bracketPos = [x, x, x]; //记录括号层次 var bracketsLevel = 0; //记录根号层次 var radicalLevel = 0; //记录每一层根号的起始位置和层次数的数组...[[start, end, level], ...] var radicalSpan = []; //设置正常字体 this.setNormalFont(); for (var i = 0; i < len; i++) { if (s[i] == '_') { //下标开始 //下标标记形式为..._[内容]... if (s0 != '') { //先把正常字符打印出 if (r1 != 20) { //字体字号大小还在上标状态 r1 = 20; this.setNormalFont(); } measure = plot.measureText(s0); plot.fillText(s0, x, y, measure); s0 = ''; x += measure; } var subScript = ''; var j = 0; for (j = i+1; s[j]!=']'; j++) { if (s[j] != '[') { subScript+=s[j]; } } if (r1 != 10) {//正常字体状态,需要改为上标字体 r1 = 10; this.setScriptFont(); } measure = plot.measureText(subScript); plot.fillText(subScript, x, y+8, measure); if (j < len-1 && s[j+1] == '^') { } else { x += 1.2*measure; } i = j; } else if (s[i] == '^') { //上标开始 //上标标记形式为...^[内容]... if (s0 != '') { //先把正常字符打印出 if (r1 != 20) { //字体字号大小还在上标状态 r1 = 20; this.setNormalFont(); } measure = plot.measureText(s0); plot.fillText(s0, x, y, measure); s0 = ''; x += measure; } var upperScript = ''; var j = 0; for (j = i+1; s[j]!=']'; j++) { if (s[j] != '[') { upperScript+=s[j]; } } //二次根式 if (upperScript == '1/2' || upperScript == '0.5') { var x1, y1; if (i > 0 && s[i-1] == ')') { x1 = bracketPos[bracketsLevel]; } else { x1 = xMem[bracketsLevel]; } /* 存疑代码 if (radicalSpan == []) { radicalLevel = 0; radicalSpan.push([x1, x, radicalLevel]); } else { var len = radicalSpan.length; for (var k = 0; k < len; k++) { if (x1 < radicalSpan[k][0]) { radicalLevel = radicalSpan[k][2]+1; break; } if (k >= len-1) { radicalLevel = 0; } } radicalSpan.push([x1, x, radicalLevel]); }*/ y1 = y-20-5*radicalLevel; plot.save() .setLineWidth(1); plot.beginPath() .moveTo(x1-5, y+5) .lineTo(x1-8, y-3) .moveTo(x1-5, y+5) .lineTo(x1+5, y1) .moveTo(x1+5, y1) .lineTo(x, y1) .closePath() .stroke(); plot.restore(); } else { if (r1 != 10) {//正常字体状态,需要改为上标字体 r1 = 10; this.setScriptFont(); } measure = plot.measureText(upperScript); plot.fillText(upperScript, x, y-8, measure); if (j < len-1 && s[j+1] == '_') { } else { x += 1.2*measure; } } //直接跳跃过上标字符区段 i = j; } else { c = s[i]; if (c == ')') { s0 += c; bracketsLevel -= 1; } else if (c == '(') { //如果整个括号被开根式,根号在括号左边 bracketPos[bracketsLevel] = x + plot.measureText(s0); s0 += c; bracketsLevel+=1; //过了括号就是过了一道关,要刷新坐标 xMem[bracketsLevel] = x + plot.measureText(s0); } else if (c == '+' || c == '-' || c == '*' || c == '/' || c == '÷' || c == '=' || c == ' ') { if (c == '*') { if (i > 0 && /[0-9]/.test(s[i-1]) && /[0-9]/.test(s[i+1])) { //对于乘号前后都是数字的情况,把乘号改成叉号 c = ' \u00D7 '; } else { //对于代数式中,乘号改为点号 c = ' \u00B7 '; } } //如果是运算符后的数被开根式,根号在运算符右边 if (c == '-' || c == '/') { s0 += ' '+c+' '; } else { s0 += c; } if (bracketsLevel < 3) { xMem[bracketsLevel] = x+plot.measureText(s0); } } else { s0 += c; } } } if (s0 != '') { //先把正常字符打印出 if (r1 != 20) { //字体字号大小还在上标状态 r1 = 20; this.setNormalFont(); } measure = plot.measureText(s0); plot.fillText(s0, x, y, measure); x += measure; } plot.restore(); } } //文本显示方便类 function DrawText() { this.protype = function(str, xPos, yPos, traits, rotate, style, fontSize, alignment) { //traits参数指定了显示正常/加粗/斜体等区别 //style 是颜色/渐变的区别 //fontSize是字体字号的区别 //alignment是对齐方式的区别 rotate = rotate ? rotate : 0; style = style ? style : 'black'; fontSize = fontSize >= 10 ? fontSize : 20; //左对齐0, 中对齐1, 右对齐2 alignment = alignment ? alignment : 'L'; var tmp = fontSize.toFixed(0)+'px'; var font = ''; if (traits == 'normal') { font = "normal normal normal "+tmp+" Times New Roman"; } else if (traits == 'bold') { font = 'normal normal 800 '+tmp+' Arial'; } else if (traits == 'italic') { font = 'italic normal bold '+tmp+' Microsoft Sans Serif'; } else { font = "normal normal normal "+tmp+" Times New Roman"; } plot.save() .setFont(font) .setFillStyle(style) .translate(xPos, yPos) .rotate(-rotate); var x = 0, y = 0; var measure = 0; var s = ''; //多行 var len = str.length; for (var i = 0; i < len; i++) { s = str[i]; measure = plot.measureText(s); if (alignment == 1 || alignment.toUpperCase() == 'M' || alignment.toUpperCase() == 'C'){ //[x,y]为居中对齐的中点 plot.fillText(s, x-measure/2, y, measure); } else if (alignment == 2 || alignment.toUpperCase() == 'R'){ //[x,y]为右对齐的右边边界点 plot.fillText(s, x-measure, y, measure); } else { //于[x,y]处左对齐 plot.fillText(s, x, y, measure); } y += fontSize*1.5; } plot.restore(); } this.normal = function(str, xPos, yPos, rotate, style, fontSize, alignment) { return this.protype(str, xPos, yPos, 'normal', rotate, style, fontSize, alignment); } this.bold = function(str, xPos, yPos, rotate, style, fontSize, alignment) { return this.protype(str, xPos, yPos, 'bold', rotate, style, fontSize, alignment); } this.italic = function(str, xPos, yPos, rotate, style, fontSize, alignment) { return this.protype(str, xPos, yPos, 'italic', rotate, style, fontSize, alignment); } } </span>
本节到此结束,欲知后事如何,请看下回分解。