Windows Phone Collision Detection #Part 2

Mapping a tapped location to 3D

To pick an object in a 3D game is relevant in terms of real-time strategy. For example, in StarCraft2, you can choose a construction from a 2D panel, then a semi-transparent 3D model will show up in the game view that lets you choose the best location for building the construction. Or, you can select your army by just clicking the left button of the mouse and drawing a rectangle covering the units you want to control. All of these happen between 2D and 3D. This is magic! Actually, this technique maps the clicking position in screen coordinate from 2D to 3D world. In this recipe, you will learn how this important mapping method works in the Windows Phone 7 game.

How to do it…

The following steps will lead you to make your own version of picking an object in a 3D game:

  1. Create the Windows Phone Game project named Pick3DModel, change Game1. cs to Pick3DModelGame.cs and add a new Marker.cs to the project. Then, we create a Content Pipeline Extension Library called ModelVerticesPipeline, replace ContentProcessor1.cs with ModelVerticesPipeline.cs. After that, add the model file BallLowPoly.FBX and image Marker.png to the content project.
  2. Create ModelVerticesProcessor in ModelVerticesProcessor.cs of the ModelVerticesPipeline project. Since the ray-triangle collision detection between ray and model needs the triangle information, in the extension model process, we will extract all of the model vertices and the model global bounding sphere. The returned bounding sphere will serve for the ray-sphere collision detection, before model ray-triangle collision detection, as a performance consideration; the extracted vertices will be used to generate the triangles for the model ray-triangle collision detection when the ray collides with the model bounding sphere. Add the definition of ModelVerticesProcessor class to ModelVerticesProcessor.cs.
  3. ModelVerticesProcessor inherits from the ModelProcessor for extracting extra model vertices. The beginning of the class should be:
    publicclass ModelVerticesProcessor : ModelProcessor {. . .}
    
  4. Add the variable vertices to store the model vertices in the ModelVerticesProcessor class field.
    List<Vector3> vertices = new List<Vector3>();
    
  5. Override the Process()method of the ModelVerticesProcessor class. This is the main method in charge of processing the content. In the method, we extract the model vertices and BoundingSphere, and store them into the ModelContent. Tag property as a Dictionary object.
    // Chain to the base ModelProcessor class.
    ModelContent model = base.Process(input, context);
    // Look up the input vertex positions.
    FindVertices(input);
    // Create a dictionary object to store the model vertices and
    // BoundingSphere
    Dictionary<string, object> tagData =
    new Dictionary<string, object>();
    model.Tag = tagData;
    // Store vertex information in the tag data, as an array of
    // Vector3.
    tagData.Add("Vertices", vertices.ToArray());
    // Also store a custom bounding sphere.
    tagData.Add("BoundingSphere",
    BoundingSphere.CreateFromPoints(vertices));
    return model;
    
  6. Define the FindVertices() method of the ModelVerticesProcessor class:
    // Helper for extracting a list of all the vertex positions in
    // a model.
    void FindVertices(NodeContent node)
    {
    // Convert the current NodeContent to MeshContent if it is
    // a mesh
    MeshContent mesh = node as MeshContent;
    if (mesh != null)
    {
    // Get the absolute transform of the mesh
    Matrix absoluteTransform = mesh.AbsoluteTransform;
    // Iterate every geometry in the mesh
    foreach (GeometryContent geometry in mesh.Geometry)
    {
    // Loop over all the indices in geometry.
    // Every group of three indices represents one
    // triangle.
    foreach (int index in geometry.Indices)
    {
    // Get the vertex position
    Vector3 vertex =
    geometry.Vertices.Positions[index];
    // Transform from local into world space.
    vertex = Vector3.Transform(vertex,
    absoluteTransform);
    // Store this vertex.
    vertices.Add(vertex);
    }
    }
    }
    // Recursively scan over the children of this node.
    foreach (NodeContent child in node.Children)
    {
    FindVertices(child);
    }
    }
    
  7. Now, build the ModelVerticesPipeline project. You will get the runtime library ModelVerticesProcessor.dll in which the ModelVerticesProcessor stores.
  8. In the next few steps, we will define the Marker class in Marker.cs of the Pick3DModel project.
  9. The Marker class inherits from DrawableGameComponent. Add the variables to the Marker class field:
    // SpriteBatch for drawing the marker texture
    SpriteBatch spriteBatch;
    // ContentManager for loading the marker texture
    ContentManager content;
    // Marker texture
    Texture2D texMarker;
    // Texture origin position for moving or rotation
    Vector2 centerTexture;
    // Texture position on screen
    publicVector2 position;
    
  10. Add the constructor of the Marker class.
    public Marker(Game game, ContentManager content)
    : base(game)
    {
    this.content = content;
    }
    
  11. Implement the LoadContent() method, which will load the marker texture and define the texture origin position.
    protected override void LoadContent()
    {
    spriteBatch = new SpriteBatch(GraphicsDevice);
    texMarker = content.Load<Texture2D>("Marker");
    centerTexture = new Vector2(texMarker.Width / 2,
    texMarker.Height / 2);
    base.LoadContent();
    }
    
  12. Let the marker inside the Windows Phone 7 screen. We define the Update() method to achieve this.
    // Calculate where the marker's position is on the screen. The
    // position is clamped to the viewport so that the marker
    // can't go off the screen.
    publicoverridevoid Update(GameTime gameTime)
    {
    TouchCollection touches = TouchPanel.GetState();
    if (touches.Count > 0 && touches[0].State ==
    TouchLocationState.Pressed)
    {
    position.X = touches[0].Position.X;
    position.Y = touches[0].Position.Y;
    }
    base.Update(gameTime);
    }
    
  13. Define the CalculateMarkerRay() method that calculates a world space ray starting at the camera’s eye and pointing in the direction of the cursor. The Viewport.Unproject() method is used to accomplish this.
    publicRay CalculateMarkerRay(Matrix projectionMatrix,
    Matrix viewMatrix)
    {
    // Create 2 positions in screenspace using the tapped
    // position. 0 is as close as possible to the camera, 1 is
    // as far away as possible.
    Vector3 nearSource = newVector3(position, 0f);
    Vector3 farSource = newVector3(position, 1f);
    // Use Viewport.Unproject to tell what those two screen
    // space positions would be in world space.
    Vector3 nearPoint =
    GraphicsDevice.Viewport.Unproject(nearSource,
    projectionMatrix, viewMatrix, Matrix.Identity);
    Vector3 farPoint =
    GraphicsDevice.Viewport.Unproject(farSource,
    projectionMatrix, viewMatrix, Matrix.Identity);
    // Find the direction vector that goes from the nearPoint
    // to the farPoint and normalize it
    Vector3 direction = farPoint - nearPoint;
    direction.Normalize();
    // Return a new ray using nearPoint as the source.
    returnnewRay(nearPoint, direction);
    }
    
  14. From this step we begin to compute the ray-model collision and draw the collided triangle and model mesh on Windows Phone 7 in the game main class Pick3DModelGame. Now, insert the lines to the class field as data member:
    // Marker Ray
    Ray markerRay;
    // Marker
    Marker marker;
    // Model object and model world position
    Model modelObject;
    Matrix worldModel = Matrix.Identity;
    // Camera view and projection matrices
    Matrix viewMatrix;
    Matrix projectionMatrix;
    // Define the picked triangle vertex array
    VertexPositionColor[] pickedTriangle =
    {
    newVertexPositionColor(Vector3.Zero, Color.Black),
    newVertexPositionColor(Vector3.Zero, Color.Black),
    newVertexPositionColor(Vector3.Zero, Color.Black),
    };
    // Vertex array to represent the selected model
    VertexPositionColor[] verticesModel;
    VertexBuffer vertexBufferModel;
    // The flag indicates whether the ray collides with the model
    float? intersection;
    // The effect of the object is to draw the picked triangle
    BasicEffectwireFrameEffect;
    // The wire frame render state
    staticRasterizerState WireFrame = newRasterizerState
    {
    FillMode = FillMode.WireFrame,
    CullMode = CullMode.None
    };
    
  15. Initialize the camera, marker, and wireFrameEffect. Add the code to the Initialize() method:
    // Intialize the camera
    viewMatrix = Matrix.CreateLookAt(new Vector3(0, 5, 15),
    Vector3.Zero, Vector3.Up);
    projectionMatrix = Matrix.CreatePerspectiveFieldOfView(
    MathHelper.ToRadians(45.0f),
    GraphicsDevice.Viewport.AspectRatio, .01f, 1000);
    // Initialize the marker
    marker = new Marker(this, Content);
    Components.Add(marker);
    wireFrameEffect = new BasicEffect(graphics.GraphicsDevice);
    
  16. Load the ball model and read the ball vertices. Then initialize the vertex array of the ball model for drawing the ball mesh on the Windows Phone 7 screen. Insert the following code in to the LoadContent() method:
    // Load the ball object
    modelObject = Content.Load<Model>("BallLowPoly");
    // Read the vertices
    Dictionary<string, object> tagData =
    (Dictionary<string, object>)modelObject.Tag;
    Vector3[] vertices = (Vector3[])tagData["Vertices"];
    // Initialize the model vertex array for drawing on screen
    verticesModel = new VertexPositionColor[vertices.Length];
    for (int i = 0; i < vertices.Length; i++)
    {
    verticesModel[i] =
    new VertexPositionColor(vertices[i], Color.Red);
    }
    vertexBufferModel = new VertexBuffer(
    GraphicsDevice, VertexPositionColor.VertexDeclaration,
    vertices.Length, BufferUsage.WriteOnly);
    vertexBufferModel.SetData(verticesModel);
    
  17. Define the ray-model collision detection method UpdatePicking() in the Pick3DModelGame class.
    void UpdatePicking()
    {
    // Look up a collision ray based on the current marker
    // position.
    markerRay = marker.CalculateMarkerRay(projectionMatrix,
    viewMatrix);
    // Keep track of the closest object we have seen so far,
    // so we can choose the closest one if there are several
    // models under the cursor.
    float closestIntersection = float.MaxValue;
    317
    Vector3 vertex1, vertex2, vertex3;
    // Perform the ray to model intersection test.
    intersection = RayIntersectsModel(markerRay, modelObject,
    worldModel, out vertex1, out vertex2,out vertex3);
    // Check whether the ray-model collistion happens
    if (intersection != null)
    {
    // If so, is it closer than any other model we might
    // have previously intersected?
    if (intersection < closestIntersection)
    {
    // Store information about this model.
    closestIntersection = intersection.Value;
    // Store vertex positions so we can display the
    // picked triangle.
    pickedTriangle[0].Position = vertex1;
    pickedTriangle[1].Position = vertex2;
    pickedTriangle[2].Position = vertex3;
    }
    }
    }
    
  18. Define the RayIntersectsModel() method in the Pick3DModelGame class:
    float? RayIntersectsModel(Ray ray, Model model, Matrix
    modelTransform, out Vector3 vertex1, out Vector3 vertex2,
    out Vector3 vertex3)
    {
    bool insideBoundingSphere;
    vertex1 = vertex2 = vertex3 = Vector3.Zero;
    Matrix inverseTransform = Matrix.Invert(modelTransform);
    ray.Position = Vector3.Transform(ray.Position,
    inverseTransform);
    ray.Direction = Vector3.TransformNormal(ray.Direction,
    inverseTransform);
    // Look up our custom collision data from the Tag property
    // of the model.
    Dictionary<string, object> tagData =
    (Dictionary<string, object>)model.Tag;
    BoundingSphere boundingSphere =
    (BoundingSphere)tagData["BoundingSphere"];
    if (boundingSphere.Intersects(ray) == null)
    {
    // If the ray does not intersect the bounding sphere,
    // there is no need to do the the ray-triangle
    // collision detection
    insideBoundingSphere = false;
    return null;
    }
    else
    {
    // The bounding sphere test passed, do the ray-
    // triangle test
    insideBoundingSphere = true;
    // Keep track of the closest triangle we found so far,
    // so we can always return the closest one.
    float? closestIntersection = null;
    // Loop over the vertex data, 3 at a time for a
    // triangle
    Vector3[] vertices = (Vector3[])tagData["Vertices"];
    for (int i = 0; i < vertices.Length; i += 3)
    {
    // Perform a ray to triangle intersection test.
    float? intersection;
    RayIntersectsTriangle(ref ray,
    ref vertices[i],
    ref vertices[i + 1],
    ref vertices[i + 2],
    out intersection);
    // Does the ray intersect this triangle?
    if (intersection != null)
    {
    // If so, find the closest one
    if ((closestIntersection == null) ||
    (intersection < closestIntersection))
    {
    // Store the distance to this triangle.
    closestIntersection = intersection;
    // Transform the three vertex positions
    // into world space, and store them into
    // the output vertex parameters.
    Vector3.Transform(ref vertices[i],
    ref modelTransform, out vertex1);
    Vector3.Transform(ref vertices[i + 1],
    ref modelTransform, out vertex2);
    Vector3.Transform(ref vertices[i + 2],
    ref modelTransform, out vertex3);
    }
    }
    }
    return closestIntersection;
    }
    }
    
  19. Draw the ball on the Windows Phone 7 screen with the model heighted wireframe and the picked triangle. Add the following code to the Draw() method:
    GraphicsDevice.BlendState = BlendState.Opaque;
    GraphicsDevice.DepthStencilState = DepthStencilState.Default;
    // Draw model
    DrawModel(modelObject, worldModel);
    // Draw the model wire frame
    DrawPickedWireFrameModel();
    // Draw the outline of the triangle under the cursor.
    DrawPickedTriangle();
    
  20. Now we should give the definitions of the called methods:
    DrawPickedWireFrameModel(), DrawPickedTriangle(), and DrawModel().
    
  21. Define the DrawPickedWireFrameModel() method in the Pick3DModelGame class:
    void DrawPickedWireFrameModel()
    {
    if (intersection != null)
    {
    GraphicsDevice device = graphics.GraphicsDevice;
    device.RasterizerState = WireFrame;
    device.DepthStencilState = DepthStencilState.None;
    // Activate the line drawing BasicEffect.
    wireFrameEffect.Projection = projectionMatrix;
    wireFrameEffect.View = viewMatrix;
    wireFrameEffect.CurrentTechnique.Passes[0].Apply();
    // Draw the triangle.
    device.DrawUserPrimitives(PrimitiveType.TriangleList,
    verticesModel, 0, verticesModel.Length / 3);
    // Reset renderstates to their default values.
    device.RasterizerState =
    RasterizerState.CullCounterClockwise;
    device.DepthStencilState = DepthStencilState.Default;
    }
    }
    
  22. Implement the DrawPickedTriangle() method in the Pick3DModelGame class:
    [ocde]
    void DrawPickedTriangle()
    {
    if (intersection != null)
    {
    GraphicsDevice device = graphics.GraphicsDevice;
    // Set line drawing renderstates. We disable backface
    // culling and turn off the depth buffer because we
    // want to be able to see the picked triangle outline
    // regardless of which way it is facing, and even if
    // there is other geometry in front of it.
    device.RasterizerState = WireFrame;
    device.DepthStencilState = DepthStencilState.None;
    // Activate the line drawing BasicEffect.
    wireFrameEffect.Projection = projectionMatrix;
    wireFrameEffect.View = viewMatrix;
    wireFrameEffect.VertexColorEnabled = true;
    wireFrameEffect.CurrentTechnique.Passes[0].Apply();
    // Draw the triangle.
    device.DrawUserPrimitives(PrimitiveType.TriangleList,
    pickedTriangle, 0, 1);
    // Reset renderstates to their default values.
    device.RasterizerState =
    RasterizerState.CullCounterClockwise;
    device.DepthStencilState = DepthStencilState.Default;
    }
    }
  23. Give the definition of the DrawModel() method in the Pick3DModelGame class:
    private void DrawModel(Model model, Matrix worldTransform)
    {
    Matrix[] transforms = new Matrix[model.Bones.Count];
    model.CopyAbsoluteBoneTransformsTo(transforms);
    foreach (ModelMesh mesh in model.Meshes)
    {
    foreach (BasicEffect effect in mesh.Effects)
    {
    effect.EnableDefaultLighting();
    effect.PreferPerPixelLighting = true;
    effect.View = viewMatrix;
    effect.Projection = projectionMatrix;
    effect.World = transforms[mesh.ParentBone.Index] *
    worldTransform;
    }
    mesh.Draw();
    }
    }
    
  24. Now, build and run the application. It should run as shown in the following screenshots:
    Mapping a tapped location

How it works...

In step 5, after processing the model basic information as usual in the base class, we call the FindVertices()method to get all of the model vertices. After that, the dictionary object tagData will receive the vertices information and the generated BoundingSphere from the model vertices, the tagData will be assigned to ModelContent.Tag for the game application to read the model vertices and BoundingSphere from the model XNB file.

In step 6, the first line is to convert the current NodeContent to MeshContent if the current ModelContent holds a model mesh and not a bone or other types. If mesh, an object of MeshContent, is not null, we begin to extract its vertices. First of all, the code reads the mesh.AbsoluteTransform for transforming the model vertices from object coordinate to world coordinate. Then, we iterate the geometry of the current mesh to get the vertices. In the loop for looping over each of the vertices, we use the Vector3.Transform() with absoluteTransform matrix to actually transform the vertex from object coordinate to world. After that, the transformed vertex will be saved to the vertices collection. When all of the vertices of the current mesh are processed, the code will deal with the current child content for retrieving the vertices.

In step 8, the spriteBatch is the main object in charge of rendering the texture on the Windows Phone 7 screen; the content object of ContentManager manages the game contents; texMarker represents the marker texture; the centerTexture specifies the origin point of texture for rotating and moving; the variable position holds the texture position on screen.

In step 10, the constructor receives the Game and ContentManager objects. The game object provides GraphicsDevice and the content offers access to the texture file.

In step 13, this is the key method to generate the ray from the screen coordinates to the world coordinates. The nearSource is used for generating the closest point to the camera; farSource is for the point that is far away. Then, call the Viewport.Unproject() method to generate the nearPoint and farPoint. After that, convert the nearSource and farSource from screen space to the nearPoint and farPoint in world space. Next, use the unprojected points farPoint and nearPoint to compute the ray direction. Finally, return the new ray object with the nearPoint and normalized direction.

In step 14, the markerRay specifies the ray from the tapped position to world space; marker is the visual sign on screen that indicates the start point of markerRay; modelObject will load the model; worldModel stands for the transformation matrix of modelObject; the view and projection will be used to initialize the camera and help generate the markerRay; pickedTriangle is the triangle vertex array which will be used to draw the triangle on the model where it collides with the markerRay; the verticesModel reads and stores all of the model vertices and will serve the picked model draw in wireframe; intersection indicates the collision state. If not null, the value is the distance between the intersection point and the markerRay start point. The final WireFrame defines the device render state.

Implementing sphere-triangle collision detection

In FPS game, when the character moves forward to a building or a wall and contacts the object, it will stop and stand there. And you know there is no object around you, because the camera is your eye in the FPS game. If you wonder how the game developers achieve this, you will find the answer in this recipe.

How to do it...

The following steps will show you the best practice of applying the sphere-triangle collision detection for first-person perspective camera:

  1. Create a Windows Phone Game project named CameraModelCollision, change Game1.cs to CameraModelCollisionGame.cs. Meanwhile, add Triangle. cs and TriangleSphereCollisionDetection.cs to the project. Then, create a Content Pipeline Extension Library project named MeshVerticesProcessor and replace the ContentProcessor1.cs with MeshVerticesProcessor.cs. After that, insert the 3D model file BigBox.fbx and sprite font file gameFont. spriteFont to the content project.
  2. Define the MeshVerticesProcessor class in MeshVerticesProcessor.cs of MeshVerticesProcessor project. The class is the same as the processor defined in the Implementing BoundingSphere collision detection in a 3D game recipe.
  3. Implement the Triangle class in Triangle.cs in the CameraModelCollision project.
    Declare the necessary data members of the Triangle class. Add the following lines to the class field:

    // The triangle corners
    public Vector3 A;
    public Vector3 B;
    public Vector3 C;
    
  4. Define the constructors for the class:
    // Constructor
    public Triangle()
    {
    A = Vector3.Zero;
    B = Vector3.Zero;
    C = Vector3.Zero;
    }
    // Constructor
    public Triangle(Vector3 v0, Vector3 v1, Vector3 v2)
    {
    A = v0;
    B = v1;
    C = v2;
    }
    
  5. Implement the Normal class of the Triangle class. This method returns a unit length normal vector perpendicular to the plane of the triangle.
    public void Normal(outVector3 normal)
    {
    normal = Vector3.Zero;
    Vector3 side1 = B - A;
    Vector3 side2 = C - A;
    normal = Vector3.Normalize(Vector3.Cross(side1, side2));
    }
    
  6. Define the InverseNormal() method of the Triangle class. This method gets a normal that faces away from the point specified (faces in).
    // Get a normal that faces away from the point specified // (faces in) public void InverseNormal(ref Vector3 point, out Vector3 inverseNormal) { Normal(out inverseNormal); // The direction from any corner of the triangle to the //point Vector3 inverseDirection = point - A; // Roughly facing the same way
    if (Vector3.Dot(inverseNormal, inverseDirection) > 0)
    {
    // Same direction therefore invert the normal to face
    // away from the direction to face the point
    Vector3.Multiply(ref inverseNormal, -1.0f,
    out inverseNormal);
    }
    }
    
  7. Create the TriangleSphereCollisionDetection class. This class contains the methods to take the triangle sphere collision detection.
    Define the IsSphereCollideWithTringles() method. This method is the root method that kicks off the sphere triangle collision detection:

    public static bool IsSphereCollideWithTringles(
    List<Vector3> vertices,
    BoundingSphere boundingSphere, out Triangle triangle)
    {
    bool result = false;
    triangle = null;
    for (int i = 0; i < vertices.Count; i += 3)
    {
    // Create triangle from the tree vertices
    Triangle t = new Triangle(vertices[i], vertices[i + 1],
    vertices[i + 2]);
    // Check if the sphere collides with the triangle
    result = SphereTriangleCollision(ref boundingSphere,
    ref t);
    if (result)
    {
    triangle = t;
    return result;
    }
    }
    return result;
    }
    
  8. Implement the SphereTriangleCollision() method. This method will generate a ray from the center of the sphere and perform the ray-triangle collision check:
    private static bool SphereTriangleCollision(
    ref BoundingSphere sphere, ref Triangle triangle)
    {
    Ray ray = new Ray();
    ray.Position = sphere.Center;
    // Create a vector facing towards the triangle from the
    // ray starting point.
    Vector3 inverseNormal;
    triangle.InverseNormal(
    ref ray.Position, out inverseNormal);
    ray.Direction = inverseNormal;
    // Check if the ray hits the triangle
    float? distance = RayTriangleIntersects(ref ray,
    ref triangle);
    if (distance != null && distance > 0 &&
    distance <= sphere.Radius)
    {
    // Hit the surface of the triangle
    return true;
    }
    return false;
    }
    
  9. Give the definition of RayTriangleIntersects() to the TriangleSphereCollisionDetection class. This is the method that performs the ray-triangle collision detection and returns a distance value if the collision takes place:
    public static float? RayTriangleIntersects(ref Ray ray, ref
    Triangle triangle)
    {
    float? result;
    RayIntersectsTriangle(ref ray, ref triangle.A,
    ref triangle.B, ref triangle.C, out result);
    return result;
    }
    
  10. Add MeshVerticesProcessor.dll to the content project reference list, and change the processor of BigBox.FBX to MeshVerticesProcessor, as shown in the following screenshot:
    the processor of BigBox.FBX to MeshVerticesProcessor
  11. From this step, we will begin to take the real-time collision between the camera bounding sphere and the model in the main game project CameraModelCollision. Add the code to the CameraModelCollision class field:
    // SpriteFont for showing instructions
    SpriteFont font;
    // Box model
    Model modelBox;
    // Box model world transformation
    Matrix worldBox = Matrix.Identity;
    // Camera position and look at target
    Vector3 cameraPosition;
    Vector3 targetOffset;
    // Camera view and projection matrices
    public Matrix view;
    public Matrix projection;
    // Camera BoundingSphere
    BoundingSphere boundingSphereCamera;
    // Vertices of Box Model
    List<Vector3> verticesBox;
    // Collided triangle
    Triangle triangleCollided;
    // Normal of collided triangle
    Vector3 normalTriangle;
    // The moving forward flag
    bool ForwardCollide;
    bool BackwardCollide;
    // The top and bottom hit regions on screen
    Rectangle TopHitRegion;
    Rectangle BottomHitRegion;
    
  12. Initialize the camera and hit regions. Add the code to the Initialize() method in the CameraModelCollision class:
    // Initialize camera
    cameraPosition = new Vector3(0, 5, 50);
    targetOffset = new Vector3(0, 0, -1000);
    view = Matrix.CreateLookAt(cameraPosition, targetOffset,
    Vector3.Up);
    projection = Matrix.CreatePerspectiveFieldOfView(
    MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio,
    0.1f, 1000.0f);
    // Initialize the top and bottom hit regions
    Viewport viewport = GraphicsDevice.Viewport;
    TopHitRegion = new Rectangle(0, 0, viewport.Width,
    viewport.Height / 2);
    BottomHitRegion = new Rectangle(0, viewport.Height / 2,
    viewport.Width, viewport.Height / 2);
    
  13. Load the box model and initialize the camera bounding sphere. Insert the following code into the LoadContent() method in the CameraModelCollision class:
    // Load the game font font = Content.Load<SpriteFont>("gameFont"); // Load the box model modelBox = Content.Load<Model>("BigBox");
    // Get the vertex collection of box model
    verticesBox = ((Dictionary<string,
    List<Vector3>>)modelBox.Tag)["Box001"];
    // Create the BoundingSphere of camera
    boundingSphereCamera = new BoundingSphere(cameraPosition, 5);
    
  14. Move the camera and take camera-sphere collision detection. Insert the code into the Update()method in the CameraModelCollision class.
    // Check whether the tapped position is inside the TopHitRegion
    // or BottomHitRegion
    TouchCollection touches = TouchPanel.GetState();
    if (touches.Count > 0 && touches[0].State ==
    TouchLocationState.Pressed)
    {
    Point point = new Point((int)touches[0].Position.X,
    (int)touches[0].Position.Y);
    if (TopHitRegion.Contains(point))
    {
    if (!ForwardCollide)
    {
    // If the tapped position is within the
    // TopHitRegion and the camera has not collided
    // with the model, move the camera forward
    view.Translation += new Vector3(0, 0, 1);
    }
    }
    if (BottomHitRegion.Contains(point))
    {
    // If the tapped position is within the
    // BottomHitRegion and the camera has not
    // collided with the model, move the camera
    // backward
    if (!BackwardCollide)
    {
    view.Translation -= new Vector3(0, 0, 1);
    }
    }
    }
    // Update the center position of camera bounding sphere
    boundingSphereCamera.Center = view.Translation;
    // Detect the collision between camera bounding sphere and
    // model triangles
    TriangleSphereCollisionDetection.IsSphereCollideWithTriangles(
    verticesBox, boundingSphereCamera,
    out triangleCollided);
    // If the collision happens, the collided triangle
    // is not null
    if (triangleCollided != null)
    {
    // Get the normal of the collided triangle
    triangleCollided.Normal(out normalTriangle);
    // Get the direction from the center of camera
    // BoundingSphere to the collided triangle
    Vector3 Direction = view.Translation - triangleCollided.A;
    // If the camera faces the model, the dot
    // product between the triangle normal
    // and direction is less than 0
    float directionChecker =
    Vector3.Dot(normalTriangle, Direction);
    if (directionChecker < 0)
    {
    ForwardCollide = true;
    }
    }
    else
    {
    ForwardCollide = false;
    }
    
  15. Define the DrawModel() method in the CameraModelCollision class to draw the 3D model.
    // Draw model
    public void DrawModel(Model model, Matrix world, Matrix view,
    Matrix projection)
    {
    Matrix[] transforms = new Matrix[model.Bones.Count];
    model.CopyAbsoluteBoneTransformsTo(transforms);
    foreach (ModelMesh mesh in model.Meshes)
    {
    foreach (BasicEffect effect in mesh.Effects)
    {
    effect.PreferPerPixelLighting = true;
    effect.EnableDefaultLighting();
    effect.DiffuseColor = Color.White.ToVector3();
    effect.World = transforms[mesh.ParentBone.Index] *
    world;
    effect.View = view;
    effect.Projection = projection;
    }
    mesh.Draw();
    }
    }
    
  16. Draw the model and instructions on the Windows Phone 7 screen. Add the code to the Draw() method:
    // Draw the box model
    DrawModel(modelBox, worldBox, view, projection);
    // Draw the instructions
    spriteBatch.Begin();
    spriteBatch.DrawString(font, "1.Tap the top half of screen"
    + "for moving the camera forward\n2.Tap the bottom half"
    + "of" screen for moving the camera backward.",
    new Vector2(0, 0), Color.White);
    spriteBatch.End();
    
  17. Now, build and run the application. It should run as shown in the following screenshots:
    sphere-triangle

How it works...

In step 3, A, B, and C represent the three corners of a triangle; you can use them to calculate the triangle edges.

In step 5, first we calculate the two edges of the triangle. Then we use the Vector3. Cross() method to get the normal vector and the Vector3.Normalize() method to normalize the normal vector to a unit length vector.

In step 6, first we get the normal of the triangle. Then, calculate the direction from the triangle corner A to the point outside the triangle. After that, we examine the return value of the Vector3.Dot() method between the triangle normal vector and the direction from the triangle to the outside point. If the dot product is greater than 0, this means the two vectors are in the same direction or on the same side.

In step 7, this method goes through all of the vertices of a model and creates a triangle in every three vertices. With the triangle t and the given boundingSphere, it calls the SphereTriangleCollision() method to take the sphere triangle collision detection. If the result is true, it means the sphere triangle collision happens and the collided triangle will be returned. If not true, the method return value will be false and the triangle t will be null.

In step 8, the first line is to initialize a ray object with the original information. Then, we assign the translation of the sphere center to the ray.Position. After that, we use the Triangle. InverseNormal() method for getting the direction from the point to the current triangle. Now, the ray is ready, the next part is to take the core ray triangle collision detection using the RayTriangleIntersects() method. If the returned distance is not null, greater than zero and less than the radius of the given bounding sphere, a ray triangle collision happens. The method will return true to the caller.

In step 9, insert the definition of the inner RayIntersectsTriangle() method to the class, which we had discussed in the Implementing ray-triangle collision detection recipe in this chapter. Refer to the recipe for a detailed explanation.

In step 11, the font is responsible for showing the instructions; the modelBox loads the box model; worldBox stands for the transformation of the box model; the following four variables cameraPosition, targetOffset, view, and projection are used to initialize the camera; boundingSphereCamera is the bounding sphere around the camera; verticesBox holds the vertices of the box model; triangleCollided specifies the triangle when sphere-triangle collision happens; normalTriangle stores the normal vector of the collided triangle; ForwardCollide and BackwardCollide show that the camera is moving forward or backward; TopHitRegion and BottomHitRegion are the hit regions if you want to move the camera forward or backward.

In step 12, the camera target is -1000 at the Z-axis for realistic viewing when you move the camera. TopHitRegion occupies the top half of the screen; BottomHitRegion takes up the bottom half of the screen.

In step 13, after loading the box model and getting its vertices, we initialize the boundingSphereCamera with a radius of about five units at the camera position.

In step 14, the first part is to check whether the tapped position is inside the TopHitRegion or BottomHitRegion to move the camera forward or backward. After that, we should update the position of the camera bounding sphere, as this is important for us to take the collision detection between camera bounding sphere and model triangles. In the next line, we call the TriangleSphereCollisionDetection.IsSphereCollideWithTriangles() method to detect the collision detection. If the returned triangle is not null, we will calculate the dot product between the camera ray direction and the normal of the collided triangle. If it is less than zero, it means the camera is moving forward, otherwise, it is moving backward.

Making a 3D ball move along a curved surface

No doubt, the real modern 3D games are much more complex; they are not a simple ball or a box with a few triangles. Thousands of polygons for games is common, millions is not unheard of. As a technique, the differences in how to do collision detection between different shape objects are not that much. You should already know the core concept or idea behind how to do it. In this recipe, you will learn the idea of dealing with collisions between models of different shapes.

How to do it...

The following steps will show you how to perform collision detection between a ball and a curved surface:

  1. Create a Windows Phone Game project named BallCollideWithCurve, change Game1.cs to BallCollideWithCurveGame.cs. Then, add triangle.cs and TriangleSphereCollisionDetection.cs to the project. Next, add the Content Pipeline Extension Project named MeshVerticesProcessor, replace the ContentProcessor1.cs with MeshVerticesProcessor.cs. After that, insert the model file ball.FBX and CurveSurface.FBX to the content project.
  2. Define the MeshVerticesProcessor in MeshVerticesProcessor.cs of the MeshVerticesProcessor project. The class definition is the same as the class in the Implementing BoundingBox collision detection in a 3D game recipe in this chapter. For the full explanation, please refer to that recipe.
  3. Define Triangle in Triangle.cs and TriangleSphereCollisionDetection in TriangleSphereCollisionDetection.cs of the CameraModelCollision project. The two class definitions are the same as the classes implemented in the last Implementing sphere-triangle collision detection recipe in this chapter. For a full explanation, please take a look at that recipe.
  4. Change the processor of ball.FBX and CurveSurface.FBX in content project, as shown in the following screenshot:
    ball.FBX and CurveSurface.FBX
  5. Now it is time to draw the ball and curve surface models on screen and take the collision detection in the main game project BallCollideWithCurve. First, add the following lines to the class field:
    // Ball model and the world transformation matrix
    Model modelBall;
    Matrix worldBall = Matrix.Identity;
    // Curve model and the world transformation matrix
    Model modelSurface;
    Matrix worldSurface = Matrix.Identity;
    // Camera
    Vector3 cameraPosition;
    publicMatrix view;
    publicMatrix projection;
    // The bounding sphere of ball model
    BoundingSphere boundingSphereBall;
    // The vertices of curve model
    List<Vector3>verticesCurveSurface;
    // Collided triangle
    Triangle CollidedTriangle;
    // The velocity of ball model
    Vector3 Velocity = Vector3.Zero;
    // The acceleration factor
    Vector3 Acceleration = newVector3(0, 0.0098f, 0);
    
  6. Initialize the camera and the collided triangle. Insert the following code into the Initialize() method:
    // Initialize the camera
    cameraPosition = new Vector3(0, 0, 20);
    view = Matrix.CreateLookAt(cameraPosition, Vector3.Zero,
    Vector3.Up);
    projection = Matrix.CreatePerspectiveFieldOfView(
    MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio,
    0.1f, 1000.0f);
    // Initialize the collided triangle
    CollidedTriangle = new Triangle();
    
  7. Load the ball and curve surface models and extract their vertices. Then, create the bounding sphere of the ball model from the extracted vertices.
    modelBall = Content.Load<Model>("Ball");
    modelSurface = Content.Load<Model>("CurveSurface");
    worldBall = Matrix.CreateTranslation(new Vector3(-2, 5, 0));
    Dictionary<string, List<Vector3>> o =
    (Dictionary<string, List<Vector3>>)modelBall.Tag;
    boundingSphereBall =
    BoundingSphere.CreateFromPoints(o["Sphere001"]);
    boundingSphereBall.Center = worldBall.Translation;
    verticesCurveSurface = ((Dictionary<string, List<Vector3>>)
    modelSurface.Tag)["Tube001"];
    
  8. Take the sphere-triangle collision and update the position of the ball model and its bounding sphere. Insert the following code into the Update() method:
    // Take the sphere triangle collision detection
    TriangleSphereCollisionDetection.IsSphereCollideWithTriangles(
    verticesCurveSurface, boundingSphereBall,
    out CollidedTriangle);
    float elapsed;
    // If no collision happens, move the ball
    if (CollidedTriangle == null)
    {
    elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
    Velocity += -Acceleration * elapsed;
    worldBall.Translation += Velocity;
    }
    // Update the translation of ball bounding sphere
    boundingSphereBall.Center = worldBall.Translation;
    
  9. Define the DrawModel() method to draw the model:
    public void DrawModel(Model model, Matrix world, Matrix view,
    Matrix projection)
    {
    Matrix[] transforms = new Matrix[model.Bones.Count];
    model.CopyAbsoluteBoneTransformsTo(transforms);
    foreach (ModelMesh mesh in model.Meshes)
    {
    foreach (BasicEffect effect in mesh.Effects)
    {
    effect.PreferPerPixelLighting = true;
    effect.EnableDefaultLighting();
    effect.DiffuseColor = Color.White.ToVector3();
    effect.World = transforms[mesh.ParentBone.Index] *
    world;
    effect.View = view;
    effect.Projection = projection;
    }
    mesh.Draw();
    }
    }
    
  10. Draw the ball model and curve surface ball on the Windows Phone 7 screen. Paste the following code into the Draw() method:
    GraphicsDevice.DepthStencilState = DepthStencilState.Default;
    // Draw the ball model and surface model
    DrawModel(modelBall, worldBall, view, projection);
    DrawModel(modelSurface, worldSurface, view, projection);
    
  11. Now, build and run the application. The application runs as shown in the following screenshots:
    3D ball move along a curved surface

How it works...

In step 5, the modelBall loads the ball model object; worldBall specifies world transformation of the ball model. Similarly, modelSurface for curve surface model, worldSurface for its world matrix; the next three variables cameraPosition, view, and projection serve for the camera; boundingSphereBall is the bounding sphere around the ball model; verticesCurveSurface is a vertex collection of the curve surface model; CollidedTriangle stores the collided triangle when the ball bounding sphere collides with the curve surface model triangles; Velocity specifies the ball moving velocity; Acceleration defines how the velocity will be changed.

In step 7, we use the BoundingSphere.CreateFromPoints() method to create the bounding sphere of the ball model using the vertices extracted from the Tag property of its model file. For verticesCurveSurface, we just read the vertex collection from its model file.

In step 8, the first line is to detect collision between ball bounding sphere and the triangles of the curve surface model. If the collision happens, CollidedTriangle is not null. At this moment, we start to move the ball. Anytime, it is required that updating the center position of the ball bounding sphere along with the ball model.

Related Posts:

Both comments and pings are currently closed.

Comments are closed.

Powered by Blog.