0
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ OK I found one major problem in my code, the rounding decisions when identifying the grid coordinates for a block from world coordinates were quite flawed - working to fix those right now, which should eliminate a lot of these bugs.. \$\endgroup\$ – Yushatak Sep 5 '14 at 14:53
  • \$\begingroup\$ Alright, found the actual issue here. My bounding boxes are created as Min = Pos - Size / 2 and Max = Pos + Size / 2. This is fine, and makes the physics "world" line up with the visual one if drawn. However, I am mixing physics with direct accesses to the arrays of blocks for performance enhancement, and that doesn't line up with the physics world due to the .5 offset. \$\endgroup\$ – Yushatak Sep 5 '14 at 15:15
0
\$\begingroup\$

adxz is not adx + adz

If adx and adz where vectors, it will work. But adx and adz are the distance of the vector in that dimension, when you add them you will have the vector (adx, 0, adz). You need to calculate the distance of that resulting vector.

You need to use pitagoras for calculating that.

adxz = Math.sqrt(adx*adx+adz*adz);
\$\endgroup\$
  • \$\begingroup\$ Which would of course be faster to square ady and skip the Sqrt call (which is how I did it). It seems to make things a little less wonky feeling but didn't fix the issue at hand. I suspect corner cases (which I also can in some specific situations get stuck on a corner even just with horizontal collisions) are to blame.. I suppose I should change one of the horizontal cases to be "or equal" so that it catches those, though I'm not sure why it would cause this behavior. tests this \$\endgroup\$ – Yushatak Sep 5 '14 at 14:24
  • \$\begingroup\$ Realized I already dealt with that case with the "else" block.. There's an oddity where you can jump into a block, landing on it and passing through to vertically collide not with the block but with the one below it and a bit closer to you. I think that the vertical collision isn't registered, allowing the camera to pass through the block and then only get stopped by the horizontal collision. However, while this stops the camera, it doesn't move it back out of the block because my collision system isn't a penetration-based one but a "don't let them penetrate" sort of one. \$\endgroup\$ – Yushatak Sep 5 '14 at 14:32
  • \$\begingroup\$ Since the block under the block in question is hidden, it is ignored for collision detection, allowing the camera to phase downward and fall. I suspect that the horizontal phasing is something similar but on the other axis. I also noticed that if you are holding down a horizontal direction, a jump will not occur, instead keeping the camera locked in place vertically. I suspect that one will be easy to fix, though. attempts \$\endgroup\$ – Yushatak Sep 5 '14 at 14:32
  • \$\begingroup\$ But wait.. the block under the first shouldn't matter because I extended the boundingbox down to Y=0 to avoid this very issue.. \$\endgroup\$ – Yushatak Sep 5 '14 at 14:37
0
\$\begingroup\$

Apparently the ultimate solution here was to subtract .5 from the position of each block as it was created so that the coordinates of the visual/physics worlds would line up with the block arrays. Changing the adxz calculation probably helped remove some bugs, but it wasn't related to the actual problem.

\$\endgroup\$
  • \$\begingroup\$ That was the biggest issue but for completeness I also had to fix an issue with my camera's "feet" Y level. I haven't fine-tuned it and figured out precisely what was wrong, but the bounding box and the actual Y coordinate were out of sync in some way. If you find yourself falling through blocks be sure to also check what's falling through the blocks! lol.. \$\endgroup\$ – Yushatak Sep 5 '14 at 19:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.