在完成了创意画板的基础功能实现后,我们就可以通过画板来绘制一些有趣的图形了
1.平面山脉图
效果图如上
山脉图是由一个个山峰构成的,所以先绘制一个山峰
而山峰的绘制算法是:先确定两个点A B,然后获取A B的中间点P,其中P的x值为A Bx轴向上的中点,P点的y值是以A By中值为基础,在(-k,k)范围内随机取值。算出P点后,对AP BP 进行如上的相通算法,不过k值要减小,迭代下去。在所有的点计算完成后,将点连线就获得了一个山脉曲线。
具体代码实现如下,其中N为迭代次数,K为初始缩小比例
//山峰public void shanfen(int x1,int y1,int x2,int y2,int N,Graphics gr,double k){ int y = (y1 + y2)/2;if(N>1){int px,py;int ran = (int)((Math.random()*800-400)*k);px = (x1 +x2)/2;py = ran + y;k*=0.5;//等比缩小shanfen(x1,y1,px,py,N-1,gr,k);shanfen(px,py,x2,y2,N-1,gr,k); }else {gr.drawLine(x1,y1,x2,y2);} }
这样,你就获得了一张这样的山脉曲线
现在我们可以将颜色填充进去
注意到山脉曲线的绘制实际上是一条条线段构成的,所以我们可以在绘制每一条线段之后给线段下方填充颜色
这里要用到Polygon类中的填充方法
Polygon py = new Polygon();//创建Polygon类的对象 py.addPoint(x1,y1);//依次添加点,注意要依次添加,具体可以自己尝试 py.addPoint(x2,y2); py.addPoint(x2,2000);//y值可以自己定 py.addPoint(x1,2000); int r = (int)Math.random()*256;//这里我取随机颜色 int g = (int)Math.random()*256; int b = (int)Math.random()*256; gr.setColor(new Color(r,g,b,128));//128是指透明度 gr.fillPolygon(py);//填充方法 gr.setColor(new Color(0,0,0,255));//复原透明度
这样,我们就获得了一张这样的图形
然后山脉就是多绘制几次,我这里绘制了五次,就有了最初的效果了。
2.谢宾斯基地毯
效果图如下
谢宾斯基地毯的绘制原理是给定两个点A B,将以AB为对角点的矩形均分为九宫格,将九宫格中间部分涂黑,然后对剩下的八个宫格进行相同操作
具体代码如下,其中N为迭代次数
//谢宾斯基地毯 public void xiefu(int x1,int y1,int x2,int y2,int N,Graphics gr){ if(N > 1){gr.fillRect(x1 + (x2 - x1)/3,y1 + (y2 - y1)/3,(x2 - x1)/3,(y2 - y1)/3);//画笔的填充方法 xiefu(x1,y1,x1 + (x2 - x1)/3,y1 + (y2 - y1)/3,N - 1,gr);//分别对八个宫格进行迭代 xiefu(x1 + (x2 - x1)/3,y1,x1 + 2*(x2 - x1)/3,y1 + (y2 - y1)/3,N - 1,gr); xiefu(x1 + 2*(x2 - x1)/3,y1,x2,y1 + (y2 - y1)/3,N - 1,gr); xiefu(x1,y1 + (y2 - y1)/3,x1 + (x2 - x1)/3,y1 + 2*(y2 - y1)/3,N - 1,gr); xiefu(x1 + 2*(x2 - x1)/3,y1 + (y2 - y1)/3,x2,y1 + 2*(y2 - y1)/3,N - 1,gr); xiefu(x1,y1 + 2*(y2 - y1)/3,x1 + (x2 - x1)/3,y2,N - 1,gr); xiefu(x1 + (x2 - x1)/3,y1 + 2*(y2 - y1)/3,x1 + 2*(x2 - x1)/3,y2,N - 1,gr); xiefu(x1 + 2*(x2 - x1)/3,y1 + 2*(y2 - y1)/3,x2,y2,N - 1,gr); } }
其核心编程思想是跟山脉的绘制一样的,都是利用的迭代算法能存储数据的能力。
3.门格海绵
效果图如下
门格海绵是谢宾斯基地毯的立体版,想要绘制门格海绵,就需要先绘制出立体的正方体(矩形也行,这里我选择了正方体)
绘制立体正方体就是绘制我们看见的立体正方体的线,然后将不同的面填充不同的颜色以达到区分的效果
//绘制正方体 public void drawCube(int x1,int y1,int x2,int y2,Graphics gr){ int dx = (x2-x1)/2;int dy = (y2-y1)/2; //绘线gr.drawLine(x1,y1,x2,y1);gr.drawLine(x2,y1,x2,y2);gr.drawLine(x2,y2,x1,y2);gr.drawLine(x1,y2,x1,y1);gr.drawLine(x1,y1,x1+dx,y1-dy);gr.drawLine(x2,y1,x2+dx,y1-dy);gr.drawLine(x2,y2,x2+dx,y2-dy);gr.drawLine(x1+dx,y1-dy,x2+dx,y1-dy);gr.drawLine(x2+dx,y1-dy,x2+dx,y2-dy); //填充颜色Polygon py = new Polygon();py.addPoint(x1,y1);py.addPoint(x2,y1);py.addPoint(x2,y2);py.addPoint(x1,y2);gr.setColor(new Color(200,100,100));//三个面填充不同的颜色,以便区分gr.fillPolygon(py); Polygon py1 = new Polygon();py1.addPoint(x1,y1);py1.addPoint(x2,y1);py1.addPoint(x2+dx,y1-dy);py1.addPoint(x1+dx,y1-dy);gr.setColor(new Color(100,200,100));gr.fillPolygon(py1); Polygon py2 = new Polygon();py2.addPoint(x2,y1);py2.addPoint(x2,y2);py2.addPoint(x2+dx,y2-dy);py2.addPoint(x2+dx,y1-dy);gr.setColor(new Color(100,100,200));gr.fillPolygon(py2); }
这样,我们就可以获得一个看起来是立体的正方体
下一步就是进行迭代,与谢宾斯基地毯的思路一样,对正方体操作,然后再对需要重复操作的地方进行迭代,用N控制迭代次数
门格海绵是将正方体(长方体)均分成27份(以谢宾斯基地毯的划分形式),然后将每个面的中间正方体和中心正方体掏去。再对每个小正方体进行相同操作
效果是这样子的
具体代码如下:
public void drawSponge(int x1,int y1,int x2,int y2,Graphics gr,int N){if(N > 1){int dx = (x2-x1)/2;int dy = (y2-y1)/2; //后面8个drawSponge(x1+2*dx/3,y1+2*(y2-y1)/3-2*dy/3,x1+(x2-x1)/3+2*dx/3,y2-2*dy/3,gr,N-1);drawSponge(x1+(x2-x1)/3+2*dx/3,y1+2*(y2-y1)/3-2*dy/3,x1+2*(x2-x1)/3+2*dx/3,y2-2*dy/3,gr,N-1);drawSponge(x1+2*dx/3+2*(x2-x1)/3,y1+2*(y2-y1)/3-2*dy/3,x2+2*dx/3,y2-2*dy/3,gr,N-1); drawSponge(x1+2*dx/3,y1+(y2-y1)/3-2*dy/3,x1+(x2-x1)/3+2*dx/3,y1+2*(y2-y1)/3-2*dy/3,gr,N-1);drawSponge(x1+2*(x2-x1)/3+2*dx/3,y1+(y2-y1)/3-2*dy/3,x2+2*dx/3,y1+2*(y2-y1)/3-2*dy/3,gr,N-1); drawSponge(x1+2*dx/3,y1-2*dy/3,x1+(x2-x1)/3+2*dx/3,y1+(y2-y1)/3-2*dy/3,gr,N-1);drawSponge(x1+(x2-x1)/3+2*dx/3,y1-2*dy/3,x1+2*(x2-x1)/3+2*dx/3,y1+(y2-y1)/3-2*dy/3,gr,N-1);drawSponge(x1+2*(x2-x1)/3+2*dx/3,y1-2*dy/3,x2+2*dx/3,y1+(y2-y1)/3-2*dy/3,gr,N-1); //中间四个drawSponge(x1+dx/3,y1+2*(y2-y1)/3-dy/3,x1+(x2-x1)/3+dx/3,y2-dy/3,gr,N-1);drawSponge(x1+dx/3+2*(x2-x1)/3,y1+2*(y2-y1)/3-dy/3,x2+dx/3,y2-dy/3,gr,N-1);drawSponge(x1+dx/3,y1-dy/3,x1+(x2-x1)/3+dx/3,y1+(y2-y1)/3-dy/3,gr,N-1);drawSponge(x1+dx/3+2*(x2-x1)/3,y1-dy/3,x2+dx/3,y1+(y2-y1)/3-dy/3,gr,N-1); //正面8个drawSponge(x1,y1+2*(y2-y1)/3,x1+(x2-x1)/3,y2,gr,N-1);drawSponge(x1+(x2-x1)/3,y1+2*(y2-y1)/3,x1+2*(x2-x1)/3,y2,gr,N-1);drawSponge(x1+2*(x2-x1)/3,y1+2*(y2-y1)/3,x2,y2,gr,N-1); drawSponge(x1,y1+(y2-y1)/3,x1+(x2-x1)/3,y1+2*(y2-y1)/3,gr,N-1);drawSponge(x1+2*(x2-x1)/3,y1+(y2-y1)/3,x2,y1+2*(y2-y1)/3,gr,N-1); drawSponge(x1,y1,x1+(x2-x1)/3,y1+(y2-y1)/3,gr,N-1);drawSponge(x1+(x2-x1)/3,y1,x1+2*(x2-x1)/3,y1+(y2-y1)/3,gr,N-1);drawSponge(x1+2*(x2-x1)/3,y1,x2,y1+(y2-y1)/3,gr,N-1); }else{drawCube(x1,y1,x2,y2,gr); } }
注意,由于绘制次序的原因,后绘制的图形会覆盖先绘制的图形,所以绘制正方体的时候要先绘制大部分看不见的,就是后面的,在绘制前面的,先绘制下面的,再绘制上面的。
4.分形的绘制
IFS manual
以上的三个效果图是就根据我提供的网站中类似
这样的算法实现的
其中set1234的意思是,在你每次要算新的xy时随机等概率选取其中一组值进行计算,x0y0可以随机选取。
例如其中枫叶图形的绘制
//枫叶 public void drawMaple(double x,double y,Graphics gr){ for (int i = 0; i < 7000000; i++) {int m = (int)(Math.random()*4);double xb = 0 ,yb = 0;if(m == 0){xb = 0.14*x + 0.01*y - 0.08;yb = 0.51*y - 1.31;gr.drawLine((int) (xb*100) + 700,(int) (yb*100) + 500,(int) (xb*100) + 700,(int) (yb*100) + 500); }if(m == 1){xb = 0.43*x + 0.52*y +1.49;yb = (-0.45)*x + 0.5*y - 0.75;gr.drawLine((int) (xb*100) + 700,(int) (yb*100) + 500,(int) (xb*100) + 700,(int) (yb*100) + 500); }if(m == 2){xb = 0.45*x - 0.49*y - 1.62;yb = 0.47*x + 0.47*y - 0.74;gr.drawLine((int) (xb*100) + 700,(int) (yb*100) + 500,(int) (xb*100) + 700,(int) (yb*100) + 500); }if(m == 3){xb = 0.49*x + 0.02;yb = 0.51*y + 1.62;gr.drawLine((int) (xb*100) + 700,(int) (yb*100) + 500,(int) (xb*100) + 700,(int) (yb*100) + 500); }x = xb;y = yb;} }
注意绘制比例的缩放和移动
其中也有一些加概率的,就是每次要算新的xy时,按概率选取数据进行计算就行
5.立体山脉
效果图
立体山脉的原理与绘制山峰类似
选取ABC三点,对AB,AC,BC进行山峰算法计算,算出DEF三点,然后对三角形ADE,BDF,DEF,CEF进行相同算法计算
其代码实现如下
public void liti(int x1,int y1,int x2,int y2,int x3,int y3,Graphics gr,int N,double k){ if(N > 1){int pax,pay,pbx,pby,pcx,pcy;pax = (x1 + x2)/2;pbx = (x1 + x3)/2;pcx = (x2 + x3)/2; double ran = (Math.random()*300-150)*k;pay = (int) ((y1 + y2)/2 + ran);pby = (int) ((y1 + y3)/2 + ran);pcy = (int) ((y2 + y3)/2 + ran); k*=0.5;liti(x1,y1,pax,pay,pbx,pby,gr,N-1,k);liti(pax,pay,x2,y2,pcx,pcy,gr,N-1,k);liti(pbx,pby,pcx,pcy,x3,y3,gr,N-1,k);liti(pcx,pcy,pbx,pby,pax,pay,gr,N-1,k);}else {gr.drawLine(x1,y1,x2,y2);gr.drawLine(x1,y1,x3,y3);gr.drawLine(x2,y2,x3,y3);} }
但是如果按照这种方法进行计算就会由这种效果
显然,这样的效果不对
分析一下,在三角形ADE中和三角形DEF中计算DE的中间波动点应该为同一个点,但是上面的代码计算了两次,算出两个不同的点,所以就会有了上面图形的效果
解决方案:由于算中间波动点是根据两个点的坐标进行计算的,所以我没计算出一个中间波动点,就将这两个用来计算的点和中间的点的坐标存储起来,下次计算时遍历一遍,如果下次计算时用来计算的两个点在存储的数据中,则直接使用之前算出来的点,如果不在,则算出新的点,存储进去。
理一下思路:
新建三个集合,两个用来存储用来计算的两个点的y坐标,一个用来存储算出来的点的y坐标,集合相同的坐标存储一套数据(两个原始点和算出来的点),每次计算新点时都将两个用来计算的点遍历一遍集合,如果有,就用,没有,就用新算出来的点
具体实现代码如下
ArrayList yalist = new ArrayList();ArrayList yblist = new ArrayList();//用来存储两个用来计算的点的y坐标ArrayList yclist = new ArrayList();//用来存储计算出来的点的y坐标public void liti(int x1,int y1,int x2,int y2,int x3,int y3,Graphics gr,int N,double k){ if(N > 1){int pax,pay,pbx,pby,pcx,pcy;pax = (x1 + x2)/2;pbx = (x1 + x3)/2;pcx = (x2 + x3)/2; double ran = (Math.random()*300-150)*k;pay = (int) ((y1 + y2)/2 + ran);pby = (int) ((y1 + y3)/2 + ran);pcy = (int) ((y2 + y3)/2 + ran);for (int i = 0; i < yalist.size(); i++) {if(y2 == (Integer)yalist.get(i) && y1 == (Integer)yblist.get(i)||y1 == (Integer)yalist.get(i) && y2 == (Integer)yblist.get(i)){pay = (Integer) yclist.get(i);}}for (int i = 0; i < yalist.size(); i++) {if(y1 == (Integer)yalist.get(i) && y3 == (Integer)yblist.get(i)||y3 == (Integer)yalist.get(i) && y1 == (Integer)yblist.get(i)){pby = (Integer) yclist.get(i);}}for (int i = 0; i < yalist.size(); i++) {if(y2 == (Integer)yalist.get(i) && y3 == (Integer)yblist.get(i)||y3 == (Integer)yalist.get(i) && y2 == (Integer)yblist.get(i)){pcy = (Integer) yclist.get(i);}} yalist.add(y2);yblist.add(y1);yclist.add(pay);yalist.add(y1);yblist.add(y3);yclist.add(pby);yalist.add(y2);yblist.add(y3);yclist.add(pcy); k*=0.5;liti(x1,y1,pax,pay,pbx,pby,gr,N-1,k);liti(pax,pay,x2,y2,pcx,pcy,gr,N-1,k);liti(pbx,pby,pcx,pcy,x3,y3,gr,N-1,k);liti(pcx,pcy,pbx,pby,pax,pay,gr,N-1,k);}else {gr.drawLine(x1,y1,x2,y2);gr.drawLine(x1,y1,x3,y3);gr.drawLine(x2,y2,x3,y3);} }