Here’s how far I’ve got with polygons: http://www.freewebs.com/commanderkeith/PolygonPhysics.jnlp
There are a couple of bugs that beat me, but maybe someone clever could look into it :):
1) convex (>180 degree) vertices lead to overlap (see demo 6)
2) points in a line cause ‘bumps’ as something slides down an otherwise straight line (see demo 2)
3) the stacking boxes (demo 7) doesn’t work properly without a high framerate - it’s as if the penetration amount is being taken account of - the boxes overlap each other for frame rates of less than 20FPS.
In the demo you can see the size & angle of the forces being applied - they’re all at vertices of polygons. Also, you can zoom in using PG_DOWN or the mouse wheel to get a good look at what’s happening. Dragging the mouse moves the view.
The source is at http://www.freewebs.com/commanderkeith/PolygonPhysics.zip.
This is basically how it works:
// find point a on polyA that’s contained in polyB.
// check if the line from a to a-1 intersects a line on polyB.
// if it does, that polyB line is the ‘incident face’ - ie the one that the ‘normal force’ will be applied at 90 degrees.
// if it doesn’t, do same with a & a+1. if still no instersection, just ignore point a.
// repeat for polyB on polyA.
// penetration (sep) is just the distance from the contained point a to the incident face (just perpendicular dist).
Kev & I would love some help with this project, and to be (geekily) honest its quite an interesting physics & geometry problem! I don’t want to bore everyone, but here’s the (long but complete) code that works out the forces between 2 polygons, in PolygonPhysics/src - net.phys2d.raw.collide.KPolygonKPolygonCollider:
(note that I changed the array reference [ b ] to [b_] to stop bold text appearing).
public int collide(Contact[] contacts, Body bodyA, Body bodyB) {
float x1 = bodyA.getPosition().getX();
float y1 = bodyA.getPosition().getY();
float x2 = bodyB.getPosition().getX();
float y2 = bodyB.getPosition().getY();
boolean touches = bodyA.getShape().boundsIntersect(bodyB.getShape());
if (!touches) {
return 0;
}
KPolygon polyA = (KPolygon) bodyA.getShape();
KPolygon polyB = (KPolygon) bodyB.getShape();
polyA.name = "A";
polyB.name = "B";
int count = 0;
boolean first = true;
Point2D.Float[] pointsA = polyA.getPoints();
Point2D.Float[] pointsB = polyB.getPoints();
OuterLoopA:
for (int a = 0; a < pointsA.length; a++){
if (polyB.contains(pointsA[a])){
//System.out.println("polyB contains a");
int aPlus = (a+1 == pointsA.length ? 0 : a+1);
Vector2f point = null;
Vector2f normal = null;
float separation = Float.MAX_VALUE;
for (int b = 0; b < pointsB.length; b++){
int bPlus = (b+1 == pointsB.length ? 0 : b+1);
if (KPolygon.linesIntersect(pointsA[a].x,pointsA[a].y,pointsA[aPlus].x,pointsA[aPlus].y,
pointsB[b_].x,pointsB[b_].y,pointsB[bPlus].x,pointsB[bPlus].y)){
float newSeparation = (float)Line2D.ptLineDist(pointsB[b_].x, pointsB[b_].y, pointsB[bPlus].x, pointsB[bPlus].y, pointsA[a].x, pointsA[a].y);
if (separation < newSeparation){
continue;
}
separation = newSeparation;
Vector2f face = new Vector2f(pointsB[b_].x-pointsB[bPlus].x, pointsB[b_].y-pointsB[bPlus].y);
normal = new Vector2f(face.getY(), -face.getX());
//System.out.println("polyB contains point a, a-aPlus intersects polyB");
normal.normalise();
point = new Vector2f(pointsA[a].x, pointsA[a].y);
}
int aMinus = (a-1 == -1 ? pointsA.length-1 : a-1);
if (KPolygon.linesIntersect(pointsA[a].x,pointsA[a].y,pointsA[aMinus].x,pointsA[aMinus].y,
pointsB[b_].x,pointsB[b_].y,pointsB[bPlus].x,pointsB[bPlus].y)){
float newSeparation = (float)Line2D.ptLineDist(pointsB[b_].x, pointsB[b_].y, pointsB[bPlus].x, pointsB[bPlus].y, pointsA[a].x, pointsA[a].y);
if (separation < newSeparation){
continue;
}
separation = newSeparation;
Vector2f face = new Vector2f(pointsB[b_].x-pointsB[bPlus].x, pointsB[b_].y-pointsB[bPlus].y);
normal = new Vector2f(face.getY(), -face.getX());
//System.out.println("polyB contains point a, a-aMinus intersects polyB");
normal.normalise();
point = new Vector2f(pointsA[a].x, pointsA[a].y);
}
}
if (point != null){
//System.out.println("added contact no. "+count);
contacts[count].setSeparation(separation);
contacts[count].setPosition(point);
contacts[count].setNormal(normal);
count++;
if (count >= Arbiter.MAX_POINTS-1){
return count;
}
continue OuterLoopA;
}
}
}
OuterLoopB:
for (int b = 0; b < pointsB.length; b++){
if (polyA.contains(pointsB[b_])){
//System.out.println("polyA contains b");
int bPlus = (b+1 == pointsB.length ? 0 : b+1);
Vector2f point = null;
Vector2f normal = null;
float separation = Float.MAX_VALUE;
for (int a = 0; a < pointsA.length; a++){
int aPlus = (a+1 == pointsA.length ? 0 : a+1);
if (KPolygon.linesIntersect(pointsB[b_].x,pointsB[b_].y,pointsB[bPlus].x,pointsB[bPlus].y,
pointsA[a].x,pointsA[a].y,pointsA[aPlus].x,pointsA[aPlus].y)){
float newSeparation = (float)Line2D.ptLineDist(pointsA[a].x, pointsA[a].y, pointsA[aPlus].x, pointsA[aPlus].y, pointsB[b_].x, pointsB[b_].y);
if (separation < newSeparation){
continue;
}
separation = newSeparation;
Vector2f face = new Vector2f(pointsA[a].x-pointsA[aPlus].x, pointsA[a].y-pointsA[aPlus].y);
normal = new Vector2f(-face.getY(), face.getX());
//System.out.println("polyA contains point b, b-bPlus intersects polyA");
normal.normalise();
point = new Vector2f(pointsB[b_].x, pointsB[b_].y);
}
int bMinus = (b-1 == -1 ? pointsB.length-1 : b-1);
if (KPolygon.linesIntersect(pointsB[b_].x,pointsB[b_].y,pointsB[bMinus].x,pointsB[bMinus].y,
pointsA[a].x,pointsA[a].y,pointsA[aPlus].x,pointsA[aPlus].y)){
float newSeparation = (float)Line2D.ptLineDist(pointsA[a].x, pointsA[a].y, pointsA[aPlus].x, pointsA[aPlus].y, pointsB[b_].x, pointsB[b_].y);
if (separation < newSeparation){
continue;
}
separation = newSeparation;
Vector2f face = new Vector2f(pointsA[a].x-pointsA[aPlus].x, pointsA[a].y-pointsA[aPlus].y);
normal = new Vector2f(-face.getY(), face.getX());
//System.out.println("polyA contains point b, b-bMinus intersects polyA");
normal.normalise();
point = new Vector2f(pointsB[b_].x, pointsB[b_].y);
}
}
if (point != null){
//System.out.println("added contact no. "+count);
contacts[count].setSeparation(separation);
contacts[count].setPosition(point);
contacts[count].setNormal(normal);
count++;
if (count >= Arbiter.MAX_POINTS-1){
return count;
}
continue OuterLoopB;
}
}
}
return count;
}
Cheers,
Keith