第一部分软光栅 (1)绘制线段 1 思路
如上图所示,当画一条线时,首先计算增量dx,dy。如果dy<dx,则x方向增长快,每次必加1,而y看判别式。
取dx所在线(x轴平行线)和画的线所成角的角平分线,若新的y落在该平分线上面,则y递增,否则y不变。而角平分线方程为:$dy=\frac{1}{2}dx$,即平分斜率。即$dy-\frac{1}{2}dx>0 \Longrightarrow 2dy-dx>0$时,y递增。此时下一个判别式的增量为$2(dy-dx)$。否则增量为$2dy$。
为了将直线扩展到所有斜率使用,实现使用了动态增量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 function drawLine (p1, p2 ){ var x1 = p1[0 ], y1 = p1[1 ]; var x2 = p2[0 ], y2 = p2[1 ]; var x = x1; var y = y1; var dx = x2 - x1; var dy = y2 - y1; var ix = (dx > 0 ) ? 1 : -1 ; var iy = (dy > 0 ) ? 1 : -1 ; dx = Math .abs (dx); dy = Math .abs (dy); if (dx > dy) { var p = 2 * dy - dx; for (var i = 0 ; i <= dx; i++) { addPixel (x,y); x += ix; if (p >= 0 ) { y += iy; p += 2 * (dy - dx); } else { p += 2 * dy; } } } else { var p = 2 * dx - dy; for (var i = 0 ; i <= dy; i++) { addPixel (x,y); y += iy; if (p >= 0 ) { x += ix; p += 2 * (dx - dy); } else { p += 2 * dx; } } } }
2重点难点 (1)实现全斜率直线绘制
(2)DDA浮点数远算到Bresenham整数远算的优化过程思考
3心得 掌握了DDA的基本思想和由其升级得到的Bresenman算法。
(2)画圆 1 思路 根据圆心在原点的圆的方程$R^2=x^2+y^2$和圆的性质得:$F(x,y)=x^2+y^2-R^2$。
选取第二象限0-45度得圆弧作为绘制得基本圆弧,其他七个区域有基本圆弧对称得出。
在基本圆弧中,x得初始值为0,y得初始值为R。由于基本圆弧中,x的变化率大于y,令x每一步都递增1,而y由判别式判断是否递增。
考虑当前点$P(x,y)$,下一步递增有两个候选点$P_b(x+1,y-1)$,$P_u(x+1,y)$。选取候选点的方式是判断两点的中点$P_m(x+1,y-0.5)$是落在园内还是圆外。若落在圆内,说明$P_u$距离圆弧更接近,选择$P_u$,否则选择$P_b$。
而判断方式由函数$F(x,y)$判定。$F(x,y)<=0则在院圆内或圆上,F(x,y)>0则在圆外$。
若当前$F(x,y)<=0$,则当前的$F_{current}(x,y)=(x+1)^2+(y-0.5)^2-R^2$,下一个$F_{next}(x,y)=(x+1+1)^2+(y-0.5)^2-R^2$。所以得到判别式的差值$\Delta d=F_{next}(x,y)-F_{current}(x,y)=2x+3$。
若当前$F(x,y)>0$,则当前的$F_{current}(x,y)=(x+1)^2+(y-0.5)^2-R^2$,下一个$F_{next}(x,y)=(x+1+1)^2+(y-0.5-1)^2-R^2$。所以得到判别式的差值$\Delta d=F_{next}(x,y)-F_{current}(x,y)=2(x-y)+5$。
而判别式的初值$d_0=F(1,\frac{R+(R-1)}{2})=1.25-R$。
然后对判别式进行浮点数优化。由于判别式的差值为整数,所以$d_0=D_0-0.25=1-R$。而$0.25 \in (0,1)$,直接舍去。
这样就计算出了基本圆弧的每一个点。然后对基本圆弧的点以圆心$(cx,cy)$进行八个方向对称:$(cx \pm x,cy \pm y),(cx \pm y,cy \pm x)$。画出圆。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 let d = 1 -radius;let y = radius;let xMax = radius/Math .sqrt (2 );for (let x = 0 ;x<=xMax;++x){ addPixel (centerx+x,centery+y); addPixel (centerx+x,centery-y); addPixel (centerx-x,centery+y); addPixel (centerx-x,centery-y); addPixel (centerx+y,centery+x); addPixel (centerx+y,centery-x); addPixel (centerx-y,centery+x); addPixel (centerx-y,centery-x); if (d<=0 ){ d += 2 *x+3 ; } else { d += 2 *(x-y)+5 ; --y; } }
2重点难点 (1)浮点数优化。
(2)判别式去浮点数的推导。
3心得 (1)对浮点数优化成整数运算有了一些启蒙性的感悟。
(2)明白了最终代码都是算法精简后的结果,研究算法时要先实现完整过程在优化。否则不知道那一步出错或中间过程不具有可调试性。降低效率。
(3)实践理解了Bressenham中点画圆法及其优化。
(3)绘制多边形 1 思路 (1)建立边表ET
将每一个边加入边表,获取y的最大最小值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function initEdgeTable (points ){ let ET = {}; let yMin = 1000000000 ; let yMax = 0 ; for (var index = 0 ;index<points.length - 1 ;++index){ let result = addToET (points[index],points[index + 1 ],ET ); ET = result.ET ; yMin = Math .min (yMin,result.yMin ); yMax = Math .max (yMax,result.yMax ); } let result = addToET (points[0 ],points[points.length - 1 ],ET ); ET = result.ET ; yMin = Math .min (yMin,result.yMin ); yMax = Math .max (yMax,result.yMax ); return {ET : ET , yMin : yMin, yMax : yMax}; }
一条边加入边表的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function addToET (point1,point2,ET ){ if (point2[Loc .Y ] < point1[Loc .Y ]) [point1,point2] = [point2,point1]; let edge = { x : point1[Loc .X ], yMax : point2[Loc .Y ], ivK : (point2[Loc .X ]-point1[Loc .X ])/(point2[Loc .Y ] - point1[Loc .Y ]), }; if (ET [point1[Loc .Y ]] == undefined ){ ET [point1[Loc .Y ]] = []; } ET [point1[Loc .Y ]].push (edge); return {ET : ET , yMin : point1[Loc .Y ], yMax : point2[Loc .Y ]}; }
(2)开始迭代
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 let AET = []; for (let currentY = result.yMin ;currentY<=result.yMax ;++currentY){ for (let index = 0 ;index < AET .length ;++index){ if (currentY > AET [index].yMax ){ AET .splice (index,1 ); --index; } else { AET [index].x += AET [index].ivK ; } } let edges = ET [currentY]; if (edges != undefined ){ for (let index = 0 ;index < edges.length ;++index){ AET .push (edges[index]); } } AET .sort (function (left,right ){ if (left.x == right.x ) return left.ivK - right.ivK ; return left.x - right.x ; }); let isDraw = false ; for (let index = 0 ;index < AET .length - 1 ;++index){ isDraw = !isDraw; if (!isDraw)continue ; drawLine ( [AET [index].x ,currentY], [AET [index + 1 ].x ,currentY] ); } }
2重点难点 边表数据结构的建立
3心得 (1)实践了js的许多语法和debug手段,加强了js编码能力。
(2)学会了js实现链表,哈希表数据结构。
(3)实践理解了有序边表法。理解了先前有些疑惑的地方。
(4)绘制贝塞尔曲线 1 思路 通过三次贝塞尔多项式:
对0到1进行采样计算出多个点的函数值,然后连接起来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function drawCurve (p1, p2, p3, p4 ){ let lineP = [p1,]; for (let dt=0 ;dt<=1 ;dt+=0.001 ) { let yt=1 -dt; let c1=yt*yt; let c2=3 *yt*dt; let dt3 = dt*dt*dt; let xt=p1[0 ]*c1*yt+p2[0 ]*c2*yt+p3[0 ]*c2*dt+p4[0 ]*dt3; yt=p1[1 ]*yt*c1+p2[1 ]*c2*yt+p3[1 ]*c2*dt+p4[1 ]*dt3; lineP.push ([xt,yt]); } for (let index = 0 ;index < lineP.length - 1 ;++index){ drawLine (lineP[index],lineP[index+1 ]); } }
2重点难点 (1)贝塞尔曲线公式的展开和推到
(2)采样率的选择
3心得 (1)了解了不同采样率选取对绘图的影响,这是一个性能和效果的权衡。不过就本题的例子而言,,最高的效果性能也远远足够。
(2)加深了对贝塞尔曲线的理解和使用
第二部分 gasket分形 1 思路
如上图所示,每一次分型实际上就是绘制四个三角形。三个彩色一个白色。每个三角形有三个顶点。只要确定着12个顶点的位置和颜色即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function recurse (p1,p2,p3,depth ){ if (depth==0 ){ return ; } let mid12 = [(p1[0 ]+p2[0 ])/2 ,(p1[1 ]+p2[1 ])/2 ]; let mid23 = [(p2[0 ]+p3[0 ])/2 ,(p2[1 ]+p3[1 ])/2 ]; let mid13 = [(p1[0 ]+p3[0 ])/2 ,(p1[1 ]+p3[1 ])/2 ]; triangles.push (p1,mid12,mid13,mid12,p2,mid23,mid13,mid23,p3,mid12,mid23,mid13); triangleColors.push (red,green,blue,red,green,blue,red,green,blue,white,white,white); recurse (p1,mid12,mid13,depth-1 ); recurse (mid12,p2,mid23,depth-1 ); recurse (mid13,mid23,p3,depth-1 ); }
2重点难点 (1)递归算法设计
(2)代码精简和可读性维护
3心得 (1)加强了自己递归算法的设计能力
(2)学会了webgl颜色的设置
(3)实践了分形几何的自相似性的递归实现。自相似性本身就是递归的特性。