欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

太阳系运动模型

程序员文章站 2022-07-13 11:03:31
...

基本思路

首先,为了让行星运动看起来更加地逼真,采用斜二测画法计算行星的轨道,并将其绘制出来,再将无背景的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(Mercury.getImage(), x[1] + CENTRAL_X - size[1] / 2, y[1] + CENTRAL_Y - size[1] / 2,
				size[1], size[1], 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(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(Mercury.getImage(), x[1] + CENTRAL_X - size[1] / 2, y[1] + CENTRAL_Y - size[1] / 2,
				size[1], size[1], 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;
		}
	}
}

在一开始做此处的轨道处理时我考虑的不够周到,造成了一个小bug,上面的代码是修改后的。以下是有bug的代码(仅供参考):

for (int i = 0; i < 1800; i++) {
	int y1 = setY(r[1], line_angle);
	int x1 = setX(r[1], line_angle, y1);
	g.fillOval(x1 + CENTRAL_X - 2, y1 + CENTRAL_Y - 2, 4, 4);
	int y2 = setY(r[2], line_angle);
	int x2 = setX(r[2], line_angle, y2);
	g.fillOval(x2 + CENTRAL_X - 2, y2 + CENTRAL_Y - 2, 4, 4);
	line_angle += 0.1;
}

此代码执行两次,先绘制一半的轨道,在绘制太阳后,绘制另一半(太阳前方的轨道)。
出现的bug如下:
太阳系运动模型
具体的出现此bug的原因有兴趣的读者可以自己去查看我上方的代码,在此不做赘述了。

完整代码和运行效果

由于屏幕大小有限,天王星和海王星没有绘制。
太阳系运动模型

package com.Solar201028;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JWindow;

public class SunSystem extends JFrame{
	
    public static void main(String[] agrs) {
		SunSystem sun=new SunSystem();
		sun.mysun();
	}
	
	public void mysun() {
		this.setTitle("太阳系运动模型");
		this.setSize(1900,1020);
		this.setLocationRelativeTo(null);
		this.setDefaultCloseOperation(3);
		JPanel mainjp=new JPanel();
		this.add(mainjp);//主面板
		Listener move=new Listener();
		mainjp.addMouseListener(move);
		this.setVisible(true);
		Graphics g=mainjp.getGraphics();
		move.setG(g);
	}
	
}
package com.Solar201028;

import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.JButton;

public class Listener implements MouseListener{

	private int begin=0;
	private Planet planet=new Planet();
	public void setG(Graphics g) {
		planet.setG(g);
	}
	@Override
	public void mouseClicked(MouseEvent e) {
	
	}

	@Override
	public void mousePressed(MouseEvent e) {

	}

	@Override
	public void mouseReleased(MouseEvent e) {
		if(begin==0) {
			begin++;
			planet.start();
		}
		planet.turn();//控制开始与暂停
	}

	@Override
	public void mouseEntered(MouseEvent e) {

	}

	@Override
	public void mouseExited(MouseEvent e) {

	}


}

package com.Solar201028;

import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.Random;

import javax.swing.ImageIcon;


public class Planet extends Thread{
	
	private static final int CENTRAL_X=942;//中心X坐标
	private static final int CENTRAL_Y=490;//中心Y坐标
	private static final int LINE_SIZE=4;//轨道宽度
	
	//依次为太阳,水星,金星,地球,月球,火星,木星,土星,天王星,海王星的尺寸
	private int[] size= {200,15,30,36,12,25,80,100,70,70};
	//依次为,水星,金星,地球,月球,火星,木星,土星,天王星,海王星的轨道半径,第一位0空出,使数组与size对应
	private double[] r= {0,90,160,300,60,450,600,820,900,1000};
	
	private int[] x=new int[9];//行星的X坐标
	private int[] y=new int[9];//行星的Y坐标
	private double[] angle=new double[9]; //绘制行星所需角度
	private double line_angle=0;//绘制轨道时所需的角度
	private URI uri;
	private URL url;
    private AudioClip ac; 
	private boolean turn=false;
	private Graphics G; //面板画布
	public void setG(Graphics g) {
		this.G=g;
	}
	public void turn() {
		this.turn=!this.turn;
	}
	
	/**
	 * 
	 * @param r 半径
	 * @param angle 角度
	 * @return 行星相对运动中心的Y坐标
	 */
	public int setY(double r,double angle) {
		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;
	}
	public void run() {
		for(int i =0;i<angle.length;i++) {
		angle[i]=new Random().nextInt(360); //让行星从轨道上随机的一个位置出发
		}
		//创建缓冲图片
		BufferedImage buff=new BufferedImage(1900,1000,BufferedImage.TYPE_INT_ARGB);
		Graphics g=buff.getGraphics();
		
		
		ImageIcon background=new ImageIcon("image/cosmos.jpg");
		ImageIcon Sun =new ImageIcon("image/太阳.png");
		ImageIcon Mercury =new ImageIcon("image/水星.png");
		ImageIcon Venus =new ImageIcon("image/金星.png");
		ImageIcon Earth =new ImageIcon("image/地球.png");
		ImageIcon Moon = new ImageIcon("image/月球.png");
		ImageIcon Mars = new ImageIcon("image/火星.png");
		ImageIcon Jupiter = new ImageIcon("image/木星.png");
		ImageIcon Saturn = new ImageIcon("image/土星.png");
//		ImageIcon Uranus = new ImageIcon("image/天王星.png");
//		ImageIcon Neptune = new ImageIcon("image/海王星.png");
		
		//添加背景音乐
		File f1 = new File("music/Cornfield Chase.wav");
		try {
			uri = f1.toURI();
			url = uri.toURL();
		} catch (MalformedURLException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		ac = Applet.newAudioClip(url);
		ac.loop();
		
		while (true) {
			try {
				Thread.sleep(42);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if (turn) {
				g.drawImage(background.getImage(), 0, 0, buff.getWidth(), buff.getHeight(), null);

				for (int i = 1; i < 8; i++) {
					y[i] = setY(r[i], angle[i]);
					x[i] = setX(r[i], angle[i], y[i]);
				}

				g.setColor(Color.BLACK);
				for (int i = 0; i < 3600; i++) {//轨道
					for (int j = 3; j < 8; j++) {
						if (j == 4) {
							int y0 = setY(r[j], line_angle);
							int x0 = setX(r[j], line_angle, y0);
							g.fillOval(x0 + x[3] + CENTRAL_X - 2, y0 + y[3] + CENTRAL_Y - 2, 4, 4); //月球轨道
						} else {
							int y0 = setY(r[j], line_angle);
							int x0 = setX(r[j], line_angle, y0);
							g.fillOval(x0 + CENTRAL_X - 2, y0 + CENTRAL_Y - 2, 4, 4);
						}

					}
					line_angle += 0.1;
				}
				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(Mercury.getImage(), x[1] + CENTRAL_X - size[1] / 2, y[1] + CENTRAL_Y - size[1] / 2,
								size[1], size[1], 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(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(Mercury.getImage(), x[1] + CENTRAL_X - size[1] / 2, y[1] + CENTRAL_Y - size[1] / 2,
								size[1], size[1], 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(Earth.getImage(), x[3] + CENTRAL_X - size[3] / 2, y[3] + CENTRAL_Y - size[3] / 2, size[3],
						size[3], null);
				g.drawImage(Moon.getImage(), x[4] + x[3] + CENTRAL_X - size[4] / 2,
						y[4] + y[3] + CENTRAL_Y - size[4] / 2, size[4], size[4], null);
				g.drawImage(Mars.getImage(), x[5] + CENTRAL_X - size[5] / 2, y[5] + CENTRAL_Y - size[5] / 2, size[5],
						size[5], null);
				g.drawImage(Jupiter.getImage(), x[6] + CENTRAL_X - size[6] / 2, y[6] + CENTRAL_Y - size[6] / 2, size[6],
						size[6], null);
				g.drawImage(Saturn.getImage(), x[7] + CENTRAL_X - size[7] / 2, y[7] + CENTRAL_Y - size[7] / 2, size[7],
						size[7], null);
				// 将缓冲图片画到面板上
				G.drawImage(buff, 0, 0, null);
				// 运动速度
				angle[1] += 4;
				angle[2] += 1.5;
				angle[3] += 1;
				angle[4] += 12;
				angle[5] += 0.5;
				angle[6] += 0.1;
				angle[7] += 0.06;
			}
		}
	}
}

由于视频无法上传,只能展示图片了。

背景音乐和图片就不做提供了,有兴趣的读者可以自行寻找图片和背景音乐。

最后,还存在一个小问题,由于绘制的坐标强制转换成int型,丢失了精度,导致行星运动轨迹不够平滑,如果大家有什么办法,欢迎大家提出自己的想法!