Java 如何正确检查两个移动椭圆(圆)之间的碰撞并更新其位置+;速度
好的,所以我试着写一个简单的碰撞检测两个移动的椭圆(圆)和更新他们的位置和速度使用我找到的指南。 然而,我得到的结果不是很完美,对象正在碰撞和更新,但并不总是正确的,它们有时会纠结在一起,我很沮丧,因为我不明白为什么会发生这种情况。Java 如何正确检查两个移动椭圆(圆)之间的碰撞并更新其位置+;速度,java,math,collision-detection,collision,game-physics,Java,Math,Collision Detection,Collision,Game Physics,好的,所以我试着写一个简单的碰撞检测两个移动的椭圆(圆)和更新他们的位置和速度使用我找到的指南。 然而,我得到的结果不是很完美,对象正在碰撞和更新,但并不总是正确的,它们有时会纠结在一起,我很沮丧,因为我不明白为什么会发生这种情况。 注意:请忽略对象与边界的碰撞,它们有时会卡住(我需要更改,这不是我遇到的问题) 以下是我使用的两个类的代码: package letifer.com; import java.awt.Color; import java.awt.Container; import
注意:请忽略对象与边界的碰撞,它们有时会卡住(我需要更改,这不是我遇到的问题) 以下是我使用的两个类的代码:
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();
}
}
package letifer.com;
导入java.awt.Color;
导入java.awt.Container;
导入java.awt.Dimension;
导入java.awt.Font;
导入java.awt.Graphics;
导入java.awt.Graphics2D;
导入java.awt.RenderingHints;
导入java.awt.event.ActionEvent;
导入java.awt.event.ActionListener;
导入java.awt.geom.Point2D;
导入java.util.Random;
导入javax.swing.JFrame;
导入javax.swing.JPanel;
导入javax.swing.Timer;
公共类MaxwellsDemon扩展了JPanel{
公共静态最终长serialVersionUID=1L;
专用最终尺寸标注DIMS=新尺寸(800500);
私人最终整数更新间隔=20,球数=20;
专用最终JFrame=新JFrame(“MaxwellsDemon”);
private int leftCountR、leftCountB、righCountr、righCountb;
私人舞会[];
私有随机;
私人定时器;
公共MaxwellsDemon(){
超级();
initThis();
initFrame();
initBalls();
注册表项();
timer.start();
}
private void initThis(){
设置大小(DIMS);
挫折地面(颜色:白色);
随机=新随机();
}
私有void initFrame(){
FRAME.setContentPane(新容器(){
公共空间涂料(图g){
超级油漆(g);
Graphics2D g2d=(Graphics2D)g.create();
g2d.SetRenderingHists(新的RenderingHists(RenderingHists.KEY_ANTIALIASING,RenderingHists.VALUE_ANTIALIAS_ON));
对于(int i=0;i假设i=1,2没有碰撞,则ui和vi分别为当前时间步之前和之后的圆i的位置。对于i=1,2,ri为圆i的半径。R=r1+r2
假设圆在时间步长内以恒定速度移动。也就是说,球i的路径由pi(t)=(1-t)ui+tvi,t在[0,1]中描述,假设没有碰撞
我们可以通过求解方程| | p2(t)-p1(t)| |=r1+r2=R来检测碰撞。让我们简化这个方程
||p2(t)-p1(t)| |=R
||(1-t)(u2-u1)+t(v2-v1)| 2=R2
||(1-t)U+tV | | 2=R2,其中U=u2-u1和V=v2-v1
=R2
(1-t)+t=R2
(1-t)2+(1-t)t+t(1-t)+t2=R2
(1-t)2+2(1-t)t+t2=R2
在这一点上,应该很清楚:这只是t!中的一个二次方程,求t的可能值。如果区间[0,1]中没有实解,则不存在碰撞。如果区间[0,1]中有任何实解,则区间[0,1]中的最早(最小)解描述了碰撞时间
设T为区间[0,1]中描述的最早实解,然后为p1(T)和p2(T)是碰撞时的位置。这些位置之间的向量是碰撞的法线。你是指圆还是椭圆?@CodesInChaos我指的是圆。所以基本上你是想说我对碰撞时间的计算是错误的?这就是为什么物体会纠结在一起?@dimaligin好吧,看看你的代码和fi弄清楚里面发生了什么并不容易。这是如何确定圆碰撞的数学理论。我认为你所采取的方法更多的是,在每一个时间步,检查彼此之间是否有圆(相交)然后将它们分开。这种方法会有稳定性问题。这种方法从一开始就不会让圆彼此相交。它假设在每个时间步的持续时间内呈线性,然后解决精确的碰撞时刻。我想你建议基本上模拟下一个时间点的时间路径(我真正想做的事)在绘制int之前,但在这种情况下,它会对性能产生相当大的影响,这是我希望避免的。@DimaAligin这种方法可以比在相交时将事物分开的方法表现得更好,实际上……如果不太麻烦的话,你能分享一个实现模拟的示例的链接吗?我想不出一个没有性能损失的实现方法。
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);
}
}