太阳系行星运动模型
基本思路
首先,为了让行星运动看起来更加地逼真,采用斜二测画法计算行星的轨道,并将其绘制出来,再将无背景的PNG行星图片按照一定的大小比例绘制在对应的轨道上的某一点。同时,为了让行星运动起来,我们需要用一个循环,不停地计算行星的坐标,并将其绘制。在这之前,要将之前绘制的所有图片进行擦除,也就是清屏(这一操作放在循环当中)。在执行循环的过程中要在绘制图片和清屏之前加上一个时间间隔,这样才能让我们看到图片。这样呈现出的效果就是每个行星在其对应的轨道上运动。这个过程采用一个线程来执行。
此程序的核心算法
计算轨道的方程和计算行星的坐标在原理上是一致的,绘制轨道就是把行星在轨道上的每一点的坐标计算出来,并画上一个点,为了让效果更加明显,我采用了绘制一个半径为2个像素点的圆形的办法。
接下来我们考虑如何来计算轨道上每个点的坐标。因为考虑到行星运动近似圆周运动,因此我们采用将极坐标转化为直角坐标系的办法,即确定了轨道半径和角度,便可以计算出该坐标了。
数学公式如下:
y=√2/4 × r × sin(θ)
x=r × cos(θ)+y
在程序中定义的角度angle是一般的角度,要将它转换成弧度制,于是前面的θ应该写成(angle/180)*π。具体的实现方法如下:
/**
*
* @param r 半径
* @param angle 角度
* @return 行星相对运动中心的Y坐标
*/
public int setY(double r,double angle) {
//由于屏幕中的原点是在左上角,因此y坐标添加一个负号
return -(int)(r*Math.sin((angle/180)*Math.PI)*Math.sqrt(2)/4);
}
public int setX(double r,double angle,int y) {
return (int)(r*Math.cos((angle/180)*Math.PI))-y;
}
计算出的这些坐标需要加上屏幕中心点的坐标(让轨道移动到屏幕中间)。
遇到的问题和解决办法
在绘制行星的过程中要考虑哪个行星先画,原则上y坐标较小的先画(在屏幕上y坐标更小意味着它距离观测点更远)。这样,当行星图片重叠时,相对观测点更近的图片后画便可以覆盖更远的那张图片,可以呈现更加真实的效果。当考虑多个行星的重叠问题时,它的情况变得十分复杂,目前只能对处于中央的太阳、水星、金星做了相应的处理,对于其他的行星通过控制他们间的距离避免图像重叠问题。但这种方法使得离太阳的距离比例不够协调(虽然本来比例就无法真实还原,因为太阳相对地球等行星,它的半径过于大,并且木星与太阳的距离是地球与太阳的距离的29倍之多)。
除了行星间的图片重叠问题,还有轨道绘制问题,一般情况下,轨道最先画,确保轨道不会挡住行星,但考虑到中央的太阳会将水星与金星的一部分轨道覆盖,所以这部分的轨道绘制也要做特殊处理。
还有一个小问题,轨道是具有宽度的,要让行星的中心位于轨道的中心就要做相应的调整。具体实现如下:
for (int i = 0; i < 2700; i++) {
//中间会发生重叠的轨道分为两部分分开画
int y1 = setY(r[1], line_angle-20);
int x1 = setX(r[1], line_angle-20, y1);
g.fillOval(x1 + CENTRAL_X - 2, y1 + CENTRAL_Y - 2, 4, 4);
int y2 = setY(r[2], line_angle-20);
int x2 = setX(r[2], line_angle-20, y2);
g.fillOval(x2 + CENTRAL_X - 2, y2 + CENTRAL_Y - 2, 4, 4);
line_angle += 0.1;
}
if (y[1] > 0) {
// 水星在太阳前
if (y[2] > 0) {
// 金星在太阳前
g.drawImage(Sun.getImage(), CENTRAL_X - size[0] / 2, CENTRAL_Y - size[0] / 2, size[0], size[0],
null);
for (int i = 0; i < 900; i++) {
//另一半轨道
int y1 = setY(r[1], line_angle-20);
int x1 = setX(r[1], line_angle-20, y1);
g.fillOval(x1 + CENTRAL_X - 2, y1 + CENTRAL_Y - 2, 4, 4);
int y2 = setY(r[2], line_angle-20);
int x2 = setX(r[2], line_angle-20, y2);
g.fillOval(x2 + CENTRAL_X - 2, y2 + CENTRAL_Y - 2, 4, 4);
line_angle += 0.1;
}
g.drawImage(Mercury.getImage(), x[1] + CENTRAL_X - size[1] / 2, y[1] + CENTRAL_Y - size[1] / 2,
size[1], size[1], null);
g.drawImage(Venus.getImage(), x[2] + CENTRAL_X - size[2] / 2, y[2] + CENTRAL_Y - size[2] / 2,
size[2], size[2], null);
} else {
// 金星在太阳后
g.drawImage(Venus.getImage(), x[2] + CENTRAL_X - size[2] / 2, y[2] + CENTRAL_Y - size[2] / 2,
size[2], size[2], null);
g.drawImage(Sun.getImage(), CENTRAL_X - size[0] / 2, CENTRAL_Y - size[0] / 2, size[0], size[0],
null);
for (int i = 0; i < 900; i++) {
//另一半轨道
int y1 = setY(r[1], line_angle-20);
int x1 = setX(r[1], line_angle-20, y1);
g.fillOval(x1 + CENTRAL_X - 2, y1 + CENTRAL_Y - 2, 4, 4);
int y2 = setY(r[2], line_angle-20);
int x2 = setX(r[2], line_angle-20, y2);
g.fillOval(x2 + CENTRAL_X - 2, y2 + CENTRAL_Y - 2, 4, 4);
line_angle += 0.1;
}
g.drawImage(Mercury.getImage(), x[1] + CENTRAL_X - size[1] / 2, y[1] + CENTRAL_Y - size[1] / 2,
size[1], size[1], null);
}
} else {
//水星在太阳后
if (y[2] > 0) {
//金星在太阳前
g.drawImage