epsilon awareness in collision detection

This commit is contained in:
Mike Schwörer 2017-04-26 20:22:01 +02:00
parent b1eeed1119
commit dc6aa75f20
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
5 changed files with 5303 additions and 5261 deletions

File diff suppressed because it is too large Load Diff

View File

@ -138,7 +138,13 @@ public class CRGameRenderer extends AbstractGameRenderer {
renderDebug("CameraOffset: (" + (int)cam.position.x + "|" + (int)cam.position.y + ")"); renderDebug("CameraOffset: (" + (int)cam.position.x + "|" + (int)cam.position.y + ")");
renderDebug("Player(x): " + (int)gameworld.player.bounds.x); renderDebug(
"Player(x): " + (int)gameworld.player.bounds.x +
" | Touch [T;L;B;R] := [" + (gameworld.player.isTouching_TOP()?1:0) +
";" + (gameworld.player.isTouching_LEFT()?1:0) +
";" + (gameworld.player.isTouching_BOTTOM()?1:0) +
";" + (gameworld.player.isTouching_RIGHT()?1:0) + "]");
if (gameworld.mapprovider instanceof EndlessMapProvider) if (gameworld.mapprovider instanceof EndlessMapProvider)
renderDebug("Procedural Piece: \"" + ((EndlessMapProvider)gameworld.mapprovider).getCurrentSection(gameworld.player.bounds).piece_name + "\""); renderDebug("Procedural Piece: \"" + ((EndlessMapProvider)gameworld.mapprovider).getCurrentSection(gameworld.player.bounds).piece_name + "\"");
else if (gameworld.mapprovider instanceof StaticMapProvider) else if (gameworld.mapprovider instanceof StaticMapProvider)

View File

@ -7,7 +7,6 @@ import com.badlogic.gdx.math.Vector2;
import de.samdev.colorrunner.game.world.CRGameWorld; import de.samdev.colorrunner.game.world.CRGameWorld;
public abstract class CRGameEntity { public abstract class CRGameEntity {
public final static float F_EPSILON = 0.00004f;
protected CRGameWorld world; protected CRGameWorld world;

View File

@ -9,6 +9,10 @@ import de.samdev.colorrunner.game.world.CRGameWorld;
public abstract class MovingEntity extends CRGameEntity { public abstract class MovingEntity extends CRGameEntity {
protected Vector2 velocity = new Vector2(); protected Vector2 velocity = new Vector2();
public final static float COLL_PUSHBACK_EPSILON = 1e-5F;
public final static float COLL_DETECT_EPSILON = 2e-5f;
public final static float EXPAND_EPSILON = 1e-6f;
private boolean face_TOP_isTouching = false; private boolean face_TOP_isTouching = false;
private boolean face_LEFT_isTouching = false; private boolean face_LEFT_isTouching = false;
private boolean face_BOTTOM_isTouching = false; private boolean face_BOTTOM_isTouching = false;
@ -24,41 +28,49 @@ public abstract class MovingEntity extends CRGameEntity {
protected boolean moveByX(float bx) { protected boolean moveByX(float bx) {
if (bx == 0) return false; if (bx == 0) return false;
boolean collided = false; boolean collided = false;
Rectangle original = new Rectangle(bounds); Rectangle next = new Rectangle(bounds);
bounds.x += bx; next.x += bx;
next.set(next.x - COLL_PUSHBACK_EPSILON, next.y - COLL_PUSHBACK_EPSILON, next.width + 2*COLL_PUSHBACK_EPSILON, next.height + 2*COLL_PUSHBACK_EPSILON);
for (CRGameEntity ent : world.entities) { for (CRGameEntity ent : world.entities) {
if (ent == this) continue; if (ent == this) continue;
if (ent.bounds.overlaps(bounds) && ent.canCollide(true, this)) { if (ent.bounds.overlaps(next) && ent.canCollide(true, this)) {
if (ent.bounds.overlaps(original)) { if (ent.bounds.overlaps(bounds)) {
Gdx.app.log("Collision", "Ignore in bounds collision"); Gdx.app.log("Collision", "Ignore in bounds collision");
continue; continue;
} }
if (bx < 0) { // LEFT if (bx < 0) { // DOWN
float correction = (ent.bounds.x + ent.bounds.width) - bounds.x; float correction = (ent.bounds.x + ent.bounds.height) - next.x;
bounds.x += correction; next.x += correction;
bx += correction; bx += correction;
if (bx >= -COLL_PUSHBACK_EPSILON)
return true; // collision but no movement
collided = true; collided = true;
} else { // RIGHT } else { // UP
float correction = ent.bounds.x - (bounds.x + bounds.width); float correction = ent.bounds.x - (next.x + next.height);
bounds.x += correction; next.x += correction;
bx += correction; bx += correction;
if (bx <= COLL_PUSHBACK_EPSILON)
return true; // collision but no movement
collided = true; collided = true;
} }
} }
} }
bounds.x += bx;
return collided; return collided;
} }
@ -67,38 +79,46 @@ public abstract class MovingEntity extends CRGameEntity {
boolean collided = false; boolean collided = false;
Rectangle original = new Rectangle(bounds); Rectangle next = new Rectangle(bounds);
bounds.y += by; next.y += by;
next.set(next.x - COLL_PUSHBACK_EPSILON, next.y - COLL_PUSHBACK_EPSILON, next.width + 2*COLL_PUSHBACK_EPSILON, next.height + 2*COLL_PUSHBACK_EPSILON);
for (CRGameEntity ent : world.entities) { for (CRGameEntity ent : world.entities) {
if (ent == this) continue; if (ent == this) continue;
if (ent.bounds.overlaps(bounds) && ent.canCollide(true, this)) { if (ent.bounds.overlaps(next) && ent.canCollide(true, this)) {
if (ent.bounds.overlaps(original)) { if (ent.bounds.overlaps(bounds)) {
Gdx.app.log("Collision", "Ignore in bounds collision"); Gdx.app.log("Collision", "Ignore in bounds collision");
continue; continue;
} }
if (by < 0) { // DOWN if (by < 0) { // DOWN
float correction = (ent.bounds.y + ent.bounds.height) - bounds.y; float correction = (ent.bounds.y + ent.bounds.height) - next.y;
bounds.y += correction; next.y += correction;
by += correction; by += correction;
if (by >= -COLL_PUSHBACK_EPSILON)
return true; // collision but no movement
collided = true; collided = true;
} else { // UP } else { // UP
float correction = ent.bounds.y - (bounds.y + bounds.height); float correction = ent.bounds.y - (next.y + next.height);
bounds.y += correction; next.y += correction;
by += correction; by += correction;
if (by <= COLL_PUSHBACK_EPSILON)
return true; // collision but no movement
collided = true; collided = true;
} }
} }
} }
bounds.y += by;
return collided; return collided;
} }
@ -110,56 +130,55 @@ public abstract class MovingEntity extends CRGameEntity {
} }
private boolean updateHitBoxWidth(float width) { private boolean updateHitBoxWidth(float width) {
Rectangle original = new Rectangle(bounds); Rectangle next = new Rectangle(bounds);
float add = width - bounds.width; float add = width - bounds.width;
if (isTouching_LEFT() && ! isTouching_RIGHT()) { if (isTouching_LEFT() && ! isTouching_RIGHT()) {
bounds.width += add; next.width += add;
bounds.x -= add; next.x -= add;
} else if (! isTouching_LEFT() && isTouching_RIGHT()) { } else if (! isTouching_LEFT() && isTouching_RIGHT()) {
bounds.width += add; next.width += add;
} else { } else {
bounds.width += add; next.width += add;
bounds.x -= add/2; next.x -= add/2;
} }
if (add < 0) { if (add < 0) {
bounds.set(next);
return true; return true;
} else { } else {
next.set(next.x - COLL_PUSHBACK_EPSILON, next.y - COLL_PUSHBACK_EPSILON, next.width + 2*COLL_PUSHBACK_EPSILON, next.height + 2*COLL_PUSHBACK_EPSILON);
for (CRGameEntity ent : world.entities) { for (CRGameEntity ent : world.entities) {
if (ent == this) continue; if (ent == this) continue;
if (ent.bounds.overlaps(bounds) && ent.canCollide(false, this)) {
if (ent.bounds.overlaps(original)) { if (ent.bounds.overlaps(next) && ent.canCollide(false, this)) {
Gdx.app.log("Collision", "Ignore in bounds collision on HB Expand");
if (ent.bounds.overlaps(bounds)) {
Gdx.app.log("Collision", "Ignore in bounds collision on HB Expand [X]");
continue; continue;
} }
if (ent.bounds.x > next.x) {
if (ent.bounds.x > bounds.x) { next.x -= (next.x + next.width) - ent.bounds.x;
bounds.x -= (bounds.x + bounds.width) - ent.bounds.x;
for (CRGameEntity ent2 : world.entities) { for (CRGameEntity ent2 : world.entities) {
if (ent2 == this) continue; if (ent2 == this) continue;
if (ent2.bounds.overlaps(bounds) && ent2.canCollide(false, this)) { if (ent2.bounds.overlaps(next) && ent2.canCollide(false, this)) {
bounds = original;
Gdx.app.log("Entity Hitbox", "Expand failed X+"); Gdx.app.log("Entity Hitbox", "Expand failed X+");
return false; return false;
} }
} }
} else if (ent.bounds.x < bounds.x) { } else if (ent.bounds.x < next.x) {
bounds.x += (ent.bounds.x + ent.bounds.width) - bounds.x; next.x += (ent.bounds.x + ent.bounds.width) - next.x;
for (CRGameEntity ent2 : world.entities) { for (CRGameEntity ent2 : world.entities) {
if (ent2 == this) continue; if (ent2 == this) continue;
if (ent2.bounds.overlaps(bounds) && ent2.canCollide(false, this)) {
bounds = original;
if (ent2.bounds.overlaps(next) && ent2.canCollide(false, this)) {
Gdx.app.log("Entity Hitbox", "Expand failed X-"); Gdx.app.log("Entity Hitbox", "Expand failed X-");
return false; return false;
} }
@ -168,61 +187,61 @@ public abstract class MovingEntity extends CRGameEntity {
} }
} }
bounds.set(next.x + COLL_PUSHBACK_EPSILON, next.y + COLL_PUSHBACK_EPSILON, next.width - 2*COLL_PUSHBACK_EPSILON, next.height - 2*COLL_PUSHBACK_EPSILON);
return true; return true;
} }
} }
private boolean updateHitBoxHeight(float height) { private boolean updateHitBoxHeight(float height) {
Rectangle original = new Rectangle(bounds); Rectangle next = new Rectangle(bounds);
float add = height - bounds.height; float add = height - bounds.height;
if (isTouching_BOTTOM() && ! isTouching_TOP()) { if (isTouching_BOTTOM() && ! isTouching_TOP()) {
bounds.height += add; next.height += add;
} else if (! isTouching_BOTTOM() && isTouching_TOP()) { } else if (! isTouching_BOTTOM() && isTouching_TOP()) {
bounds.height += add; next.height += add;
bounds.y -= add; next.y -= add;
} else { } else {
bounds.height += add; next.height += add;
bounds.y -= add/2; next.y -= add/2;
} }
if (add < 0) { if (add < 0) {
bounds.set(next);
return true; return true;
} else { } else {
next.set(next.x - COLL_PUSHBACK_EPSILON, next.y - COLL_PUSHBACK_EPSILON, next.width + 2*COLL_PUSHBACK_EPSILON, next.height + 2*COLL_PUSHBACK_EPSILON);
for (CRGameEntity ent : world.entities) { for (CRGameEntity ent : world.entities) {
if (ent == this) continue; if (ent == this) continue;
if (ent.bounds.overlaps(bounds) && ent.canCollide(false, this)) { if (ent.bounds.overlaps(next) && ent.canCollide(false, this)) {
if (ent.bounds.overlaps(original)) { if (ent.bounds.overlaps(bounds)) {
Gdx.app.log("Collision", "Ignore in bounds collision on HB Expand"); Gdx.app.log("Collision", "Ignore in bounds collision on HB Expand [Y]");
continue; continue;
} }
if (ent.bounds.y > next.y) {
if (ent.bounds.y > bounds.y) { next.y -= (next.y + next.height) - ent.bounds.y;
bounds.y -= (bounds.y + bounds.height) - ent.bounds.y;
for (CRGameEntity ent2 : world.entities) { for (CRGameEntity ent2 : world.entities) {
if (ent2 == this) continue; if (ent2 == this) continue;
if (ent2.bounds.overlaps(bounds) && ent2.canCollide(false, this)) { if (ent2.bounds.overlaps(next) && ent2.canCollide(false, this)) {
bounds = original;
Gdx.app.log("Entity Hitbox", "Expand failed Y+"); Gdx.app.log("Entity Hitbox", "Expand failed Y+");
return false; return false;
} }
} }
} else if (ent.bounds.y < bounds.y) { } else if (ent.bounds.y < next.y) {
bounds.y += (ent.bounds.y + ent.bounds.height) - bounds.y; next.y += (ent.bounds.y + ent.bounds.height) - next.y;
for (CRGameEntity ent2 : world.entities) { for (CRGameEntity ent2 : world.entities) {
if (ent2 == this) continue; if (ent2 == this) continue;
if (ent2.bounds.overlaps(bounds) && ent2.canCollide(false, this)) { if (ent2.bounds.overlaps(next) && ent2.canCollide(false, this)) {
bounds = original;
Gdx.app.log("Entity Hitbox", "Expand failed Y-"); Gdx.app.log("Entity Hitbox", "Expand failed Y-");
return false; return false;
} }
@ -231,6 +250,7 @@ public abstract class MovingEntity extends CRGameEntity {
} }
} }
bounds.set(next.x + COLL_PUSHBACK_EPSILON, next.y + COLL_PUSHBACK_EPSILON, next.width - 2*COLL_PUSHBACK_EPSILON, next.height - 2*COLL_PUSHBACK_EPSILON);
return true; return true;
} }
} }
@ -244,19 +264,19 @@ public abstract class MovingEntity extends CRGameEntity {
for (CRGameEntity ent : world.entities) { for (CRGameEntity ent : world.entities) {
if (ent == this) continue; if (ent == this) continue;
if (Math.abs((ent.bounds.y + ent.bounds.height) - bounds.y) < F_EPSILON && ent.bounds.x < (bounds.x + bounds.width) && (ent.bounds.x + ent.bounds.width) > bounds.x && ent.canCollide(false, this)) { if (Math.abs((ent.bounds.y + ent.bounds.height) - bounds.y) < COLL_DETECT_EPSILON && ent.bounds.x < (bounds.x + bounds.width) && (ent.bounds.x + ent.bounds.width) > bounds.x && ent.canCollide(false, this)) {
face_BOTTOM_isTouching = true; face_BOTTOM_isTouching = true;
} }
if (Math.abs((bounds.y + bounds.height) - ent.bounds.y) < F_EPSILON && ent.bounds.x < (bounds.x + bounds.width) && (ent.bounds.x + ent.bounds.width) > bounds.x && ent.canCollide(false, this)) { if (Math.abs((bounds.y + bounds.height) - ent.bounds.y) < COLL_DETECT_EPSILON && ent.bounds.x < (bounds.x + bounds.width) && (ent.bounds.x + ent.bounds.width) > bounds.x && ent.canCollide(false, this)) {
face_TOP_isTouching = true; face_TOP_isTouching = true;
} }
if (Math.abs((bounds.x + bounds.width) - ent.bounds.x) < F_EPSILON && ent.bounds.y < (bounds.y + bounds.height) && (ent.bounds.y + ent.bounds.height) > bounds.y && ent.canCollide(false, this)) { if (Math.abs((bounds.x + bounds.width) - ent.bounds.x) < COLL_DETECT_EPSILON && ent.bounds.y < (bounds.y + bounds.height) && (ent.bounds.y + ent.bounds.height) > bounds.y && ent.canCollide(false, this)) {
face_RIGHT_isTouching = true; face_RIGHT_isTouching = true;
} }
if (Math.abs((ent.bounds.x + ent.bounds.width) - bounds.x) < F_EPSILON && ent.bounds.y < (bounds.y + bounds.height) && (ent.bounds.y + ent.bounds.height) > bounds.y && ent.canCollide(false, this)) { if (Math.abs((ent.bounds.x + ent.bounds.width) - bounds.x) < COLL_DETECT_EPSILON && ent.bounds.y < (bounds.y + bounds.height) && (ent.bounds.y + ent.bounds.height) > bounds.y && ent.canCollide(false, this)) {
face_LEFT_isTouching = true; face_LEFT_isTouching = true;
} }
} }
@ -273,17 +293,33 @@ public abstract class MovingEntity extends CRGameEntity {
if (moveByX(velocity.x * delta)) { if (moveByX(velocity.x * delta)) {
velocity.x = 0; velocity.x = 0;
} }
for (CRGameEntity ent : world.entities) {
if (ent == this) continue;
if (ent.bounds.overlaps(bounds) && ent.canCollide(true, this))
Gdx.app.error("Collision", "Collision after move X");
}
} }
if (velocity.y != 0) { if (velocity.y != 0) {
if (moveByY(velocity.y * delta)) { if (moveByY(velocity.y * delta)) {
velocity.y = 0; velocity.y = 0;
} }
for (CRGameEntity ent : world.entities) {
if (ent == this) continue;
if (ent.bounds.overlaps(bounds) && ent.canCollide(true, this))
Gdx.app.error("Collision", "Collision after Y");
}
} }
updateTouchCollisions(); updateTouchCollisions();
} }
public boolean isTouching_ANY() {
return face_TOP_isTouching || face_LEFT_isTouching || face_BOTTOM_isTouching || face_RIGHT_isTouching;
}
public boolean isTouching_TOP() { public boolean isTouching_TOP() {
return face_TOP_isTouching; return face_TOP_isTouching;
} }

View File

@ -73,37 +73,38 @@ public class PlayerEntity extends GravityEntity {
} }
private void updateRotation(float delta) { private void updateRotation(float delta) {
if (((!isTouching_BOTTOM() && getType == GetTriggerType.RUNBOTTOM)||(!isTouching_TOP() && getType == GetTriggerType.RUNTOP) )|| visualRotation % 90 != 0) { boolean touch = isTouching_ANY();
if (! isTouching_BOTTOM() && getType == GetTriggerType.RUNBOTTOM) { boolean aligned = (visualRotation % 90 == 0);
visualRotation = visualRotation + PLAYER_ROTATION_SPEED * delta;
visualRotation = (visualRotation + 720f) % 360f;
float p = (float) ((Math.max(Math.abs(Math.cos(Math.toRadians(visualRotation + 45))), Math.abs(Math.sin(Math.toRadians(visualRotation + 45)))) * Math.sqrt(PLAYER_WIDTH * PLAYER_WIDTH + PLAYER_HEIGHT * PLAYER_HEIGHT))); if (!touch)
updateHitBox(p, p); {
} visualRotation = visualRotation + PLAYER_ROTATION_SPEED * delta;
else if (!isTouching_TOP() && getType == GetTriggerType.RUNTOP){ visualRotation = (visualRotation + 720f) % 360f;
visualRotation = visualRotation + PLAYER_ROTATION_SPEED * delta;
visualRotation = (visualRotation + 720f) % 360f;
float p = (float) ((Math.max(Math.abs(Math.cos(Math.toRadians(visualRotation + 45))), Math.abs(Math.sin(Math.toRadians(visualRotation + 45)))) * Math.sqrt(PLAYER_WIDTH * PLAYER_WIDTH + PLAYER_HEIGHT * PLAYER_HEIGHT))); float p = (float) ((Math.max(Math.abs(Math.cos(Math.toRadians(visualRotation + 45))), Math.abs(Math.sin(Math.toRadians(visualRotation + 45)))) * Math.sqrt(PLAYER_WIDTH * PLAYER_WIDTH + PLAYER_HEIGHT * PLAYER_HEIGHT)));
updateHitBox(p, p); updateHitBox(p, p);
} }
else { else if (touch && aligned)
visualRotation = (visualRotation + 720f) % 90f; {
// all ok - move along
if (visualRotation < 45) }
visualRotation = visualRotation + PLAYER_ROTATION_SPEED * delta; else if (touch && !aligned)
else {
visualRotation = visualRotation - PLAYER_ROTATION_SPEED * delta; visualRotation = (visualRotation + 720f) % 90f;
if (visualRotation >= 90f || visualRotation <= 0f) if (visualRotation < 45) visualRotation = visualRotation + PLAYER_ROTATION_SPEED * delta;
visualRotation = 0f; else visualRotation = visualRotation - PLAYER_ROTATION_SPEED * delta;
visualRotation = (visualRotation + 720f) % 90f; if (visualRotation >= 90f || visualRotation <= 0f) visualRotation = 0f;
float p = (float) ((Math.max(Math.abs(Math.cos(Math.toRadians(visualRotation + 45))), Math.abs(Math.sin(Math.toRadians(visualRotation + 45)))) * Math.sqrt(PLAYER_WIDTH * PLAYER_WIDTH + PLAYER_HEIGHT * PLAYER_HEIGHT))); visualRotation = (visualRotation + 720f) % 90f;
updateHitBox(p, p);
} float p = (float) ((Math.max(Math.abs(Math.cos(Math.toRadians(visualRotation + 45))), Math.abs(Math.sin(Math.toRadians(visualRotation + 45)))) * Math.sqrt(PLAYER_WIDTH * PLAYER_WIDTH + PLAYER_HEIGHT * PLAYER_HEIGHT)));
updateHitBox(p, p);
}
else
{
Gdx.app.error("PlayerEntity", "updateRotation::WTF");
} }
} }