物体 运行 相互穿过
Objects running through each other
我正在 java 使用本教程开发二维物理引擎:https://gamedevelopment.tutsplus.com/tutorials/how-to-create-a-custom-2d-physics-engine-the-basics-and-impulse-resolution--gamedev-6331
问题是,有时当一个盒子通过一个角与球碰撞时,盒子会穿过它,有时当球很小时,它会被困在盒子里。我认为 CollisionAABBCircle.java 文件中的穿透计算存在一些精度错误。这是两个显示正在发生的事情的 gif。
这里最小的球来自顶部并被困住。
这里有个盒子穿过最大的球
代码如下:
GUI.java
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
import physics.ImpulseScene;
import physics.Point;
import sprite.Ball;
import sprite.Box;
public class GUI extends Application {
public static final int WIDTH = 800;
public static final int HEIGHT = 500;
public static GraphicsContext gc;
@Override
public void start(Stage stage) throws Exception {
Canvas canvas = new Canvas(WIDTH, HEIGHT);
gc = canvas.getGraphicsContext2D();
stage.setScene(new Scene(new StackPane(canvas)));
stage.setTitle("BOUNCE BOUNCE BOUNCE");
stage.show();
Ball ball1 = new Ball(10, new Point(20, 20));
ball1.gc = gc;
ball1.color = Color.BLACK;
ball1.setMass(10);
ball1.getVelocity().setXComponent(1);
ball1.getVelocity().setYComponent(1);
Ball ball2 = new Ball(20, new Point(100, 100));
ball2.gc = gc;
ball2.color = Color.BLACK;
ball2.setMass(20);
ball2.getVelocity().setXComponent(1);
ball2.getVelocity().setYComponent(-2);
Ball ball3 = new Ball(30, new Point(250, 250));
ball3.gc = gc;
ball3.color = Color.BLACK;
ball3.setMass(30);
ball3.getVelocity().setXComponent(3);
ball3.getVelocity().setYComponent(-2);
Ball ball4 = new Ball(40, new Point(400, 400));
ball4.gc = gc;
ball4.color = Color.BLACK;
ball4.setMass(40);
ball4.getVelocity().setXComponent(2);
ball4.getVelocity().setYComponent(0);
Ball ball5 = new Ball(50, new Point(100, 700));
ball5.gc = gc;
ball5.color = Color.BLACK;
ball5.setMass(50);
ball5.getVelocity().setXComponent(2);
ball5.getVelocity().setYComponent(3);
Ball ball6 = new Ball(60, new Point(400, 100));
ball6.gc = gc;
ball6.color = Color.BLACK;
ball6.setMass(60);
ball6.getVelocity().setXComponent(0);
ball6.getVelocity().setYComponent(0);
Ball ball7 = new Ball(70, new Point(340, 340));
ball7.gc = gc;
ball7.color = Color.BLACK;
ball7.setMass(70);
ball7.getVelocity().setXComponent(-1);
ball7.getVelocity().setYComponent(2);
Box box1 = new Box(new Point(80, 80), 50, 50);
box1.gc = gc;
box1.color = Color.BROWN;
box1.setMass(10);
box1.getVelocity().setXComponent(3);
box1.getVelocity().setYComponent(2);
Box box2 = new Box(new Point(180, 180), 60, 60);
box2.gc = gc;
box2.color = Color.BROWN;
box2.setMass(10);
box2.getVelocity().setXComponent(4);
box2.getVelocity().setYComponent(-4);
Box box3 = new Box(new Point(280, 280), 60, 60);
box3.gc = gc;
box3.color = Color.BROWN;
box3.setMass(10);
box3.getVelocity().setXComponent(4);
box3.getVelocity().setYComponent(-4);
Box box4 = new Box(new Point(380, 380), 60, 60);
box4.gc = gc;
box4.color = Color.BROWN;
box4.setMass(10);
box4.getVelocity().setXComponent(4);
box4.getVelocity().setYComponent(-4);
ImpulseScene scene = new ImpulseScene(10);
scene.bodies.add(ball1);
scene.bodies.add(ball2);
scene.bodies.add(ball3);
scene.bodies.add(ball4);
scene.bodies.add(ball5);
scene.bodies.add(ball6);
scene.bodies.add(ball7);
scene.bodies.add(box1);
scene.bodies.add(box2);
scene.bodies.add(box3);
scene.bodies.add(box4);
Bounds bounds = canvas.getBoundsInLocal();
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(16), e -> {
gc.setFill(Color.WHITESMOKE);
gc.fillRect(0, 0, WIDTH, HEIGHT);
ball1.update(bounds);
ball2.update(bounds);
ball3.update(bounds);
ball4.update(bounds);
ball5.update(bounds);
ball6.update(bounds);
ball7.update(bounds);
box1.update(bounds);
box2.update(bounds);
box3.update(bounds);
box4.update(bounds);
scene.step();
}));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
Ball.java
package sprite;
import javafx.geometry.Bounds;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import physics.Circle;
import physics.Point;
import physics.Vector;
public class Ball extends Circle {
public static Color color;
private double height;
private double width;
public GraphicsContext gc;
public Ball(long radius, Point position) {
super(radius, position);
this.setHeight(2 * radius);
this.setWidth(2 * radius);
}
public void update(Bounds bounds) {
Point position = getPosition();
Vector velocity = getVelocity();
if (position.getX() + velocity.getXComponent() < getRadius()
|| position.getX() + velocity.getXComponent() > bounds.getMaxX() - getRadius()) {
velocity.setXComponent(velocity.getXComponent() * (-1));
}
if (position.getY() + velocity.getYComponent() < getRadius()
|| position.getY() + velocity.getYComponent() > bounds.getMaxY() - getRadius()) {
velocity.setYComponent(velocity.getYComponent() * (-1));
}
double px = position.getX() + velocity.getXComponent();
double py = position.getY() + velocity.getYComponent();
setPosition(new Point(px, py));
//System.out.println(getPosition());
}
public void render() {
if (gc != null && color != null) {
//System.out.println("Rendering");
gc.setFill(color);
gc.fillOval(getPosition().getX()-getRadius(), getPosition().getY()
- getRadius(), 2*getRadius(), 2*getRadius());
}
}
@Override
public void setPosition(Point p) {
super.setPosition(p);
render();
}
private void setWidth(double l) {
this.width = l;
}
private void setHeight(double l) {
this.height = l;
}
}
Box.java
package sprite;
import javax.swing.text.Position;
import javafx.geometry.Bounds;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import physics.AABB;
import physics.Point;
import physics.Vector;
public class Box extends AABB {
private double height;
private double width;
private double halfHeight;
private double halfWidth;
public Color color;
public GraphicsContext gc;
public Box(Point min, Point max) {
super(min, max);
this.setHeight(getMin().getY() - getMax().getY());
this.setWidth(getMax().getX() - getMin().getX());
}
public Box(Point position, double height, double width) {
super(position, height, width);
this.setHeight(getMin().getY() - getMax().getY());
this.setWidth(getMax().getX() - getMin().getX());
}
public void update(Bounds bound) {
Point position = getPosition();
Vector velocity = getVelocity();
double px = position.getX() - (this.halfWidth);
double py = position.getY() - (this.halfHeight);
if (px + velocity.getXComponent() < 0 || px + velocity.getXComponent() + width > bound.getMaxX()) {
velocity.setXComponent(velocity.getXComponent() * (-1));
}
if (py + velocity.getYComponent() < 0 || py + velocity.getYComponent() + height > bound.getMaxY()) {
velocity.setYComponent(velocity.getYComponent() * (-1));
}
px += velocity.getXComponent();
py += velocity.getYComponent();
getMin().setX(px);
getMin().setY(py + height);
getMax().setX(px + width);
getMax().setY(py);
updatePosition();
}
@Override
public void setPosition(Point p) {
super.setPosition(p);
render();
}
private void render() {
if (color!=null && gc!=null) {
gc.setFill(color);
gc.fillRect(getPosition().getX() - this.halfWidth, getPosition().getY() - this.halfHeight, this.width, this.height);
}
}
public void setHeight(double height) {
this.height = height;
this.halfHeight = height / 2.0d;
}
public void setWidth(double width) {
this.width = width;
this.halfWidth = width / 2.0d;
}
}
Circle.java
package physics;
public class Circle extends Shape {
private long radius;
public Circle(long radius, Point position) {
this.radius = radius;
this.setPosition(position);
this.setVelocity(new Vector(new Point(0.0d, 0.0d), new Point(0, 0)));
this.setMass(0);
this.setRestitution(1);
this.setAcceleration(new Vector(new Point(0, 0), new Point(0, 0)));
this.setType(Type.Circle);
}
public long getRadius() {
return this.radius;
}
public void setRadius(long radius) {
this.radius = radius;
}
}
AABB.java
package physics;
public class AABB extends Shape {
private Point min;
private Point max;
public AABB(Point min, Point max) {
this.min = min;
this.max = max;
this.setVelocity(new Vector(new Point(0, 0), new Point(0, 0)));
this.setMass(0);
this.setRestitution(1);
this.setType(Type.AABB);
updatePosition();
}
public AABB(Point position, double height, double width) throws IllegalArgumentException {
double minX;
double minY;
double maxX;
double maxY;
minX = position.getX() - (width / 2.0d);
minY = position.getY() + (height / 2.0d);
maxX = position.getX() + (width / 2.0d);
maxY = position.getY() - (height / 2.0d);
if (minX < 0 || minY < height) {
throw new IllegalArgumentException("Inappropriate position");
}
Point min = new Point(minX, minY);
Point max = new Point(maxX, maxY);
this.min = min;
this.max = max;
this.setVelocity(new Vector(new Point(0, 0), new Point(0, 0)));
this.setMass(0);
this.setRestitution(1);
this.setType(Type.AABB);
setPosition(position);
}
public void updatePosition() {
setPosition(new Point((min.getX() + max.getX()) / 2, (min.getY() + max.getY()) / 2));
}
public Point getMin() {
return this.min;
}
public Point getMax() {
return this.max;
}
}
Shape.java
package physics;
public class Shape {
private Vector velocity;
private Vector acceleration;
private long mass;
private double invMass;
private float restitution;
private Type type;
private Point position;
public enum Type {
Circle, AABB, count
}
public Vector getAcceleration() {
return this.acceleration;
}
public void setAcceleration(Vector acceleration) {
this.acceleration = acceleration;
}
public long getMass() {
return this.mass;
}
public void setMass(long mass) {
this.mass = mass;
this.setInvMass(mass);
}
public Vector getVelocity() {
return this.velocity;
}
public void setVelocity(Vector velocity) {
this.velocity = velocity;
}
public double getInvMass() {
return this.invMass;
}
private void setInvMass(long mass) {
if (mass == 0) {
invMass = Long.MAX_VALUE;
return;
}
this.invMass = 1.0d / (double) mass;
}
public float getRestitution() {
return this.restitution;
}
public void setRestitution(float d) {
this.restitution = d;
}
public Type getType() {
return this.type;
}
public void setType(Type type) {
this.type = type;
}
public Point getPosition() {
return this.position;
}
public void setPosition(Point position) {
this.position = position;
}
}
Vector.java
封装物理;
public class Vector {
private Point p1;
private Point p2;
private double xComponent;
private double yComponent;
private double angle;
private double magnitude;
public Vector(Point p1, Point p2) {
this.p1 = p1;
this.p2 = p2;
this.xComponent = this.p2.getX() - this.p1.getX();
this.yComponent = this.p2.getY() - this.p1.getY();
this.angle = Math.atan2(this.yComponent, this.xComponent);
this.magnitude = Math.sqrt(this.xComponent * this.xComponent + this.yComponent * this.yComponent);
}
public Vector(Point p2) {
Point p1 = new Point(0, 0);
this.p1 = p1;
this.p2 = p2;
this.xComponent = this.p2.getX() - this.p1.getX();
this.yComponent = this.p2.getY() - this.p1.getY();
this.angle = Math.atan2(this.yComponent, this.xComponent);
this.magnitude = Math.sqrt(this.xComponent * this.xComponent + this.yComponent * this.yComponent);
}
public Vector(double magnitude, Vector unitVector) {
scaledProduct(magnitude, unitVector);
}
public Vector() {
}
private void scaledProduct(double magnitude, Vector unitVector) {
Point point = new Point(magnitude * unitVector.getXComponent(), magnitude * unitVector.getYComponent());
new Vector(point);
}
public static Vector scalarProduct(double magnitude, Vector unitVector) {
Point point = new Point(magnitude * unitVector.getXComponent(), magnitude * unitVector.getYComponent());
return new Vector(point);
}
public static double dotProduct(Vector v1, Vector v2) {
return (v1.xComponent * v2.xComponent + v1.yComponent * v2.yComponent);
}
public static Vector sum(Vector v1, Vector v2) {
return new Vector(new Point(v1.getXComponent() + v2.getXComponent(), v1.getYComponent() + v2.getYComponent()));
}
public static Vector difference(Vector from, Vector vector) {
return new Vector(new Point(from.getXComponent() - vector.getXComponent(),
from.getYComponent() - vector.getYComponent()));
}
public static Vector scalarDivision(Vector vector, double by) {
return new Vector(new Point(vector.getXComponent() / by, vector.getYComponent() / by));
}
public static double angleBetween(Vector v1, Vector v2) {
return Math.acos(Vector.dotProduct(v1, v2) / (v1.getMagnitude() * v2.getMagnitude()));
}
public double getXComponent() {
return this.xComponent;
}
public void setXComponent(double d) {
this.xComponent = d;
update();
}
public double getYComponent() {
return this.yComponent;
}
public void setYComponent(double d) {
this.yComponent = d;
update();
}
public double getAngle() {
return this.angle;
}
public void setAngle(double angle) {
this.angle = angle;
update();
}
public double getMagnitude() {
return this.magnitude;
}
public void setMagnitude(double length) {
this.magnitude = length;
}
public void update() {
this.angle = Math.atan2(this.yComponent, this.xComponent);
this.magnitude = Math.sqrt(this.xComponent * this.xComponent + this.yComponent * this.yComponent);
}
@Override
public boolean equals(Object v) {
Vector vector = (Vector) v;
return (ImpulseMath.equal(this.xComponent, vector.xComponent)
&& ImpulseMath.equal(this.yComponent, vector.yComponent));
}
}
Point.java
package physics;
public class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public static double distance(Point p1, Point p2) {
return Math.sqrt(
(p1.getX() - p2.getX()) * (p1.getX() - p2.getX()) + (p1.getY() - p2.getY()) * (p1.getY() - p2.getY()));
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
}
ImpulseScene.java
package physics;
import java.util.ArrayList;
public class ImpulseScene {
public double dt;
public int iterations;
public ArrayList<Shape> bodies = new ArrayList<>();
public ArrayList<Manifold> contacts = new ArrayList<>();
public ImpulseScene(int iterations) {
this.iterations = iterations;
}
public void step() {
contacts.clear();
for (int i = 0; i < bodies.size(); ++i) {
Shape A = bodies.get(i);
for (int j = i + 1; j < bodies.size(); ++j) {
Shape B = bodies.get(j);
if (A.getInvMass() == 0 && B.getInvMass() == 0) {
continue;
}
Manifold m = new Manifold(A, B);
m.solve();
if (m.contactCount > 0) {
contacts.add(m);
}
}
}
for (int i = 0; i < contacts.size(); ++i) {
contacts.get(i).initialize();
}
for (int j = 0; j < iterations; ++j) {
for (int i = 0; i < contacts.size(); ++i) {
contacts.get(i).applyImpulse();
}
}
for (int i = 0; i < contacts.size(); ++i) {
contacts.get(i).positionalCorrection();
}
}
public void clear() {
contacts.clear();
bodies.clear();
}
}
Collision.java
package physics;
public class Collision {
public static CollisionCallback dispatch[][] = { { CollisionCircleCircle.instance, CollisionCircleAABB.instance },
{ CollisionAABBCircle.instance, CollisionAABBAABB.instance } };
}
CollisionCallback.java
package physics;
public interface CollisionCallback {
public void resolveCollision(Shape a, Shape b, Manifold manifold);
}
碰撞圆AABB.java
package physics;
public class CollisionCircleAABB implements CollisionCallback {
public static final CollisionCircleAABB instance = new CollisionCircleAABB();
@Override
public void resolveCollision(Shape a, Shape b, Manifold manifold) {
CollisionAABBCircle.instance.resolveCollision(b, a, manifold);
if (manifold.contactCount>0) {
manifold.normal.setXComponent(-manifold.normal.getXComponent());
manifold.normal.setYComponent(-manifold.normal.getYComponent());
}
}
}
CollisionAABBCircle.java
package physics;
public class CollisionAABBCircle implements CollisionCallback {
public static final CollisionAABBCircle instance = new CollisionAABBCircle();
@Override
public void resolveCollision(Shape shape1, Shape shape2, Manifold manifold) {
AABB a = (AABB) shape1;
Circle c = (Circle) shape2;
Vector normal = Vector.difference(new Vector(c.getPosition()), new Vector(a.getPosition()));
Vector closest = new Vector(new Point(normal.getXComponent(), normal.getYComponent()));
double xExtent = (a.getMax().getX() - a.getMin().getX()) / 2.0d;
double yExtent = (a.getMin().getY() - a.getMax().getY()) / 2.0d;
closest.setXComponent(ImpulseMath.clamp(-xExtent, xExtent, closest.getXComponent()));
closest.setYComponent(ImpulseMath.clamp(-yExtent, yExtent, closest.getYComponent()));
boolean inside = false;
if (normal.equals(closest)) {
inside = true;
if (Math.abs(normal.getXComponent()) < Math.abs(normal.getYComponent())) {
if (closest.getXComponent() > 0) {
closest.setXComponent(xExtent);
} else {
closest.setXComponent(-xExtent);
}
} else {
if (closest.getYComponent() > 0) {
closest.setYComponent(yExtent);
} else {
closest.setYComponent(-yExtent);
}
}
}
Vector n = Vector.difference(normal, closest);
double d = n.getMagnitude();
double r = c.getRadius();
if (d > r && !inside) {
manifold.contactCount = 0;
return;
}
manifold.contactCount = 1;
if (inside) {
manifold.normal.setXComponent((-normal.getXComponent()) / normal.getMagnitude());
manifold.normal.setYComponent((-normal.getYComponent()) / normal.getMagnitude());
} else {
manifold.normal.setXComponent((normal.getXComponent()) / normal.getMagnitude());
manifold.normal.setYComponent((normal.getYComponent()) / normal.getMagnitude());
}
manifold.penetration = r - d;
}
}
Manifold.java
package physics;
public class Manifold {
public Shape a;
public Shape b;
public double penetration;
public final Vector normal = new Vector(new Point(0.0d, 0.0d));
public int contactCount;
public double e;
public Manifold(Shape a, Shape b) {
this.a = a;
this.b = b;
this.contactCount = 0;
}
public void solve() {
int ia = a.getType().ordinal();
int ib = b.getType().ordinal();
Collision.dispatch[ia][ib].resolveCollision(a, b, this);
}
public void initialize() {
e = Math.min(a.getRestitution(), b.getRestitution());
}
public void applyImpulse() {
Vector relativeVelocity = Vector.difference(b.getVelocity(), a.getVelocity());
double velocityAlongNormal = Vector.dotProduct(relativeVelocity, this.normal);
if (velocityAlongNormal > 0) {
return;
}
double restitution = Math.min(a.getRestitution(), b.getRestitution());
double j = -(1 + restitution) * velocityAlongNormal;
j /= (a.getInvMass() + b.getInvMass());
Vector impulse = Vector.scalarProduct(j, this.normal);
a.setVelocity(Vector.difference(a.getVelocity(), Vector.scalarProduct(a.getInvMass(), impulse)));
b.setVelocity(Vector.sum(b.getVelocity(), Vector.scalarProduct(b.getInvMass(), impulse)));
}
public void positionalCorrection() {
double correction = Math.max(this.penetration - ImpulseMath.PENETRATION_ALLOWANCE, 0.0d)
/ (a.getInvMass() + b.getInvMass()) * ImpulseMath.PENETRATION_CORRETION;
Point posA = a.getPosition();
Point posB = b.getPosition();
Point newPosA = new Point(posA.getX() - (correction * a.getInvMass()),
posA.getY() - (correction * a.getInvMass()));
Point newPosB = new Point(posB.getX() + (correction * b.getInvMass()),
posB.getY() + (correction * b.getInvMass()));
a.setPosition(newPosA);
b.setPosition(newPosB);
}
}
我想不出发生这种情况的任何原因。请帮帮我,我没主意了。
问题已解决。原来教程网站上的代码是错误的。在CollisionAABBCircle.java文件中最后一个if条件,应该是n不正常。它解决了这个问题,因为球在盒子里面,法线应该是从球的中心指向边缘上最近点的向量。更改后最终代码将是:
if (inside) {
manifold.normal.setXComponent((-n.getXComponent()) / n.getMagnitude());
manifold.normal.setYComponent((-n.getYComponent()) / n.getMagnitude());
} else {
manifold.normal.setXComponent((normal.getXComponent()) / normal.getMagnitude());
manifold.normal.setYComponent((normal.getYComponent()) / normal.getMagnitude());
}
我正在 java 使用本教程开发二维物理引擎:https://gamedevelopment.tutsplus.com/tutorials/how-to-create-a-custom-2d-physics-engine-the-basics-and-impulse-resolution--gamedev-6331
问题是,有时当一个盒子通过一个角与球碰撞时,盒子会穿过它,有时当球很小时,它会被困在盒子里。我认为 CollisionAABBCircle.java 文件中的穿透计算存在一些精度错误。这是两个显示正在发生的事情的 gif。
这里最小的球来自顶部并被困住。
这里有个盒子穿过最大的球
代码如下:
GUI.java
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
import physics.ImpulseScene;
import physics.Point;
import sprite.Ball;
import sprite.Box;
public class GUI extends Application {
public static final int WIDTH = 800;
public static final int HEIGHT = 500;
public static GraphicsContext gc;
@Override
public void start(Stage stage) throws Exception {
Canvas canvas = new Canvas(WIDTH, HEIGHT);
gc = canvas.getGraphicsContext2D();
stage.setScene(new Scene(new StackPane(canvas)));
stage.setTitle("BOUNCE BOUNCE BOUNCE");
stage.show();
Ball ball1 = new Ball(10, new Point(20, 20));
ball1.gc = gc;
ball1.color = Color.BLACK;
ball1.setMass(10);
ball1.getVelocity().setXComponent(1);
ball1.getVelocity().setYComponent(1);
Ball ball2 = new Ball(20, new Point(100, 100));
ball2.gc = gc;
ball2.color = Color.BLACK;
ball2.setMass(20);
ball2.getVelocity().setXComponent(1);
ball2.getVelocity().setYComponent(-2);
Ball ball3 = new Ball(30, new Point(250, 250));
ball3.gc = gc;
ball3.color = Color.BLACK;
ball3.setMass(30);
ball3.getVelocity().setXComponent(3);
ball3.getVelocity().setYComponent(-2);
Ball ball4 = new Ball(40, new Point(400, 400));
ball4.gc = gc;
ball4.color = Color.BLACK;
ball4.setMass(40);
ball4.getVelocity().setXComponent(2);
ball4.getVelocity().setYComponent(0);
Ball ball5 = new Ball(50, new Point(100, 700));
ball5.gc = gc;
ball5.color = Color.BLACK;
ball5.setMass(50);
ball5.getVelocity().setXComponent(2);
ball5.getVelocity().setYComponent(3);
Ball ball6 = new Ball(60, new Point(400, 100));
ball6.gc = gc;
ball6.color = Color.BLACK;
ball6.setMass(60);
ball6.getVelocity().setXComponent(0);
ball6.getVelocity().setYComponent(0);
Ball ball7 = new Ball(70, new Point(340, 340));
ball7.gc = gc;
ball7.color = Color.BLACK;
ball7.setMass(70);
ball7.getVelocity().setXComponent(-1);
ball7.getVelocity().setYComponent(2);
Box box1 = new Box(new Point(80, 80), 50, 50);
box1.gc = gc;
box1.color = Color.BROWN;
box1.setMass(10);
box1.getVelocity().setXComponent(3);
box1.getVelocity().setYComponent(2);
Box box2 = new Box(new Point(180, 180), 60, 60);
box2.gc = gc;
box2.color = Color.BROWN;
box2.setMass(10);
box2.getVelocity().setXComponent(4);
box2.getVelocity().setYComponent(-4);
Box box3 = new Box(new Point(280, 280), 60, 60);
box3.gc = gc;
box3.color = Color.BROWN;
box3.setMass(10);
box3.getVelocity().setXComponent(4);
box3.getVelocity().setYComponent(-4);
Box box4 = new Box(new Point(380, 380), 60, 60);
box4.gc = gc;
box4.color = Color.BROWN;
box4.setMass(10);
box4.getVelocity().setXComponent(4);
box4.getVelocity().setYComponent(-4);
ImpulseScene scene = new ImpulseScene(10);
scene.bodies.add(ball1);
scene.bodies.add(ball2);
scene.bodies.add(ball3);
scene.bodies.add(ball4);
scene.bodies.add(ball5);
scene.bodies.add(ball6);
scene.bodies.add(ball7);
scene.bodies.add(box1);
scene.bodies.add(box2);
scene.bodies.add(box3);
scene.bodies.add(box4);
Bounds bounds = canvas.getBoundsInLocal();
Timeline timeline = new Timeline(new KeyFrame(Duration.millis(16), e -> {
gc.setFill(Color.WHITESMOKE);
gc.fillRect(0, 0, WIDTH, HEIGHT);
ball1.update(bounds);
ball2.update(bounds);
ball3.update(bounds);
ball4.update(bounds);
ball5.update(bounds);
ball6.update(bounds);
ball7.update(bounds);
box1.update(bounds);
box2.update(bounds);
box3.update(bounds);
box4.update(bounds);
scene.step();
}));
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
Ball.java
package sprite;
import javafx.geometry.Bounds;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import physics.Circle;
import physics.Point;
import physics.Vector;
public class Ball extends Circle {
public static Color color;
private double height;
private double width;
public GraphicsContext gc;
public Ball(long radius, Point position) {
super(radius, position);
this.setHeight(2 * radius);
this.setWidth(2 * radius);
}
public void update(Bounds bounds) {
Point position = getPosition();
Vector velocity = getVelocity();
if (position.getX() + velocity.getXComponent() < getRadius()
|| position.getX() + velocity.getXComponent() > bounds.getMaxX() - getRadius()) {
velocity.setXComponent(velocity.getXComponent() * (-1));
}
if (position.getY() + velocity.getYComponent() < getRadius()
|| position.getY() + velocity.getYComponent() > bounds.getMaxY() - getRadius()) {
velocity.setYComponent(velocity.getYComponent() * (-1));
}
double px = position.getX() + velocity.getXComponent();
double py = position.getY() + velocity.getYComponent();
setPosition(new Point(px, py));
//System.out.println(getPosition());
}
public void render() {
if (gc != null && color != null) {
//System.out.println("Rendering");
gc.setFill(color);
gc.fillOval(getPosition().getX()-getRadius(), getPosition().getY()
- getRadius(), 2*getRadius(), 2*getRadius());
}
}
@Override
public void setPosition(Point p) {
super.setPosition(p);
render();
}
private void setWidth(double l) {
this.width = l;
}
private void setHeight(double l) {
this.height = l;
}
}
Box.java
package sprite;
import javax.swing.text.Position;
import javafx.geometry.Bounds;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import physics.AABB;
import physics.Point;
import physics.Vector;
public class Box extends AABB {
private double height;
private double width;
private double halfHeight;
private double halfWidth;
public Color color;
public GraphicsContext gc;
public Box(Point min, Point max) {
super(min, max);
this.setHeight(getMin().getY() - getMax().getY());
this.setWidth(getMax().getX() - getMin().getX());
}
public Box(Point position, double height, double width) {
super(position, height, width);
this.setHeight(getMin().getY() - getMax().getY());
this.setWidth(getMax().getX() - getMin().getX());
}
public void update(Bounds bound) {
Point position = getPosition();
Vector velocity = getVelocity();
double px = position.getX() - (this.halfWidth);
double py = position.getY() - (this.halfHeight);
if (px + velocity.getXComponent() < 0 || px + velocity.getXComponent() + width > bound.getMaxX()) {
velocity.setXComponent(velocity.getXComponent() * (-1));
}
if (py + velocity.getYComponent() < 0 || py + velocity.getYComponent() + height > bound.getMaxY()) {
velocity.setYComponent(velocity.getYComponent() * (-1));
}
px += velocity.getXComponent();
py += velocity.getYComponent();
getMin().setX(px);
getMin().setY(py + height);
getMax().setX(px + width);
getMax().setY(py);
updatePosition();
}
@Override
public void setPosition(Point p) {
super.setPosition(p);
render();
}
private void render() {
if (color!=null && gc!=null) {
gc.setFill(color);
gc.fillRect(getPosition().getX() - this.halfWidth, getPosition().getY() - this.halfHeight, this.width, this.height);
}
}
public void setHeight(double height) {
this.height = height;
this.halfHeight = height / 2.0d;
}
public void setWidth(double width) {
this.width = width;
this.halfWidth = width / 2.0d;
}
}
Circle.java
package physics;
public class Circle extends Shape {
private long radius;
public Circle(long radius, Point position) {
this.radius = radius;
this.setPosition(position);
this.setVelocity(new Vector(new Point(0.0d, 0.0d), new Point(0, 0)));
this.setMass(0);
this.setRestitution(1);
this.setAcceleration(new Vector(new Point(0, 0), new Point(0, 0)));
this.setType(Type.Circle);
}
public long getRadius() {
return this.radius;
}
public void setRadius(long radius) {
this.radius = radius;
}
}
AABB.java
package physics;
public class AABB extends Shape {
private Point min;
private Point max;
public AABB(Point min, Point max) {
this.min = min;
this.max = max;
this.setVelocity(new Vector(new Point(0, 0), new Point(0, 0)));
this.setMass(0);
this.setRestitution(1);
this.setType(Type.AABB);
updatePosition();
}
public AABB(Point position, double height, double width) throws IllegalArgumentException {
double minX;
double minY;
double maxX;
double maxY;
minX = position.getX() - (width / 2.0d);
minY = position.getY() + (height / 2.0d);
maxX = position.getX() + (width / 2.0d);
maxY = position.getY() - (height / 2.0d);
if (minX < 0 || minY < height) {
throw new IllegalArgumentException("Inappropriate position");
}
Point min = new Point(minX, minY);
Point max = new Point(maxX, maxY);
this.min = min;
this.max = max;
this.setVelocity(new Vector(new Point(0, 0), new Point(0, 0)));
this.setMass(0);
this.setRestitution(1);
this.setType(Type.AABB);
setPosition(position);
}
public void updatePosition() {
setPosition(new Point((min.getX() + max.getX()) / 2, (min.getY() + max.getY()) / 2));
}
public Point getMin() {
return this.min;
}
public Point getMax() {
return this.max;
}
}
Shape.java
package physics;
public class Shape {
private Vector velocity;
private Vector acceleration;
private long mass;
private double invMass;
private float restitution;
private Type type;
private Point position;
public enum Type {
Circle, AABB, count
}
public Vector getAcceleration() {
return this.acceleration;
}
public void setAcceleration(Vector acceleration) {
this.acceleration = acceleration;
}
public long getMass() {
return this.mass;
}
public void setMass(long mass) {
this.mass = mass;
this.setInvMass(mass);
}
public Vector getVelocity() {
return this.velocity;
}
public void setVelocity(Vector velocity) {
this.velocity = velocity;
}
public double getInvMass() {
return this.invMass;
}
private void setInvMass(long mass) {
if (mass == 0) {
invMass = Long.MAX_VALUE;
return;
}
this.invMass = 1.0d / (double) mass;
}
public float getRestitution() {
return this.restitution;
}
public void setRestitution(float d) {
this.restitution = d;
}
public Type getType() {
return this.type;
}
public void setType(Type type) {
this.type = type;
}
public Point getPosition() {
return this.position;
}
public void setPosition(Point position) {
this.position = position;
}
}
Vector.java
封装物理;
public class Vector {
private Point p1;
private Point p2;
private double xComponent;
private double yComponent;
private double angle;
private double magnitude;
public Vector(Point p1, Point p2) {
this.p1 = p1;
this.p2 = p2;
this.xComponent = this.p2.getX() - this.p1.getX();
this.yComponent = this.p2.getY() - this.p1.getY();
this.angle = Math.atan2(this.yComponent, this.xComponent);
this.magnitude = Math.sqrt(this.xComponent * this.xComponent + this.yComponent * this.yComponent);
}
public Vector(Point p2) {
Point p1 = new Point(0, 0);
this.p1 = p1;
this.p2 = p2;
this.xComponent = this.p2.getX() - this.p1.getX();
this.yComponent = this.p2.getY() - this.p1.getY();
this.angle = Math.atan2(this.yComponent, this.xComponent);
this.magnitude = Math.sqrt(this.xComponent * this.xComponent + this.yComponent * this.yComponent);
}
public Vector(double magnitude, Vector unitVector) {
scaledProduct(magnitude, unitVector);
}
public Vector() {
}
private void scaledProduct(double magnitude, Vector unitVector) {
Point point = new Point(magnitude * unitVector.getXComponent(), magnitude * unitVector.getYComponent());
new Vector(point);
}
public static Vector scalarProduct(double magnitude, Vector unitVector) {
Point point = new Point(magnitude * unitVector.getXComponent(), magnitude * unitVector.getYComponent());
return new Vector(point);
}
public static double dotProduct(Vector v1, Vector v2) {
return (v1.xComponent * v2.xComponent + v1.yComponent * v2.yComponent);
}
public static Vector sum(Vector v1, Vector v2) {
return new Vector(new Point(v1.getXComponent() + v2.getXComponent(), v1.getYComponent() + v2.getYComponent()));
}
public static Vector difference(Vector from, Vector vector) {
return new Vector(new Point(from.getXComponent() - vector.getXComponent(),
from.getYComponent() - vector.getYComponent()));
}
public static Vector scalarDivision(Vector vector, double by) {
return new Vector(new Point(vector.getXComponent() / by, vector.getYComponent() / by));
}
public static double angleBetween(Vector v1, Vector v2) {
return Math.acos(Vector.dotProduct(v1, v2) / (v1.getMagnitude() * v2.getMagnitude()));
}
public double getXComponent() {
return this.xComponent;
}
public void setXComponent(double d) {
this.xComponent = d;
update();
}
public double getYComponent() {
return this.yComponent;
}
public void setYComponent(double d) {
this.yComponent = d;
update();
}
public double getAngle() {
return this.angle;
}
public void setAngle(double angle) {
this.angle = angle;
update();
}
public double getMagnitude() {
return this.magnitude;
}
public void setMagnitude(double length) {
this.magnitude = length;
}
public void update() {
this.angle = Math.atan2(this.yComponent, this.xComponent);
this.magnitude = Math.sqrt(this.xComponent * this.xComponent + this.yComponent * this.yComponent);
}
@Override
public boolean equals(Object v) {
Vector vector = (Vector) v;
return (ImpulseMath.equal(this.xComponent, vector.xComponent)
&& ImpulseMath.equal(this.yComponent, vector.yComponent));
}
}
Point.java
package physics;
public class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public static double distance(Point p1, Point p2) {
return Math.sqrt(
(p1.getX() - p2.getX()) * (p1.getX() - p2.getX()) + (p1.getY() - p2.getY()) * (p1.getY() - p2.getY()));
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
}
ImpulseScene.java
package physics;
import java.util.ArrayList;
public class ImpulseScene {
public double dt;
public int iterations;
public ArrayList<Shape> bodies = new ArrayList<>();
public ArrayList<Manifold> contacts = new ArrayList<>();
public ImpulseScene(int iterations) {
this.iterations = iterations;
}
public void step() {
contacts.clear();
for (int i = 0; i < bodies.size(); ++i) {
Shape A = bodies.get(i);
for (int j = i + 1; j < bodies.size(); ++j) {
Shape B = bodies.get(j);
if (A.getInvMass() == 0 && B.getInvMass() == 0) {
continue;
}
Manifold m = new Manifold(A, B);
m.solve();
if (m.contactCount > 0) {
contacts.add(m);
}
}
}
for (int i = 0; i < contacts.size(); ++i) {
contacts.get(i).initialize();
}
for (int j = 0; j < iterations; ++j) {
for (int i = 0; i < contacts.size(); ++i) {
contacts.get(i).applyImpulse();
}
}
for (int i = 0; i < contacts.size(); ++i) {
contacts.get(i).positionalCorrection();
}
}
public void clear() {
contacts.clear();
bodies.clear();
}
}
Collision.java
package physics;
public class Collision {
public static CollisionCallback dispatch[][] = { { CollisionCircleCircle.instance, CollisionCircleAABB.instance },
{ CollisionAABBCircle.instance, CollisionAABBAABB.instance } };
}
CollisionCallback.java
package physics;
public interface CollisionCallback {
public void resolveCollision(Shape a, Shape b, Manifold manifold);
}
碰撞圆AABB.java
package physics;
public class CollisionCircleAABB implements CollisionCallback {
public static final CollisionCircleAABB instance = new CollisionCircleAABB();
@Override
public void resolveCollision(Shape a, Shape b, Manifold manifold) {
CollisionAABBCircle.instance.resolveCollision(b, a, manifold);
if (manifold.contactCount>0) {
manifold.normal.setXComponent(-manifold.normal.getXComponent());
manifold.normal.setYComponent(-manifold.normal.getYComponent());
}
}
}
CollisionAABBCircle.java
package physics;
public class CollisionAABBCircle implements CollisionCallback {
public static final CollisionAABBCircle instance = new CollisionAABBCircle();
@Override
public void resolveCollision(Shape shape1, Shape shape2, Manifold manifold) {
AABB a = (AABB) shape1;
Circle c = (Circle) shape2;
Vector normal = Vector.difference(new Vector(c.getPosition()), new Vector(a.getPosition()));
Vector closest = new Vector(new Point(normal.getXComponent(), normal.getYComponent()));
double xExtent = (a.getMax().getX() - a.getMin().getX()) / 2.0d;
double yExtent = (a.getMin().getY() - a.getMax().getY()) / 2.0d;
closest.setXComponent(ImpulseMath.clamp(-xExtent, xExtent, closest.getXComponent()));
closest.setYComponent(ImpulseMath.clamp(-yExtent, yExtent, closest.getYComponent()));
boolean inside = false;
if (normal.equals(closest)) {
inside = true;
if (Math.abs(normal.getXComponent()) < Math.abs(normal.getYComponent())) {
if (closest.getXComponent() > 0) {
closest.setXComponent(xExtent);
} else {
closest.setXComponent(-xExtent);
}
} else {
if (closest.getYComponent() > 0) {
closest.setYComponent(yExtent);
} else {
closest.setYComponent(-yExtent);
}
}
}
Vector n = Vector.difference(normal, closest);
double d = n.getMagnitude();
double r = c.getRadius();
if (d > r && !inside) {
manifold.contactCount = 0;
return;
}
manifold.contactCount = 1;
if (inside) {
manifold.normal.setXComponent((-normal.getXComponent()) / normal.getMagnitude());
manifold.normal.setYComponent((-normal.getYComponent()) / normal.getMagnitude());
} else {
manifold.normal.setXComponent((normal.getXComponent()) / normal.getMagnitude());
manifold.normal.setYComponent((normal.getYComponent()) / normal.getMagnitude());
}
manifold.penetration = r - d;
}
}
Manifold.java
package physics;
public class Manifold {
public Shape a;
public Shape b;
public double penetration;
public final Vector normal = new Vector(new Point(0.0d, 0.0d));
public int contactCount;
public double e;
public Manifold(Shape a, Shape b) {
this.a = a;
this.b = b;
this.contactCount = 0;
}
public void solve() {
int ia = a.getType().ordinal();
int ib = b.getType().ordinal();
Collision.dispatch[ia][ib].resolveCollision(a, b, this);
}
public void initialize() {
e = Math.min(a.getRestitution(), b.getRestitution());
}
public void applyImpulse() {
Vector relativeVelocity = Vector.difference(b.getVelocity(), a.getVelocity());
double velocityAlongNormal = Vector.dotProduct(relativeVelocity, this.normal);
if (velocityAlongNormal > 0) {
return;
}
double restitution = Math.min(a.getRestitution(), b.getRestitution());
double j = -(1 + restitution) * velocityAlongNormal;
j /= (a.getInvMass() + b.getInvMass());
Vector impulse = Vector.scalarProduct(j, this.normal);
a.setVelocity(Vector.difference(a.getVelocity(), Vector.scalarProduct(a.getInvMass(), impulse)));
b.setVelocity(Vector.sum(b.getVelocity(), Vector.scalarProduct(b.getInvMass(), impulse)));
}
public void positionalCorrection() {
double correction = Math.max(this.penetration - ImpulseMath.PENETRATION_ALLOWANCE, 0.0d)
/ (a.getInvMass() + b.getInvMass()) * ImpulseMath.PENETRATION_CORRETION;
Point posA = a.getPosition();
Point posB = b.getPosition();
Point newPosA = new Point(posA.getX() - (correction * a.getInvMass()),
posA.getY() - (correction * a.getInvMass()));
Point newPosB = new Point(posB.getX() + (correction * b.getInvMass()),
posB.getY() + (correction * b.getInvMass()));
a.setPosition(newPosA);
b.setPosition(newPosB);
}
}
我想不出发生这种情况的任何原因。请帮帮我,我没主意了。
问题已解决。原来教程网站上的代码是错误的。在CollisionAABBCircle.java文件中最后一个if条件,应该是n不正常。它解决了这个问题,因为球在盒子里面,法线应该是从球的中心指向边缘上最近点的向量。更改后最终代码将是:
if (inside) {
manifold.normal.setXComponent((-n.getXComponent()) / n.getMagnitude());
manifold.normal.setYComponent((-n.getYComponent()) / n.getMagnitude());
} else {
manifold.normal.setXComponent((normal.getXComponent()) / normal.getMagnitude());
manifold.normal.setYComponent((normal.getYComponent()) / normal.getMagnitude());
}