Sphere-Box Collision Issue

Hey everyone, new to this community and I’ve been messing around with MonoGame. Right now I’m focusing on collision detection and action. I have a sphere that falls and stays on a platform using BoundingSphere and BoundingBox respectively, but there seems to be an issue when the sphere goes to the edge it doesn’t detect the ground at certain points:

It’s happening at consistent areas as well. The sphere is a 1.0 radius bound and the ground is Min(-10.0, 0.1, -10.0) to Max(10.0, 0.1, 10.0). If I place the ball at exactly 9.0 as either positive or negative in the X or Z axis, the ball will fall completely through. But if I place the ball at 10.0 in the X or Z axis, it’s held up perfectly fine by the ground. It seems to fall through between 9.9~ to 9.0, and falls further down the closer to 9.0 it is. I’ve been trying to figure out on my own why it’s doing this but I can’t seem to think of any reason why. Initialize and Update code below:

Initialize:

    protected override void Initialize()
    {
        // Camera Coordinates Setup
        camPos = Vector3.Zero;
        camTarget = Vector3.Zero;
        // Mouse Setup
        Mouse.SetPosition(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2);
        ogMouseState = Mouse.GetState();
        // Matrix Setup
        projectionMat = Matrix.CreatePerspectiveFieldOfView(
            MathHelper.ToRadians(Constants.CAM_FOV), graphics.GraphicsDevice.Viewport.AspectRatio, 
            Constants.CAM_NEAR, Constants.CAM_FAR);
        viewMat = Matrix.CreateLookAt(camPos, camTarget, Vector3.Up);
        worldMat = Matrix.CreateWorld(Vector3.Zero, Vector3.Forward, Vector3.Up);
        // Load Models
        ground = Content.Load<Model>("Models/plane");
        basement = Content.Load<Model>("Models/plane");
        ball = Content.Load<Model>("Models/ball");
        // Setup Model Coords
        ballStart = Vector3.Up * Constants.BALL_STARTOFF + new Vector3(0.0f,0.0f,-0.0f);
        ballPos = ballStart;
        basementPos = new Vector3(0.0f, Constants.BASEMENT_OFFSETY, Constants.BASEMENT_OFFSETZ);
        camPos =  ballPos + new Vector3(0.0f, Constants.CAM_OFFSETY, -Constants.CAM_OFFSETZ);
        // Bounding Stuff
        groundBox = new BoundingBox(new Vector3(-10.0f, 0.1f, -10.0f), new Vector3(10.0f, 0.1f, 10.0f));
        basementBox = new BoundingBox(new Vector3(-10.0f, 0.1f, -10.0f) + basementPos, 
            new Vector3(10.0f, 0.1f, 10.0f) + basementPos);
        ballSphere = new BoundingSphere(ballPos, 1.0f);
        // Physics Setup
        accel = new Vector3(0.0f, -Constants.GRAV_Y, 0.0f);
        ballVel = Vector3.Zero;
        groundNormal = Vector3.Cross(new Vector3(-10.0f, 0.1f, -10.0f) - new Vector3(10.0f, 0.1f, -10.0f),
            new Vector3(-10.0f, 0.1f, -10.0f) - new Vector3(-10.0f, 0.1f, 10.0f));
        groundNormal.Normalize();
        // Dynamic Object Setup
        player = new GameObject("Player", ball, ballPos, ballSphere);
        x = 0;
        leftDown = false;
        ballHit = false;
        base.Initialize();
    }

Update:

    protected override void Update(GameTime gameTime) {
        var deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
            Exit();
        // Collision Detection
        if (ballSphere.Intersects(groundBox)) {
            ballVel = Vector3.Zero;
            // Move ball out of ground based on overlap distance
            float groundTop = 0.0f;
            float ballButtom = ballPos.Y - ballSphere.Radius;
            float overlap = groundTop - ballButtom;
            ballPos += overlap * -groundNormal;
        } else if (ballSphere.Intersects(basementBox)) {
            ballVel = Vector3.Zero;
            // Move ball out of ground based on overlap distance
            float groundTop = basementBox.Max.Y;
            float ballButtom = ballPos.Y - ballSphere.Radius;
            float overlap = groundTop - ballButtom;
            ballPos += overlap * -groundNormal;
        } else {
            // Physics
            ballPos += deltaTime * (ballVel + deltaTime * accel / 2.0f);
            ballVel += deltaTime * accel;
        }
        if (Keyboard.GetState().IsKeyDown(Keys.Space)) {
            ballPos = ballStart;
            ballVel = Vector3.Zero;
        }
        // Hit ball in direction
        if (Mouse.GetState().LeftButton == ButtonState.Pressed && !leftDown) {
            hitDirection = rotMat.Forward;
            ballHit = true;
            leftDown = true;
        } else if (Mouse.GetState().LeftButton == ButtonState.Released) {
            leftDown = false;
        }
        // Ball Hit
        if (ballHit) {
            ballPos += Vector3.Normalize(hitDirection) * Constants.BALL_HITSPD;
        }
        if (Mouse.GetState().RightButton == ButtonState.Pressed) {
            ballHit = false;
        }
        ballSphere.Center = ballPos;
        // Orbit camera around center
        currMouseState = Mouse.GetState();
        x += (float)currMouseState.X - (float)ogMouseState.X;
        rotMat = Matrix.CreateRotationY(MathHelper.ToRadians(-Constants.CAM_ROTSPD * x));
        Mouse.SetPosition(GraphicsDevice.Viewport.Width / 2, GraphicsDevice.Viewport.Height / 2);
        transRef = Vector3.Transform(new Vector3(0.0f, Constants.CAM_OFFSETY, -Constants.CAM_OFFSETZ), rotMat);
        camPos = transRef + ballPos;
        viewMat = Matrix.CreateLookAt(camPos, ballPos + (Vector3.Up * Constants.CAM_OFFSETVIEW), Vector3.Up);
        base.Update(gameTime);
    }

In the general case, I prefer to move the object, THEN solve collisions. Looks to me like the ball is just above the ground plane, then you move it and display it.

The new point is BELOW the ground plane, hence the bug.

Switching the order doesn’t seem to fix it. The bug is happening in very specific spots which confuses me even more.

I would first remove/outcomment the basementBox stuff from the collision detection, one box should be enough for now. If it’s still broken simplify your collision code to something like this:

if(ballPos.Y < 0)
{
    ballVel = Vector3.Zero;
    ballPos.Y = 0;
} else
...

This should be functionally pretty much equivalent to what you have now, except for the collision height. If it’s still broken after this change, you know that your problem is somewhere else, it has nothing to do with the collision code itself. If it works, you can then step by step change the code to become more like what you had before. This way you will find out which step exactly makes it break.

Add some debug drawing to verify that your collision sphere is really where you’re drawing that sphere at.

Your collision code is just fudging the sphere by its’ whole radius instead of determining the actual penetration, if the sphere you’re doing the collision test with is actually partially off the box then the code you have will cause behaviour like this.

Without looking at your current code, my best guess is that you are not updating the bounding sphere’s centre.

Make sure you are doing this before doing your collision check.

// Physics ballPos += deltaTime * (ballVel + deltaTime * accel / 2.0f); ballVel += deltaTime * accel;
ballSphere.Center = ballPos;

This is over complex.

    float ballButtom = ballPos.Y - ballSphere.Radius;
    float overlap = groundTop - ballButtom;
    ballPos += overlap * -groundNormal;

why not
ballPos = groundTop + ballSphere.Radius;

You only calculating the Y overlap what about X and Z ?
Your ball appears to move in at least two dimensions.

Your setting the new position hoping the new coordinates will be ok and not within a new collision.
Which is probably what is happening in your pic. As it is bouncing back and forth.

ballVel = Vector3.Zero; Ok
but i see nothing that reverses your velocity or changes its direction wont you just run right back into the collision the next frame ?

The above function relys on intersects which counts <= or >= the key part is = as collisions.
Your handling of the position on collision has no bias.

What is the ground normal’s value ?

You set the ballPos by a difference scalar and what i guess is a Vector normal that might push the position into the x or z ect, We have no idea what that is, what if that normal is not directly up ?

Your assuming that the collision is occuring in the Y dimension in your collision check.

float ballButtom = ballPos.Y - ballSphere.Radius;

From your own image the bugg is occuring on the edge.

Im a big fan of checking before and after collisions occur,
Where and when exactly did the collision occur ?
Exactly were is it going to end up ?
What changed as a result ?

Thanks everyone for the replies. I decided to start again from scratch (didn’t really have much anyway) and I’m going to keep all of your replies in consideration while I redo it.