门格海绵的结构可以用以下方法形象化:
从一个正方体开始。(第一个图像)
把正方体的每一个面分成9个正方形。这将把正方体分成27个小正方体,像魔方一样。
把每一面的中间的正方体去掉,把最中心的正方体也去掉,留下20个正方体(第二个图像)。
把每一个留下的小正方体都重复第1-3个步骤。
把以上的步骤重复无穷多次以后,得到的图形就是门格海绵。
实际写代码的时候思路是将一个门格海绵分为三层绘制,解析图如下:
黑色数字代表立方体的顶点,只需要7个就够了;蓝色数字用来标记小立方体,第二层立方体用红色标识,第三层用褐色标识,dx,dy是透视偏移量,立方体的边长设为d.
思路:通过设置一个立方体顶点的坐标来确定需要的其他六个坐标,然后画出一个立方体,再将该立方体分为20个小立方体,每个小立方体同样是通过顶点坐标的信息,递归调用画图方法绘制出来的。每个小立方体都是一个大立方体的缩小。
第一步:实现通过顶点坐标获取到其他顶点的坐标的方法 getpointbyp0();在这里,我用p0表示为顶点。
我是以每个立方体的0号点为顶点。
public Point[] getpointbyp0(Point p0,int d,int dx,int dy){Point[] ps = new Point[7];ps[0] = p0;ps[1] = new Point(p0.x+dx,p0.y-dy);ps[2] = new Point(p0.x+d+dx,p0.y-dy);ps[3] = new Point(p0.x+d,p0.y);ps[4] = new Point(p0.x,p0.y+d);ps[5] = new Point(p0.x+d,p0.y+d);ps[6] = new Point(p0.x+d+dx,p0.y-dy+d);return ps;
这个方法返回的是一个包含了7个顶点信息的点数组,顶点为数组的第一个元素。
第二步:递归画法
public void drawMenGe(Point p,int d,int dx,int dy, int count){if(count<=0)return;count--;Point[] ps = getpointbyp0(p, d, dx, dy);//画立方体Polygon ponlygon1 = new Polygon();ponlygon1.addPoint(ps[0].x,ps[0].y);ponlygon1.addPoint(ps[1].x,ps[1].y);ponlygon1.addPoint(ps[2].x,ps[2].y);ponlygon1.addPoint(ps[3].x,ps[3].y);g.setColor(new Color(200,233,139));g.fillPolygon(ponlygon1); Polygon ponlygon2 = new Polygon();ponlygon2.addPoint(ps[0].x,ps[0].y);ponlygon2.addPoint(ps[3].x,ps[3].y);ponlygon2.addPoint(ps[5].x,ps[5].y);ponlygon2.addPoint(ps[4].x,ps[4].y);g.setColor(new Color(142,197,230));g.fillPolygon(ponlygon2); Polygon ponlygon3 = new Polygon();ponlygon3.addPoint(ps[2].x,ps[2].y);ponlygon3.addPoint(ps[3].x,ps[3].y);ponlygon3.addPoint(ps[5].x,ps[5].y);ponlygon3.addPoint(ps[6].x,ps[6].y);g.setColor(new Color(241,131,162));g.fillPolygon(ponlygon3);
这一段是完成画立方体的,Point[] ps = getpointbyp0(p, d, dx, dy); 这一步得到了立方体的所有顶点信息,然后将立方体的三个面涂上颜色。
0123 表示的是上面,0354表示的是前面,2356表示的是侧面。
那现在要做的就是将这个完整的立方体分成20个小立方体,每个小立方体的7个顶点信息都存在一个顶点数组里,接着调用递归方法就可以画出来每个小立方体。
//第一层Point pp1[] = new Point[8];pp1[0] = new Point(p.x,p.y);pp1[1] = new Point(p.x+d/3,p.y);pp1[2] = new Point(p.x+2*d/3,p.y);pp1[3] = new Point(p.x+2*d/3+dx/3,p.y-dy/3);pp1[4] = new Point(p.x+2*d/3+dx*2/3,p.y-dy*2/3);pp1[5] = new Point(p.x+d/3+dx*2/3,p.y-dy*2/3);pp1[6] = new Point(p.x+dx*2/3,p.y-dy*2/3);pp1[7] = new Point(p.x+dx/3,p.y-dy/3);drawMenGe(pp1[0],d/3,dx/3,dy/3,count);drawMenGe(pp1[1],d/3,dx/3,dy/3,count);drawMenGe(pp1[2],d/3,dx/3,dy/3,count);drawMenGe(pp1[3],d/3,dx/3,dy/3,count);drawMenGe(pp1[4],d/3,dx/3,dy/3,count);drawMenGe(pp1[5],d/3,dx/3,dy/3,count);drawMenGe(pp1[6],d/3,dx/3,dy/3,count);drawMenGe(pp1[7],d/3,dx/3,dy/3,count);
//第二层Point pp2[] = new Point[4];pp2[0] = new Point(p.x,p.y+d/3);pp2[1] = new Point(p.x+2*d/3,p.y+d/3);pp2[2] = new Point(p.x+2*d/3+dx*2/3,p.y-dy*2/3+d/3);pp2[3] = new Point(p.x+dx*2/3,p.y-dy*2/3+d/3);drawMenGe(pp2[0],d/3,dx/3,dy/3,count);drawMenGe(pp2[1],d/3,dx/3,dy/3,count);drawMenGe(pp2[2],d/3,dx/3,dy/3,count);drawMenGe(pp2[3],d/3,dx/3,dy/3,count);
//第三层Point pp3[] = new Point[8];pp3[0] = new Point(p.x,p.y+2*d/3);pp3[1] = new Point(p.x+d/3,p.y+2*d/3);pp3[2] = new Point(p.x+2*d/3,p.y+2*d/3);pp3[3] = new Point(p.x+2*d/3+dx/3,p.y-dy/3+2*d/3);pp3[4] = new Point(p.x+2*d/3+dx*2/3,p.y-dy*2/3+2*d/3);pp3[5] = new Point(p.x+d/3+dx*2/3,p.y-dy*2/3+2*d/3);pp3[6] = new Point(p.x+dx*2/3,p.y-dy*2/3+2*d/3);pp3[7] = new Point(p.x+dx/3,p.y-dy/3+2*d/3);drawMenGe(pp3[0],d/3,dx/3,dy/3,count);drawMenGe(pp3[1],d/3,dx/3,dy/3,count);drawMenGe(pp3[2],d/3,dx/3,dy/3,count);drawMenGe(pp3[3],d/3,dx/3,dy/3,count);drawMenGe(pp3[4],d/3,dx/3,dy/3,count);drawMenGe(pp3[5],d/3,dx/3,dy/3,count);drawMenGe(pp3[6],d/3,dx/3,dy/3,count);drawMenGe(pp3[7],d/3,dx/3,dy/3,count);
第一层和第三层都只有8个立方体,第二层只有4个立方体。按图上的标识方法,三层的各个小立方体的0号顶点坐标都可以由大立方体的0号坐标计算得到。而且第一层和第三层的标识立方体的方法是一样的,所以第三层的各个小立方体的0号顶点坐标只需要在第一层的基础上给纵坐标加上某个偏移量就够了。找准哪个小立方体是搭在哪个小立方体上的就可以很容易的算出来顶点坐标。不过特殊的地方在不是每个小立方体都能在图上显示出来,会被遮盖,计算时还有画图的时候递归顺序需要注意,否则图形就会出现类似下面的错误。
图形大小都是对的,只是颜色的填充位置错误。
这就是因为发生了覆盖。
//错误代码//第一层Point pp1[] = new Point[8];pp1[0] = new Point(p.x,p.y);pp1[1] = new Point(p.x+d/3,p.y);pp1[2] = new Point(p.x+2*d/3,p.y);pp1[3] = new Point(p.x+2*d/3+dx/3,p.y-dy/3);pp1[4] = new Point(p.x+2*d/3+dx*2/3,p.y-dy*2/3);pp1[5] = new Point(p.x+d/3+dx*2/3,p.y-dy*2/3);pp1[6] = new Point(p.x+dx*2/3,p.y-dy*2/3);pp1[7] = new Point(p.x+dx/3,p.y-dy/3);drawMenGe(pp1[0],d/3,dx/3,dy/3,count);drawMenGe(pp1[1],d/3,dx/3,dy/3,count);drawMenGe(pp1[2],d/3,dx/3,dy/3,count);drawMenGe(pp1[3],d/3,dx/3,dy/3,count);drawMenGe(pp1[4],d/3,dx/3,dy/3,count);drawMenGe(pp1[5],d/3,dx/3,dy/3,count);drawMenGe(pp1[6],d/3,dx/3,dy/3,count);drawMenGe(pp1[7],d/3,dx/3,dy/3,count);//第二层Point pp2[] = new Point[4];pp2[0] = new Point(p.x,p.y+d/3);pp2[1] = new Point(p.x+2*d/3,p.y+d/3);pp2[2] = new Point(p.x+2*d/3+dx*2/3,p.y-dy*2/3+d/3);pp2[3] = new Point(p.x+dx*2/3,p.y-dy*2/3+d/3);drawMenGe(pp2[0],d/3,dx/3,dy/3,count);drawMenGe(pp2[1],d/3,dx/3,dy/3,count);drawMenGe(pp2[2],d/3,dx/3,dy/3,count);drawMenGe(pp2[3],d/3,dx/3,dy/3,count);//第三层Point pp3[] = new Point[8];pp3[0] = new Point(p.x,p.y+2*d/3);pp3[1] = new Point(p.x+d/3,p.y+2*d/3);pp3[2] = new Point(p.x+2*d/3,p.y+2*d/3);pp3[3] = new Point(p.x+2*d/3+dx/3,p.y-dy/3+2*d/3);pp3[4] = new Point(p.x+2*d/3+dx*2/3,p.y-dy*2/3+2*d/3);pp3[5] = new Point(p.x+d/3+dx*2/3,p.y-dy*2/3+2*d/3);pp3[6] = new Point(p.x+dx*2/3,p.y-dy*2/3+2*d/3);pp3[7] = new Point(p.x+dx/3,p.y-dy/3+2*d/3);drawMenGe(pp3[0],d/3,dx/3,dy/3,count);drawMenGe(pp3[1],d/3,dx/3,dy/3,count);drawMenGe(pp3[2],d/3,dx/3,dy/3,count);drawMenGe(pp3[3],d/3,dx/3,dy/3,count);drawMenGe(pp3[4],d/3,dx/3,dy/3,count);drawMenGe(pp3[5],d/3,dx/3,dy/3,count);drawMenGe(pp3[6],d/3,dx/3,dy/3,count);drawMenGe(pp3[7],d/3,dx/3,dy/3,count);
当你以这种方式画就会出错。
当我们画0号小立方体时,是这样的效果:
接着画1号的小立方体时,1号立方体的上面(0123)填充的颜色就会把0号立方体的粉色那面覆盖一部分,前面(0354)又会覆盖粉色的一部分,加起来就完全覆盖了,就会如下面效果:
2号同理,但到了3号:
所以在使用递归画小立方体的时候,你要清楚哪个小立方体要在哪个小立方体后面画才能让颜色有序覆盖。
正确代码如下:
//第三层Point pp3[] = new Point[8];pp3[0] = new Point(p.x,p.y+2*d/3);pp3[1] = new Point(p.x+d/3,p.y+2*d/3);pp3[2] = new Point(p.x+2*d/3,p.y+2*d/3);pp3[3] = new Point(p.x+2*d/3+dx/3,p.y-dy/3+2*d/3);pp3[4] = new Point(p.x+2*d/3+dx*2/3,p.y-dy*2/3+2*d/3);pp3[5] = new Point(p.x+d/3+dx*2/3,p.y-dy*2/3+2*d/3);pp3[6] = new Point(p.x+dx*2/3,p.y-dy*2/3+2*d/3);pp3[7] = new Point(p.x+dx/3,p.y-dy/3+2*d/3);drawMenGe(pp3[6],d/3,dx/3,dy/3,count);drawMenGe(pp3[5],d/3,dx/3,dy/3,count);drawMenGe(pp3[4],d/3,dx/3,dy/3,count);drawMenGe(pp3[3],d/3,dx/3,dy/3,count);drawMenGe(pp3[7],d/3,dx/3,dy/3,count);drawMenGe(pp3[0],d/3,dx/3,dy/3,count);drawMenGe(pp3[1],d/3,dx/3,dy/3,count);drawMenGe(pp3[2],d/3,dx/3,dy/3,count);//第二层Point pp2[] = new Point[4];pp2[0] = new Point(p.x,p.y+d/3);pp2[1] = new Point(p.x+2*d/3,p.y+d/3);pp2[2] = new Point(p.x+2*d/3+dx*2/3,p.y-dy*2/3+d/3);pp2[3] = new Point(p.x+dx*2/3,p.y-dy*2/3+d/3);drawMenGe(pp2[0],d/3,dx/3,dy/3,count);drawMenGe(pp2[2],d/3,dx/3,dy/3,count);drawMenGe(pp2[1],d/3,dx/3,dy/3,count);drawMenGe(pp2[3],d/3,dx/3,dy/3,count);//第一层Point pp1[] = new Point[8];pp1[0] = new Point(p.x,p.y);pp1[1] = new Point(p.x+d/3,p.y);pp1[2] = new Point(p.x+2*d/3,p.y);pp1[3] = new Point(p.x+2*d/3+dx/3,p.y-dy/3);pp1[4] = new Point(p.x+2*d/3+dx*2/3,p.y-dy*2/3);pp1[5] = new Point(p.x+d/3+dx*2/3,p.y-dy*2/3);pp1[6] = new Point(p.x+dx*2/3,p.y-dy*2/3);pp1[7] = new Point(p.x+dx/3,p.y-dy/3);drawMenGe(pp1[6],d/3,dx/3,dy/3,count);drawMenGe(pp1[5],d/3,dx/3,dy/3,count);drawMenGe(pp1[4],d/3,dx/3,dy/3,count);drawMenGe(pp1[3],d/3,dx/3,dy/3,count);drawMenGe(pp1[7],d/3,dx/3,dy/3,count);drawMenGe(pp1[0],d/3,dx/3,dy/3,count);drawMenGe(pp1[1],d/3,dx/3,dy/3,count);drawMenGe(pp1[2],d/3,dx/3,dy/3,count);
正确效果为:
记录下心得:当时画这个鬼东西,画了两天,一开始不知道是画图顺序的问题,纠结了好久一直找不到问题,后来没有直接填充颜色,用画线的方式检查了一下,发现图形的立体结构没有出现错误,然后就开始一层一层,一个一个立方体的慢慢找错误,终于发现是颜色的覆盖出了问题。思路并不难,但是因为代码多了,容易忽略某些细节,还有各个代码之间的调试,出现bug找出来也很难,非要一步一步,从小细节慢慢找,慢慢检查。以后碰到更复杂的实现过程时,就更要注意。