After much work trying to learn collision system theory and apply it, I've got my collision system working for the most part. The player can collide horizontally with no issue, sliding against walls, and can also jump and land with no problem 99.9% of the time. However, there are some cases where the player phases through a wall or floor, and it seems to be when combining horizontal and vertical movement that this occurs. However, most of the time it does not occur, so it has been difficult to narrow down the cause. I think it's a conditional/flow logic mistake, but I can't spot it.
Collision handling code:
Vec3 old = camera.Position; //store player position
if (!scene.BlockAt(camera.FootPosition)) camera.Velocity.Y -= .5f; //if there's no block below the camera, enact gravity
camera.Velocity.Y = Math.Max(camera.Velocity.Y, -10); //terminal velocity
camera.Move(camera.Velocity * (float)gameTime.ElapsedGameTime.TotalSeconds * camera.MoveSpeed); //move camera according to keyboard input
if (scene[camera.Position] != null) //if player is in a chunk
{
foreach (Block b in scene[camera.Position].Blocks) //iterate through blocks in the chunk
{
if (b != null && b.IsVisible) //ignore non-blocks and invisible blocks
{
Resolve(camera, b, old); //collision detection
}
}
}
else camera.Position = old; //don't let the player leave the playable area
camera.DiscardVelocity(true, false, true); //discard X/Z and keep Y velocity
Here's the "Resolve" function, the collision resolver itself:
void Resolve(ICollidable3 a, ICollidable3 b, Vec3 oldPos, int depth = 0)
{
Vec3 diff = b.Position - a.Position;
float adx = (float)Math.Abs(diff.X);
float ady = (float)Math.Abs(diff.Y);
float adz = (float)Math.Abs(diff.Z);
float adxz = adx + adz;
BoundingBox sweptB = new BoundingBox(new Vec3(b.BoundingBox.Min.X, 0, b.BoundingBox.Min.Z), b.BoundingBox.Max);
if (ady >= adxz) //if there's more vertical than horizontal (or they're equal), check collision and correct by resetting vertical (before running horizontal collision)
{
if (a.BoundingBox.Intersects(sweptB)) //check against "infinitely" tall bounding box to avoid falling through terrain.
a.Position = new Vec3(a.Position.X, oldPos.Y, a.Position.Z);
}
if (a.BoundingBox.Intersects(b.BoundingBox)) //if there's (still) collision
{
if (adx > adz) //if there's more X than Z
{
if (a.BoundingBox.Intersects(b.BoundingBox))
a.Position = new Vec3(oldPos.X, a.Position.Y, a.Position.Z); //reset the X
if (a.BoundingBox.Intersects(b.BoundingBox)) //if that didn't work
a.Position = new Vec3(a.Position.X, a.Position.Y, oldPos.Z); //reset the Z
}
else if (adx < adz) //if there's more Z than X
{
if (a.BoundingBox.Intersects(b.BoundingBox))
a.Position = new Vec3(a.Position.X, a.Position.Y, oldPos.Z); //reset the Z
if (a.BoundingBox.Intersects(b.BoundingBox)) //if that didn't work
a.Position = new Vec3(oldPos.X, a.Position.Y, a.Position.Z); //reset the X
}
else a.Position = new Vec3(oldPos.X, a.Position.Y, oldPos.Z);
}
if (ady < adxz) //if there's more horizontal than vertical, check collision and correct by resetting vertical (after running horizontal collision)
{
if (a.BoundingBox.Intersects(sweptB)) //check against "infinitely" tall bounding box to avoid falling through terrain.
a.Position = new Vec3(a.Position.X, oldPos.Y, a.Position.Z);
}
if (a.BoundingBox.Intersects(sweptB)) //if we fail to resolve the collision
{
if (++depth < 25) //if we have more passes left
Resolve(a, b, oldPos, depth); //keep trying
else
{
a.Position = oldPos; //otherwise, revert position
return; //abort if too deep
}
}
else return; //resolved
If it isn't obvious from reading the first code block, "a" is the camera and "b" is the block that the player is standing on (cube terrain). "sweptB" was implemented in an attempt to fix this behavior, basically extending the collision box for vertical collisions to stretch to the minimum Y (will cause issues later when doing collision from below, but I can handle that when I come to it with some conditions). This stopped the majority of the issues, but in certain cases it is possible to walk/land horizontally through blocks. I'm trying to use some variation on the separating axis theorem, separating things into X vs. Z and then XZ vs. Y. The issue seems to lay in XZ vs. Y..
Does anyone see any flaws in the conditional/flow logic for resolving vertical vs. horizontal collision and combinations thereof? I suspect that my problem may lay with this rather than the actual detection or response.