package engine.utility;
import java.util.LinkedList;
import java.util.List;
import engine.geometry.Polygon;
import engine.geometry.Vector;
import game.terrain.Block;
public class PhysicsDan implements PhysicsMode {
public static class Collision {
double time;
Vector mostClockwise;
Vector mostAntiClockwise;
public Collision(double time, Vector vector1, Vector vector2) {
this.time = time;
if (vector1.cross(vector2) > 0) {
// Vector2 is more clockwise
mostClockwise = vector2;
mostAntiClockwise = vector1;
} else {
mostClockwise = vector1;
mostAntiClockwise = vector2;
}
}
public void union(Collision other) {
if (mostClockwise.cross(other.mostClockwise) > 0) {
// other more clockwise
mostClockwise = other.mostClockwise;
}
if (mostAntiClockwise.cross(other.mostAntiClockwise) < 0) {
// other more anti-clockwise
mostAntiClockwise = other.mostAntiClockwise;
}
}
public void intersection(Collision other) {
if (mostClockwise.cross(other.mostClockwise) < 0) {
// other's less clockwise
mostClockwise = other.mostClockwise;
}
if (mostAntiClockwise.cross(other.mostAntiClockwise) > 0) {
// other's less anti-clockwise
mostAntiClockwise = other.mostAntiClockwise;
}
}
public Vector getBestNormal(Vector velocity) {
if (mostClockwise.dot(velocity) > mostAntiClockwise.dot(velocity)) {
return mostClockwise;
} else {
return mostAntiClockwise;
}
}
}
public Collision getCollision(Polygon relStatic, Polygon relDynamic, Vector relVelocity) {
Vector backVelocity = new Vector(relVelocity).flip().normalise();
List<Collision> frontFaces = new LinkedList<Collision>();
List<Collision> backFaces = new LinkedList<Collision>();
List<Collision> zeroFaces = new LinkedList<Collision>();
Vector n = new Vector();
final int size1 = relStatic.getSize();
for (int i = 0; i < size1; i++) {
n = relStatic.getNormal(i);
double nDistance = relStatic.getMaxProjection(i) - Physics.minProjection(relDynamic, n);
double nVelocity = relVelocity.dot(n);
double nTime = nDistance / nVelocity;
if (Rough.equal(nDistance, 0) && Rough.equal(nVelocity, 0)) {
// Sliding along this face
zeroFaces.add(new Collision(Double.NaN, new Vector(n), backVelocity));
} else if (Double.compare(nVelocity, 0) < 0) { // -0.0 and below
// This is a front face
frontFaces.add(new Collision(nTime, new Vector(n), backVelocity));
} else if (Double.compare(nVelocity, 0) >= 0) { // +0.0 and above
// This is a back face
backFaces.add(new Collision(nTime, new Vector(n), backVelocity));
}
}
final int size2 = relDynamic.getSize();
for (int i = 0; i < size2; i++) {
n = relDynamic.getNormal(i);
double nDistance = relDynamic.getMaxProjection(i) - Physics.minProjection(relStatic, n);
n.flip();
double nVelocity = relVelocity.dot(n);
double nTime = nDistance / nVelocity;
if (Rough.equal(nDistance, 0) && Rough.equal(nVelocity, 0)) {
// Sliding along this face
zeroFaces.add(new Collision(Double.NaN, new Vector(n), backVelocity));
} else if (Double.compare(nVelocity, 0) < 0) { // -0.0 and below
// This is a front face
frontFaces.add(new Collision(nTime, new Vector(n), backVelocity));
} else if (Double.compare(nVelocity, 0) >= 0) { // +0.0 and above
// This is a back face
backFaces.add(new Collision(nTime, new Vector(n), backVelocity));
}
}
// Find the last entry normal
double lastEntry = Double.NEGATIVE_INFINITY;
for (Collision c : frontFaces) {
if (c.time > lastEntry) {
lastEntry = c.time;
}
}
// Find the first exit normal
double firstExit = Double.POSITIVE_INFINITY;
for (Collision c : backFaces) {
if (c.time < firstExit) {
firstExit = c.time;
}
}
if (Rough.less(firstExit, lastEntry)) {
// Exited before entered => NO collision
return null;
} else if (Rough.greater(firstExit, lastEntry)) {
// Entered before exited => COLLISION
Collision result = new Collision(lastEntry, backVelocity, backVelocity);
for (Collision c : frontFaces) {
if (Rough.equal(c.time, lastEntry)) {
result.union(c);
// TODO: maybe set lastEntry to the least of these times???
}
}
for (Collision c : zeroFaces) {
result.union(c);
}
if (Rough.equal(result.getBestNormal(relVelocity).dot(relVelocity), 0)) {
// Just sliding???
// TODO: work this out...
return null;
}
// All done!
return result;
} else {
// firstExit ~= lastEntry
// => Near miss???
// TODO: work this out...
// We want to make sure we don't scrape on blocks below a slope
return null;
}
}
@Override
public Vector move(Block[][] blocks, Polygon position, Vector velocity) {
double timeToGo = 1;
for (int count = 0; count < 2; count++) {
// Get the swept area
double x1 = position.getMinX();
double y1 = position.getMinY();
double x2 = position.getMaxX();
double y2 = position.getMaxY();
int r1 = (int) Math.ceil((Math.min(y1, y1 + velocity.getY())) / Block.HEIGHT) - 1;
int r2 = (int) Math.floor((Math.max(y2, y2 + velocity.getY())) / Block.HEIGHT) + 1;
int c1 = (int) Math.ceil((Math.min(x1, x1 + velocity.getX())) / Block.WIDTH) - 1;
int c2 = (int) Math.floor((Math.max(x2, x2 + velocity.getX())) / Block.WIDTH) + 1;
if (r1 < 0) {
r1 = 0;
} else if (r1 > blocks.length) {
r1 = blocks.length;
}
if (r2 < 0) {
r2 = 0;
} else if (r2 > blocks.length) {
r2 = blocks.length;
}
if (c1 < 0) {
c1 = 0;
} else if (c1 > blocks[0].length) {
c1 = blocks.length;
}
if (c2 < 0) {
c2 = 0;
} else if (c2 > blocks[0].length) {
c2 = blocks.length;
}
// Get list of collisions with all blocks
List<Collision> collisions = new LinkedList<Collision>();
for (int r = r1; r < r2; r++) {
for (int c = c1; c < c2; c++) {
if (blocks[r][c].getPolygon() != null) {
Collision collision = getCollision(blocks[r][c].getPolygon(), position, velocity);
if (collision != null && Rough.lessEqual(0, collision.time)
&& Rough.lessEqual(collision.time, timeToGo)) {
collisions.add(collision);
}
}
}
}
// Default collision
Vector vel = new Vector(velocity).normalise();
Vector velNorm = new Vector(vel).normal();
Vector velAnti = new Vector(vel).antiNormal();
Collision result = new Collision(Double.POSITIVE_INFINITY, velNorm, velAnti);
// Find earliest collision
for (Collision c : collisions) {
if (c.time < result.time) {
result = c;
}
}
// Find all collisions which happened at that time
for (Collision c : collisions) {
if (Rough.equal(c.time, result.time)) {
result.intersection(c);
}
}
// Check if the collision was in this step
if (Rough.lessEqual(0, result.time) && Rough.lessEqual(result.time, timeToGo)) {
// Move to collision point
Vector move = new Vector(velocity).multiply(result.time);
Transform.translate(position, move);
// Calculate new velocity
Vector normal = new Vector(result.getBestNormal(velocity));
velocity.project(normal.antiNormal());
// Time used up
timeToGo -= result.time;
if (Rough.lessEqual(timeToGo, 0)) {
// Done!
break;
}
} else {
// No collision. Done!
Vector velScaled = new Vector(velocity).multiply(timeToGo);
Transform.translate(position, velScaled);
break;
}
}
// TODO: return correct normal
return null;
}
}