package engine.utility;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import engine.geometry.Polygon;
import engine.geometry.Vector;
import game.terrain.Block;
/* Slides in the closest free direction
* TODO: Finish coding and debugging
*/
public class PhysicsAnj implements PhysicsMode {
public static class Collision {
ArrayList<Intersect> intersects;
boolean wrap = false;
static class Intersect {
double angle;
Vector vect;
Intersect (double angle, Vector vect) {
this.angle = angle;
this.vect = vect;
}
}
static class MergeableIntersect implements Comparable<MergeableIntersect> {
Intersect intersect;
CollisionIter iter;
public MergeableIntersect(Intersect intersect, CollisionIter iter) {
this.intersect = intersect;
this.iter = iter;
}
@Override
public int compareTo(MergeableIntersect other) {
return Double.compare(intersect.angle, other.intersect.angle);
}
}
static class CollisionIter {
Iterator<Intersect> iter;
boolean state;
CollisionIter (Collision collision) {
iter = collision.intersects.iterator();
state = collision.wrap;
}
boolean next () {
iter.next();
state = !state;
return state;
}
}
static Collision merge (ArrayList<Collision> collisions, boolean union) {
ArrayList<MergeableIntersect> combinedIntersects = new ArrayList<MergeableIntersect>();
int collidingCount = 0;
for (Collision collision : collisions) {
CollisionIter collisionIter = new CollisionIter(collision);
if (collision.wrap) {
collidingCount++;
}
for (Intersect intersect : collision.intersects) {
combinedIntersects.add(new MergeableIntersect(intersect, collisionIter));
}
}
Collections.sort(combinedIntersects);
boolean combinedState;
boolean previousCombinedState;
if (union) {
combinedState = collidingCount>0;
} else {
combinedState = collidingCount==collisions.size();
}
Collision result = new Collision(combinedState);
for (MergeableIntersect intersect : combinedIntersects) {
boolean state = intersect.iter.next();
if (state) {
collidingCount++;
} else {
collidingCount--;
}
previousCombinedState = combinedState;
if (union) {
combinedState = collidingCount>0;
} else {
combinedState = collidingCount==collisions.size();
}
if (combinedState != previousCombinedState) {
result.intersects.add(intersect.intersect);
}
}
return result;
}
/* Closest free location to vector */
public Vector closest (Vector v) {
// TODO
return v;
}
/* A monotonic function of vector angle
* Vector is assumed to be normalised
* Output in range -2 to +2
*/
private static double map (Vector v) {
if (v.getX() > 0) {
return 1 - v.getY();
} else {
return v.getY() - 1;
}
}
/* Holds a collision range
* Range is clockwise from start to end
* When angle between vectors is small, reflexAngle is used to determine direction
* Vectors are assumed to be normalised
*/
public Collision (Vector start, Vector end, boolean reflexAngle) {
double a = map(start); // start mapped to range 'a' = -2 to 2
double b = map(end); // end mapped to range 'b' = -2 to +2
if (a <= b) {
intersects.add(new Intersect(a, start));
intersects.add(new Intersect(b, end));
} else {
intersects.add(new Intersect(b, end));
intersects.add(new Intersect(a, start));
}
if (b+1 < a || (reflexAngle && b < a+1)) {
/* either:
* - end angle is definitely smaller than start angle
* - angles are close and reflex
* which means they must have wrapped
*/
wrap = true;
}
}
/* Holds a collision range
* Complete collision if fill is true, else empty collision
*/
public Collision (boolean fill) {
wrap = fill;
}
}
/* Circular collision with single axis */
public static Collision axisCollision (Vector normal, double distance, Vector velocity) {
double cSqr = velocity.magnitudeSquared(); // TODO: cache this for entire shape
double bSqr = distance*distance;
double aSqr = cSqr - bSqr;
if (aSqr <= 0) {
// no intersect with axis
// complete collision if shape has crossed axis, else empty collision
return new Collision(distance < 0);
}
double c = Math.sqrt(cSqr); // TODO: cache this for entire shape
double b = distance;
double a = Math.sqrt(aSqr);
// find first intersect with axis anti-clockwise of normal
Vector start = new Vector (-b*normal.getX() + a*normal.getY(), -b*normal.getY() - a*normal.getX());
// find first intersect with axis clockwise of normal
Vector end = new Vector (-b*normal.getX() - a*normal.getY(), -b*normal.getY() + a*normal.getX());
// normalise
start.divide(c);
end.divide(c);
return new Collision(start, end, distance < 0);
}
/* Collision with single shape */
public static Collision shapeCollision (Polygon s, Polygon d, Vector velocity) {
Vector normal = new Vector(); // used to temporarily store normals in loop
ArrayList<Collision> collisions = new ArrayList<Collision>();
// SAT (Separating Axis Theorem) axes for dynamic shape
int dSize = d.getSize();
for (int i=0; i<dSize; i++) {
d.getNormal(i, normal);
double distance = Physics.minProjection(s, normal) - Physics.maxProjection(d, normal);
collisions.add(axisCollision (normal, distance, velocity));
}
// SAT axes for static shape
int sSize = s.getSize();
for (int i=0; i<sSize; i++) {
s.getNormal(i, normal);
normal.flip();
double distance = Physics.minProjection(s, normal) - Physics.maxProjection(d, normal);
collisions.add(axisCollision (normal, distance, velocity));
}
return Collision.merge(collisions, false); // collision if overlapping all axes
}
/* Collision with everything */
public static Collision allCollision (Block[][] blocks, Polygon position, Vector velocity) {
ArrayList<Collision> collisions = new ArrayList<Collision>();
for (Block[] row : blocks) {
for (Block block : row) {
collisions.add(shapeCollision(block.getPolygon(), position, velocity));
}
}
return Collision.merge(collisions, true); // collision if overlapping any shapes
}
@Override
public Vector move(Block[][] blocks, Polygon position, Vector velocity) {
// TODO
return null;
}
}