2
\$\begingroup\$

I've been fiddling around with 3D AABB collision in my voxel engine for the last couple of days, and every method I've come up with thus far has been almost correct, but each one never quite worked exactly the way I hoped it would.

Currently what I do is get two bounding boxes for my entity, one modified by the X translation component and the other by the Z component, and check if each collides with any of the surrounding chunks (chunks have their own octrees that are populated only with blocks that support collision). If there is a collision, then I cast out rays into that chunk to get the shortest collision distance, and set the translation component to that distance if the component is greater than the distance.

The problem is that sometimes collisions aren't even registered. Here's a video on YouTube that I created showing what I mean. I suspect the problem may be with the rays that I cast to get the collision distance not being where I think they are, but I'm not entirely sure what would be wrong with them if they are indeed the problem.

Here is my code for collision detection and response in the X direction (the Z direction is basically the same):

// create the XZ offset vector
Vector3 offsXZ = new Vector3(
    ( _translation.X > 0.0f ) ? SizeX / 2.0f : ( _translation.X < 0.0f ) ? -SizeX / 2.0f : 0.0f,
    0.0f,
    ( _translation.Z > 0.0f ) ? SizeZ / 2.0f : ( _translation.Z < 0.0f ) ? -SizeZ / 2.0f : 0.0f
);


// X physics
BoundingBox boxx = GetBounds( _translation.X, 0.0f, 0.0f );
if ( _translation.X > 0.0f )
{
    foreach ( Chunk chunk in surrounding )
    {
        if ( chunk.Collides( boxx ) )
        {
            float dist = GetShortestCollisionDistance( chunk, Vector3.Right, offsXZ ) - 0.0001f;
            if ( dist < _translation.X )
            {
                _translation.X = dist;
            }
        }
    }
}
else if ( _translation.X < 0.0f )
{
    foreach ( Chunk chunk in surrounding )
    {
        if ( chunk.Collides( boxx ) )
        {
            float dist = GetShortestCollisionDistance( chunk, Vector3.Left, offsXZ ) - 0.0001f;
            if ( dist < -_translation.X )
            {
                _translation.X = -dist;
            }
        }
    }
}

And here is my implementation for GetShortestCollisionDistance:

private float GetShortestCollisionDistance( Chunk chunk, Vector3 rayDir, Vector3 offs )
{
    int startY = (int)( -SizeY / 2.0f );
    int endY   = (int)(  SizeY / 2.0f );
    int incY   = (int)Cube.Size;

    float dist = Chunk.Size;
    for ( int y = startY; y <= endY; y += incY )
    {
        // Position is the center of the entity's bounding box
        Ray ray = new Ray(
            new Vector3(
                Position.X + offs.X,
                Position.Y + offs.Y + y,
                Position.Z + offs.Z
            ),
            rayDir
        );

        // Chunk.GetIntersections(Ray) returns Dictionary<Block, float?>
        foreach ( var pair in chunk.GetIntersections( ray ) )
        {
            if ( pair.Value.HasValue && pair.Value.Value < dist )
            {
                dist = pair.Value.Value;
            }
        }
    }

    return dist;
}

I realize some of this code can be consolidated to help with speed, but my main concern right now is to get this bit of physics programming to actually work.

\$\endgroup\$
1
\$\begingroup\$

I've solved it. It doesn't work perfectly with corners, but it works well enough for me right now.

What I'm doing is instead of only casting rays in the direction I'm traveling in, I'm also casting rays in the "opposite" direction so that I can check blocks in both +X/+Z and -X/-Z. Here's my updated code for the X direction (again, the Z direction is basically the same):

// create the XZ offset vectors
Vector3 offsX = new Vector3( Size.X / 2.0f, 0.0f, 0.0f );
Vector3 offsZ = new Vector3( 0.0f, 0.0f, Size.Z / 2.0f );


// X physics
BoundingBox boxx = GetBounds( _translation.X, 0.0f, 0.0f );
if ( _translation.X > 0.0f )
{
    foreach ( Chunk chunk in surrounding )
    {
        if ( chunk.Collides( boxx ) )
        {
            float dist = Math.Min(
                GetShortestCollisionDistance( chunk, Vector3.Right, offsX + offsZ ),
                GetShortestCollisionDistance( chunk, Vector3.Right, offsX - offsZ )
            ) - 0.0001f;
            if ( dist < _translation.X )
            {
                _translation.X = dist;
            }
        }
    }
}
else if ( _translation.X < 0.0f )
{
    foreach ( Chunk chunk in surrounding )
    {
        if ( chunk.Collides( boxx ) )
        {
            float dist = Math.Min(
                GetShortestCollisionDistance( chunk, Vector3.Left, -offsX + offsZ ),
                GetShortestCollisionDistance( chunk, Vector3.Left, -offsX - offsZ )
            ) - 0.0001f;
            if ( dist < -_translation.X )
            {
                _translation.X = -dist;
            }
        }
    }
}

Also, to help speed up the collision checking, I updated my chunks to only add exposed blocks to the octree instead of every block that supported collision.

\$\endgroup\$

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.