O'所以我试图在2个移动的椭圆(圆圈)之间编写一个简单的碰撞检测,并使用我发现的this指南更新它们的位置和速度。
然而,我得到的结果并不完美,对象正在碰撞和更新,但并不总是正确,它们会纠结一些,我很沮丧,因为我无法弄清楚为什么会发生这种情况。
注意:请忽略物体与边界的碰撞,它们有时会卡住(我需要更改它而不是我遇到问题的问题)
以下是我使用的2个类的代码:
package letifer.com;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class MaxwellsDemon extends JPanel{
public static final long serialVersionUID = 1L;
private final Dimension DIMS = new Dimension(800, 500);
private final int UPDATE_INTERVAL = 20, BALLS_COUNT = 20;
private final JFrame FRAME = new JFrame("MaxwellsDemon");
private int leftCountR, leftCountB, rightCountR, rightCountB;
private Ball balls[];
private Random random;
private Timer timer;
public MaxwellsDemon(){
super();
initThis();
initFrame();
initBalls();
registerTimer();
timer.start();
}
private void initThis(){
setSize(DIMS);
setBackground(Color.white);
random = new Random();
}
private void initFrame(){
FRAME.setContentPane(new Container(){
public void paint(Graphics g){
super.paint(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
for(int i=0; i<2; i++)
g2d.drawLine(0, DIMS.height+i, DIMS.width-1, DIMS.height+i);
g2d.setColor(Color.blue);
g2d.fillOval(25, DIMS.height+20, Ball.radius*2, Ball.radius*2);
g2d.fillOval(DIMS.width-90, DIMS.height+20, Ball.radius*2, Ball.radius*2);
g2d.setColor(Color.red);
g2d.fillOval(25, DIMS.height+60, Ball.radius*2, Ball.radius*2);
g2d.fillOval(DIMS.width-90, DIMS.height+60, Ball.radius*2, Ball.radius*2);
g2d.setFont(new Font("Serif", Font.BOLD, 18));
g2d.setColor(Color.black);
g2d.drawString(String.format("= %d", leftCountB), 45, DIMS.height+32);
g2d.drawString(String.format("= %d", leftCountR), 45, DIMS.height+72);
g2d.drawString(String.format("= %d", rightCountB), DIMS.width-70, DIMS.height+32);
g2d.drawString(String.format("= %d", rightCountR), DIMS.width-70, DIMS.height+72);
}
});
FRAME.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
FRAME.getContentPane().setPreferredSize(new Dimension(DIMS.width, DIMS.height+100));
FRAME.getContentPane().add(this);
FRAME.setResizable(false);
FRAME.pack();
FRAME.setLocationRelativeTo(null);
FRAME.setVisible(true);
}
private void initBalls(){
balls = new Ball[BALLS_COUNT];
leftCountR = 0; leftCountB = 0; rightCountR = 0; rightCountB = 0;
for(int i=0, f=1; i<BALLS_COUNT; i++, f=-f){
balls[i] = new Ball(new Point2D.Double(random.nextInt(DIMS.width-Ball.radius*2-1)+Ball.radius+1,
random.nextInt(DIMS.height-Ball.radius*2-1)+Ball.radius+1),
0.15,
random.nextDouble()*(Math.PI*2),
(f>0?Color.blue:Color.red));
if(balls[i].c == Color.blue){
if(balls[i].locOnScreen.x < DIMS.width/2){
leftCountB++;
}else{
rightCountB++;
}
}else{
if(balls[i].locOnScreen.x < DIMS.width/2){
leftCountR++;
}else{
rightCountR++;
}
}
}
}
private void update(){
checkForCollisions();
updateBalls();
}
private void checkForCollisions(){
for(int i=0; i<BALLS_COUNT-1; i++){
for(int j=i+1; j<BALLS_COUNT; j++){
double dx = balls[i].getX()-balls[j].getX();
double dy = balls[i].getY()-balls[j].getY();
Point2D.Double difference = new Point2D.Double(dx, dy);
double distanceAtFrameEnd = Math.sqrt(dx*dx+dy*dy);
double collisionDistance = Ball.radius*2;
if(distanceAtFrameEnd < collisionDistance){
//go back in time :)
double millisecondsAfterCollision = moveBackToCollisionPoint(balls[i], balls[j], distanceAtFrameEnd, collisionDistance);
//calculate the new distance vector.
dx = balls[i].getX()-balls[j].getX();
dy = balls[i].getY()-balls[j].getY();
difference.setLocation(dx, dy);
Point2D.Double normalPlane = new Point2D.Double(difference.x, difference.y);
//normalize it
double distance = Math.sqrt(dx*dx+dy*dy);
normalPlane.x /= distance;
normalPlane.y /= distance;
//calculate the collision vector(rotate by 90deg PI/2).
Point2D.Double collisionPlane = new Point2D.Double(-normalPlane.y, normalPlane.x);
//calculate prior velocities relative the the collision plane and normal.
double velIx = balls[i].getVX(), velIy = balls[i].getVY();
double velJx = balls[j].getVX(), velJy = balls[j].getVY();
double n_vel1 = (velIx * normalPlane.x) + (velIy * normalPlane.y);
double c_vel1 = (velIx * collisionPlane.x) + (velIy * collisionPlane.y);
double n_vel2 = (velJx * normalPlane.x) + (velJy * normalPlane.y);
double c_vel2 = (velJx * collisionPlane.x) + (velJy * collisionPlane.y);
//calculate the scaler velocities of each object after the collision.
double n_vel1_after = ((n_vel1 * (1/*ballI mass*/ - 1/*ballJ mass*/)) + (2 * 1/*ballJ mass*/ * n_vel2)) / (1/*ballJ mass*/ + 1/*ballJ mass*/);
double n_vel2_after = ((n_vel2 * (1/*ballJ mass*/ - 1/*ballI mass*/)) + (2 * 1/*ballJ mass*/ * n_vel1)) / (1/*ballJ mass*/ + 1/*ballJ mass*/);
//double velObject2Tangent_After = c_vel2;
//double velObject1Tangent_After = c_vel1;
//convert the scalers to vectors by multiplying by the normalized plane vectors.
Point2D.Double vec_n_vel2_after = new Point2D.Double(n_vel2_after * normalPlane.x, n_vel2_after * normalPlane.y);
Point2D.Double vec_c_vel2 = new Point2D.Double(c_vel2 * collisionPlane.x, c_vel2 * collisionPlane.y);
Point2D.Double vec_n_vel1_after = new Point2D.Double(n_vel1_after * normalPlane.x, n_vel1_after * normalPlane.y);
Point2D.Double vec_c_vel1 = new Point2D.Double(c_vel1 * collisionPlane.x, c_vel1 * collisionPlane.y);
//combine the vectors back into a single vector in world space.
Point2D.Double vel1_after = new Point2D.Double(vec_n_vel1_after.x + vec_c_vel1.x, vec_n_vel1_after.y + vec_c_vel1.y);
Point2D.Double vel2_after = new Point2D.Double(vec_n_vel2_after.x + vec_c_vel2.x, vec_n_vel2_after.y + vec_c_vel2.y);
//reapply the move-back from before the collision (using the post collision velocity)
Point2D.Double object1AdjustedPositionAfterCollision = new Point2D.Double(balls[i].getX() + vel1_after.x * millisecondsAfterCollision, balls[i].getY() + vel1_after.y * millisecondsAfterCollision);
Point2D.Double object2AdjustedPositionAfterCollision = new Point2D.Double(balls[j].getX() + vel2_after.x * millisecondsAfterCollision, balls[j].getY() + vel2_after.y * millisecondsAfterCollision);
//set the objects new positions and velocities.
balls[i].setX(object1AdjustedPositionAfterCollision.x);
balls[i].setY(object1AdjustedPositionAfterCollision.y);
balls[j].setX(object2AdjustedPositionAfterCollision.x);
balls[j].setY(object2AdjustedPositionAfterCollision.y);
balls[i].setVX(vel1_after.x);
balls[i].setVY(vel1_after.y);
balls[j].setVX(vel2_after.x);
balls[j].setVY(vel2_after.y);
}
}
}
}
private double moveBackToCollisionPoint(Ball object1, Ball object2, double distanceAtFrameEnd, double collisionDistance){
//calc the position at the start of the frame.
double object1PosAtFrameStart_X = (object1.getX() - object1.getVX() * (double)UPDATE_INTERVAL);
double object1PosAtFrameStart_Y = (double)(object1.getY() - object1.getVY() * (double)UPDATE_INTERVAL);
Point2D.Double object1PosAtFrameStart = new Point2D.Double(object1PosAtFrameStart_X, object1PosAtFrameStart_Y);
double object2PosAtFrameStart_X = (object2.getX() - object2.getVX() * (double)UPDATE_INTERVAL);
double object2PosAtFrameStart_Y = (object2.getY() - object2.getVY() * (double)UPDATE_INTERVAL);
Point2D.Double object2PosAtFrameStart = new Point2D.Double(object2PosAtFrameStart_X, object2PosAtFrameStart_Y);
//calc the distance between the objects at the start of the frame.
Point2D.Double differenceAtFrameStart = new Point2D.Double(object2PosAtFrameStart.x - object1PosAtFrameStart.x, object2PosAtFrameStart.y - object1PosAtFrameStart.y);
double distanceAtFrameStart = Math.sqrt(differenceAtFrameStart.x*differenceAtFrameStart.x + differenceAtFrameStart.y*differenceAtFrameStart.y);
//calculate the total distance change during the frame and the required change to reach the collision.
double distanceTotalDelta = distanceAtFrameEnd - distanceAtFrameStart;
double distanceDeltaToCollision = collisionDistance - distanceAtFrameStart;
// Calculate the percentage change to the collision and after the collision.
double percentageDeltaToCollision = distanceDeltaToCollision / distanceTotalDelta;
double percentageDeltaAfterCollision = 1 - percentageDeltaToCollision;
// Calculate the time before and after the collision in the frame.
double millisecondsToCollision = (double)UPDATE_INTERVAL * percentageDeltaToCollision;
double millisecondsAfterCollision = (double)UPDATE_INTERVAL * percentageDeltaAfterCollision;
// Calculate and move the objects to their positions at the point of collision.
double object1PosAtCollision_X = (object1PosAtFrameStart_X + object1.getVX() * millisecondsToCollision);
double object1PosAtCollision_Y = (object1PosAtFrameStart_Y + object1.getVY() * millisecondsToCollision);
Point2D.Double object1PosAtCollision = new Point2D.Double(object1PosAtCollision_X, object1PosAtCollision_Y);
object1.setX(object1PosAtCollision.x);
object1.setY(object1PosAtCollision.y);
double object2PosAtCollision_X = (object2PosAtFrameStart_X + object2.getVX() * millisecondsToCollision);
double object2PosAtCollision_Y = (object2PosAtFrameStart_Y + object2.getVY() * millisecondsToCollision);
Point2D.Double object2PosAtCollision = new Point2D.Double(object2PosAtCollision_X, object2PosAtCollision_Y);
object2.setX(object2PosAtCollision.x);
object2.setY(object2PosAtCollision.y);
return millisecondsAfterCollision;
}
private void updateBalls(){
leftCountR = 0; leftCountB = 0; rightCountR = 0; rightCountB = 0;
for(int i=0; i<BALLS_COUNT; i++){
balls[i].update(UPDATE_INTERVAL, DIMS);
if(balls[i].c == Color.blue){
if(balls[i].locOnScreen.x < DIMS.width/2){
leftCountB++;
}else{
rightCountB++;
}
}else{
if(balls[i].locOnScreen.x < DIMS.width/2){
leftCountR++;
}else{
rightCountR++;
}
}
}
}
private void registerTimer(){
timer = new Timer(UPDATE_INTERVAL, new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
update();
FRAME.repaint();
}
});
timer.setRepeats(true);
timer.setDelay(UPDATE_INTERVAL);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
for(int i=0; i<BALLS_COUNT; i++){
g2d.setColor(balls[i].c);
g2d.fillOval(balls[i].locOnScreen.x-Ball.radius, balls[i].locOnScreen.y-Ball.radius, Ball.radius*2, Ball.radius*2);
}
}
public static void main(String[] args){
new MaxwellsDemon();
}
}
和Ball类:
package letifer.com;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.geom.Point2D;
public class Ball{
public static int radius = 7;
private Point2D.Double loc;
private double speed;
private double dir;
Point locOnScreen;
Color c;
public Ball(Point2D.Double loc, double speed, double dir, Color c){
this.loc = loc;
this.speed = speed;
this.dir = dir;
this.c = c;
this.locOnScreen = new Point((int)(loc.x+0.5f), (int)(loc.y+0.5f));
}
public void update(int timePassed, Dimension dims){
loc.x = loc.x + speed * timePassed * Math.cos(dir);
loc.y = loc.y + speed * timePassed * Math.sin(dir);
if(loc.x <= 0 + radius){
dir = (-dir+Math.PI+2*Math.PI)%(2*Math.PI);
}
if(loc.x >= dims.width - radius - 1){
dir = (-dir+Math.PI+2*Math.PI)%(2*Math.PI);
}
if(loc.y <= 0 + radius){
dir = (-dir+2*Math.PI)%(2*Math.PI);
}
if(loc.y >= dims.height - radius - 1){
dir = (-dir+2*Math.PI)%(2*Math.PI);
}
locOnScreen.setLocation((int)(loc.x+0.5f), (int)(loc.y+0.5f));
}
public double getX(){
return loc.x;
}
public double getY(){
return loc.y;
}
public void setX(double x){
loc.x = x;
}
public void setY(double y){
loc.y = y;
}
public double getVX(){
return speed * Math.cos(dir);
}
public double getVY(){
return speed * Math.sin(dir);
}
public void setVX(double vx){
double vy = getVY();
speed = Math.sqrt(vy*vy+vx*vx);
dir = Math.atan2(vx, vy);
}
public void setVY(double vy){
double vx = getVX();
speed = Math.sqrt(vy*vy+vx*vx);
dir = Math.atan2(vx, vy);
}
}
PS:我不是一个java编码器,所以任何关于好/坏编码实践的注释都会受到赞赏。
答案 0 :(得分:1)
设u i 并且v i 是当前时间步之前和之后的圆i的位置,假设i = 1,2分别没有碰撞。让r i 是圆i的半径,i = 1,2。设R = r 1 + r 2 。
假设圆圈在时间步长期间以恒定速度移动。也就是说,球i的路径由p i (t)=(1-t)u i + tv i 描述,t在[0,1]中,假设没有碰撞。
我们可以通过求解方程检测碰撞|| p 2 (t) - p 1 (t)|| = r 1 + r 2 = R代表t。让我们简化这个等式。
|| p 2 (t) - p 1 (t)|| = R
||(1-t)(u 2 - u 1 )+ t(v 2 - v 1 < / sub>)|| 2 = R 2
||(1 - t)U + tV || 2 = R 2 其中U = u 2 - u 1 和V = v 2 - v 1
&lt;(1-t)U + tV,(1-t)U + tV&gt; = R 2
(1-t)&lt; U,(1-t)U + tV&gt; + t&lt; V,(1-t)U + tV&gt; = R 2
(1-t) 2 &lt; U,U&gt; +(1-t)t&lt; U,V&gt; + t(1-t)&lt; V,U&gt; + t 2 &lt; V,V&gt; = R 2
(1-t) 2 &lt; U,U&gt; + 2(1-t)t&lt; U,V&gt; + t 2 &lt; V,V&gt; = R 2
此时应该清楚:这只是t中的二次方程式!解决t的可能值。如果区间[0,1]中没有真正的解,那么就没有碰撞。如果区间[0,1]中有任何实际解,则区间[0,1]中最早的(最小)解决方案描述了碰撞时间。
令T为区间[0,1]中描述的最早的实解。然后p 1 (T)和p 2 (T)是碰撞时的位置。这些位置之间的向量是碰撞的法线。