Search code examples
c#3dxnacollision-detectionpong

XNA Model Sphere Will Not Collide With One or All Of The Four Wall Meshes


As you can see from the screenshot below, my desired behavior is:

  • the sphere starts out in the middle of the arena
  • when you press enter it moves in a random direction
  • when it collides with one of the four walls it will bounce back to the opposite direction at a random angle between 0 and 45 degrees

The problem is which is not shown in the picture below is that the ball goes right through any of the walls, instead of colliding or bouncing.

I have tried updating the model position of the ball's sphere but it still won't cut it. I have tried setting the radius of the ball to a large number like 100 - but it collides when it starts moving before it even hits the wall and starts vibrating.

Visual Screenshot Of World Space

Source Code (Ball.cs):

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace _3D_Pong
{
    class Ball
    {
        private Model model;
        private Vector3 modelpos;
        private Random random = new Random();
        public Vector3 ModelPosition { get; set; }
        private Vector3 FowardDirection { get; set; }
        private float randomangle;
        private int direction = 0;
        private bool start = false;
        private int v;
        public Ball(Model m, Vector3 initial_position, int velocity = 30)
        {
            v = velocity;
            model = m;
            modelpos = initial_position;
            randomangle = MathHelper.ToRadians(random.Next(0, 45));
            direction = random.Next(1);
            FowardDirection = Matrix.CreateRotationY(randomangle).Forward;

        }
        public void BeginMoving()
        {
            start = true;
        }
        private BoundingSphere BallsBounds()
        {
            Matrix worldTransform = Matrix.CreateTranslation(modelpos);
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            ModelMesh mesh = model.Meshes[0];
            foreach (ModelMeshPart meshPart in mesh.MeshParts)
            {
                int vertexStride = meshPart.VertexBuffer.VertexDeclaration.VertexStride;
                int vertexBufferSize = meshPart.NumVertices * vertexStride;

                float[] vertexData = new float[vertexBufferSize / sizeof(float)];
                meshPart.VertexBuffer.GetData<float>(vertexData);

                for (int i = 0; i < vertexBufferSize / sizeof(float); i += vertexStride / sizeof(float))
                {
                    Vector3 transformedPosition = Vector3.Transform(new Vector3(vertexData[i], vertexData[i + 1], vertexData[i + 2]),  worldTransform);
                    min = Vector3.Min(min, transformedPosition);
                    max = Vector3.Max(max, transformedPosition);
                }
            }

            BoundingSphere sphere = BoundingSphere.CreateFromBoundingBox(new BoundingBox(min, max));
            return sphere;
        }
        public void Draw(Camera camera, ArenaRenderer arena)
        {
            if (start)
            {


                bool predicate1, predicate2, predicate3, predicate4;
                predicate1 = BallsBounds().Intersects(arena.FirstWall());
                predicate2 = BallsBounds().Intersects(arena.SecondWall());
                predicate3 = BallsBounds().Intersects(arena.ThirdWall());
                predicate4 = BallsBounds().Intersects(arena.FourthWall());
                if (predicate1 || predicate2 || predicate3 || predicate4)
                {
                    if (direction == 0)
                    {
                        direction = 1;
                        randomangle = MathHelper.ToRadians(random.Next(0, 45));
                        FowardDirection = Matrix.CreateRotationY(randomangle).Forward;

                    }
                    else if (direction == 1)
                    {
                        direction = 0;
                        randomangle = MathHelper.ToRadians(random.Next(0, 45));
                        FowardDirection = Matrix.CreateRotationY(randomangle).Forward;
                    }
                }
                if (direction == 1)
                {
                    modelpos += FowardDirection * v;
                }
                else
                {
                    modelpos -= FowardDirection * v;
                }
            }
            model.Draw(Matrix.CreateTranslation(modelpos), camera.View, camera.Projection);
        }
    }
}

Source (Arena.cs)

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace _3D_Pong
{
    class ArenaRenderer
    {
        private Model model;
        public ArenaRenderer(Model m)
        {
            model = m;
        }
        public BoundingBox FirstWall()
        {
            Matrix worldTransform = Matrix.CreateTranslation(Vector3.Zero);
            // Initialize minimum and maximum corners of the bounding box to max and min values
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            ModelMesh mesh = model.Meshes[0];
            foreach (ModelMeshPart meshPart in mesh.MeshParts)
            {
                // Vertex buffer parameters
                int vertexStride = meshPart.VertexBuffer.VertexDeclaration.VertexStride;
                int vertexBufferSize = meshPart.NumVertices * vertexStride;

                // Get vertex data as float
                float[] vertexData = new float[vertexBufferSize / sizeof(float)];
                meshPart.VertexBuffer.GetData<float>(vertexData);

                // Iterate through vertices (possibly) growing bounding box, all calculations are done in world space
                for (int i = 0; i < vertexBufferSize / sizeof(float); i += vertexStride / sizeof(float))
                {
                    Vector3 transformedPosition = Vector3.Transform(new Vector3(vertexData[i], vertexData[i + 1], vertexData[i + 2]), worldTransform);

                    min = Vector3.Min(min, transformedPosition);
                    max = Vector3.Max(max, transformedPosition);
                }
            }


            // Create and return bounding box
            return new BoundingBox(min, max);
        }
        public BoundingBox SecondWall()
        {
            Matrix worldTransform = Matrix.CreateTranslation(Vector3.Zero);
            // Initialize minimum and maximum corners of the bounding box to max and min values
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            ModelMesh mesh = model.Meshes[1];
            foreach (ModelMeshPart meshPart in mesh.MeshParts)
            {
                // Vertex buffer parameters
                int vertexStride = meshPart.VertexBuffer.VertexDeclaration.VertexStride;
                int vertexBufferSize = meshPart.NumVertices * vertexStride;

                // Get vertex data as float
                float[] vertexData = new float[vertexBufferSize / sizeof(float)];
                meshPart.VertexBuffer.GetData<float>(vertexData);

                // Iterate through vertices (possibly) growing bounding box, all calculations are done in world space
                for (int i = 0; i < vertexBufferSize / sizeof(float); i += vertexStride / sizeof(float))
                {
                    Vector3 transformedPosition = Vector3.Transform(new Vector3(vertexData[i], vertexData[i + 1], vertexData[i + 2]), worldTransform);

                    min = Vector3.Min(min, transformedPosition);
                    max = Vector3.Max(max, transformedPosition);
                }
            }


            // Create and return bounding box
            return new BoundingBox(min, max);
        }
        public BoundingBox ThirdWall()
        {
            Matrix worldTransform = Matrix.CreateTranslation(Vector3.Zero);
            // Initialize minimum and maximum corners of the bounding box to max and min values
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            ModelMesh mesh = model.Meshes[2];
            foreach (ModelMeshPart meshPart in mesh.MeshParts)
            {
                // Vertex buffer parameters
                int vertexStride = meshPart.VertexBuffer.VertexDeclaration.VertexStride;
                int vertexBufferSize = meshPart.NumVertices * vertexStride;

                // Get vertex data as float
                float[] vertexData = new float[vertexBufferSize / sizeof(float)];
                meshPart.VertexBuffer.GetData<float>(vertexData);

                // Iterate through vertices (possibly) growing bounding box, all calculations are done in world space
                for (int i = 0; i < vertexBufferSize / sizeof(float); i += vertexStride / sizeof(float))
                {
                    Vector3 transformedPosition = Vector3.Transform(new Vector3(vertexData[i], vertexData[i + 1], vertexData[i + 2]), worldTransform);

                    min = Vector3.Min(min, transformedPosition);
                    max = Vector3.Max(max, transformedPosition);
                }
            }


            // Create and return bounding box
            return new BoundingBox(min, max);
        }
        public BoundingBox FourthWall()
        {
            Matrix worldTransform = Matrix.CreateTranslation(Vector3.Zero);
            // Initialize minimum and maximum corners of the bounding box to max and min values
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            ModelMesh mesh = model.Meshes[3];
            foreach (ModelMeshPart meshPart in mesh.MeshParts)
            {
                // Vertex buffer parameters
                int vertexStride = meshPart.VertexBuffer.VertexDeclaration.VertexStride;
                int vertexBufferSize = meshPart.NumVertices * vertexStride;

                // Get vertex data as float
                float[] vertexData = new float[vertexBufferSize / sizeof(float)];
                meshPart.VertexBuffer.GetData<float>(vertexData);

                // Iterate through vertices (possibly) growing bounding box, all calculations are done in world space
                for (int i = 0; i < vertexBufferSize / sizeof(float); i += vertexStride / sizeof(float))
                {
                    Vector3 transformedPosition = Vector3.Transform(new Vector3(vertexData[i], vertexData[i + 1], vertexData[i + 2]), worldTransform);

                    min = Vector3.Min(min, transformedPosition);
                    max = Vector3.Max(max, transformedPosition);
                }
            }


            // Create and return bounding box
            return new BoundingBox(min, max);
        }

        public void Draw(Camera camera)
        {

            model.Draw(Matrix.CreateTranslation(Vector3.Zero), camera.View, camera.Projection);
        }
    }
}

I haven't implemented the paddles until I fix collision detection problem. If I am missing a piece of information please leave a comment, I have tried everything I can think of.

I have changed it so there is one function for each wall's bounds.

 public BoundingBox GetWallBounds(int index)
        {
            Matrix worldTransform = Matrix.CreateTranslation(Vector3.Zero);
            // Initialize minimum and maximum corners of the bounding box to max and min values
            Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);

            ModelMesh mesh = model.Meshes[index];
            foreach (ModelMeshPart meshPart in mesh.MeshParts)
            {
                // Vertex buffer parameters
                int vertexStride = meshPart.VertexBuffer.VertexDeclaration.VertexStride;
                int vertexBufferSize = meshPart.NumVertices * vertexStride;

                // Get vertex data as float
                float[] vertexData = new float[vertexBufferSize / sizeof(float)];
                meshPart.VertexBuffer.GetData<float>(vertexData);

                // Iterate through vertices (possibly) growing bounding box, all calculations are done in world space
                for (int i = 0; i < vertexBufferSize / sizeof(float); i += vertexStride / sizeof(float))
                {
                    Vector3 transformedPosition = Vector3.Transform(new Vector3(vertexData[i], vertexData[i + 1], vertexData[i + 2]), worldTransform);

                    min = Vector3.Min(min, transformedPosition);
                    max = Vector3.Max(max, transformedPosition);
                }
            }


            // Create and return bounding box
            return new BoundingBox(min, max);
        }

Solution

  • General Tip: In XNA you shouldn't do collision detection in the Draw method. This method might be called less times than 60 frames per second. You should do it in the Update method of your class. See here for the long explanation.

    I think your collision detection is incorrect. You need to reflect the direction angle not just pick a random direction.

    Also you need to add a velocity vector that will be added to your current position. It's a lot easier to work with such vector instead of trying to work with the position directly.

    There are tons of tutorials for implementing Pong in C#. This is just a sample:

    http://www.freewebs.com/campelmxna/XNATutorials/XNATut4.htm

    Note that there you have two different members, one represents the position and the other one the speed that will be added to the position each Update.

    Other than that it might be that the delta that you are adding to your ball position each time is too much so it's "skipping" the bounding walls. You can 1) decrease the delta or 2) make your bounding walls wider.