前期准备
在上次的博客中,我们已经用java了解并制作了画图板,可以在上面添加绘制椭圆、曲线、填充、改变颜色等功能,在之后的版本中会进行修改。
今天要做的是通过递归的方式绘制山脉。
具体步骤
1、窗体的设计
这一部分没有什么需要具体描述的,步骤按照前一个博客中的进行设计就好。需要注意的是窗体背景的设置需要用到Panel组件,直接set是没有用的。
public class background {
public void showbg() {
JFrame bg = new JFrame();
// 设置窗体基本属性
bg.setTitle("地球平原");
bg.setSize(800, 600);
bg.setLocationRelativeTo(null);
// 窗体关闭时结束程序
bg.setDefaultCloseOperation(bg.EXIT_ON_CLOSE);
// 设置背景颜色,直接设置没有用,需要用Panel组件
Color bgcolor = new Color(255, 255, 255);
bg.getContentPane().setBackground(bgcolor);
// 设置布局,放在可见前才显示
FlowLayout flow = new FlowLayout();
bg.setLayout(flow);
// 设置可见,并获取画布对象
bg.setVisible(true);
Graphics g = bg.getGraphics();
// 设置鼠标监听器
MouseListener mouselistener = new MouseListener();
bg.addMouseListener(mouselistener);
}
public static void main(String args[]) {
background bg = new background();
bg.showbg();
}
}
2、山脉的绘制
这部分是整个绘制的重点,比较考验递归的思维。
- 大致思路是取一条水平横线,计算横线两个端点(记为P,Q)的中点M’,将中点向上或者向下平移一段距离,得到新的点M。
----将P和M两点作为新的端点重复上述过程
----将M和Q两点也作为新的端点重复上述过程
最终将所得的所有点连起来,这样我们就会得到一条折线,只要对变化时的参数进行控制,就会呈现山脉的效果。 - 整个的迭代过程类似于一个满二叉树。times为二叉树的总层数,timescounter为这一次调用函数时所在的那一层。
public void showmt(double startx, double starty, double endx, double endy, Color mtcolor, double range, double timescounter) {
double midx, midy;
//计算中点坐标
midx = (startx + endx) / 2;
midy = (starty + endy) / 2 + (Math.random() * 2 - 1)/*正负号*/ * range;
if (timescounter++ == times) {
//画山脉轮廓
g.drawLine((int) startx, (int) starty, (int) midx, (int) midy);
g.drawLine((int) midx, (int) midy, (int) endx, (int) endy);
} else {
//随着相邻两个点的横坐标距离减少,纵坐标随机起伏范围也要减小
range *= rate;
//递归
showmt(startx, starty, midx, midy, mtcolor, range, timescounter);
showmt(midx, midy, endx, endy, mtcolor, range, timescounter);
}
}
如果我们对变换时的参数不加控制,就会使得画出的直线类似于噪声的声波,杂乱无章。要画出比较像山脉的图案:
- 首先,要设置中点上下偏移的范围range,保证不会出现山峰过高或过矮
- 其次,为了使得山脉更平滑,加入了参数rate∈(0,1),随着timescounter的增加,range*=rate,相邻两点间的高度起伏就不会太大,否则就会如下图所示
- 最后timescounter对于每一次函数调用来说都是唯一且独立的,所以一定要放在参数中传入,使得其他函数的调用不会影响到这次函数调用的timescounter。times的值不要太小,会没有山脉的感觉;也不要太大,计算机会处理的很慢。(关于提升绘画速度的会在下一次博客中提到)
3、添加监听器
public class MouseListener implements java.awt.event.MouseListener, ActionListener {
Graphics g;
public void setGraphics(Graphics g) {
this.g = g;
}
public void mouseClicked(MouseEvent e) {
//绘制山脉
mountain mountain = new mountain();
mountain.setGraphics(g);
//此处为伪代码,颜色和位置根据需要修改
Color mtcolor = new Color( ,,);
mountain.showmt(0, starty, 800, endy, mtcolor, 100, 1);
// showmt(double startx, double starty, double endx, double endy, Color mtcolor, double range, double timescounter)
}
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void actionPerformed(ActionEvent e) {
}
}
运行结果
最终画出来应该是这样的轮廓
武汉加油!中国加油!
下集预告
只有轮廓?
一座山没感觉?
递归次数多了画图太慢?
且听下回分解。
链接:java画图板之平面山水画(二)(附源码)