Search code examples
c#xnaxna-4.0intersection

XNA: Transforming individual triangles gives different results than transforming model


I'm writing a ray-triangle collision in XNA. To do this I'm generating a list of triangles from my model, which I then transform with the same matrix I'm using to transform the model to draw it. Then I run a triangle intersection algorithm on the list of transformed triangles to see if I hit it.

I understand that this is not the best solution because of performance issues, but transforming the ray didn't work out, so I'm going with this.

My problem is that the triangles transformed individually do no generate the same shape that the transformed model is.

The Problem

On the left is the model as drawn by XNA. On the right is the area that responds to mouse clicks. (To get this image I generated an array of float?-s in which I stored the distances measured by the triangle intersection algorithm on the transformed triangles. Then for each non-null value I drew a pixel on the secreen: the brithter the pixel, the closer the intersetion way to the camera.)

My hypothesis is that the triangles transformed actually produce the shape above. This would mean that transforming their verteces individually doesn't produce the same result as transforming the model as a whole does. I can't understand why.

Here is the drawing code:

public void DrawMe(Camera camera, Matrix world, Texture2D texture)
        {
        foreach (ModelMesh mesh in ModelToDraw.Meshes)
            {
            foreach (BasicEffect effect in mesh.Effects)
                {
                effect.TextureEnabled = true;
                effect.Texture = texture;
                effect.EnableDefaultLighting();
                effect.PreferPerPixelLighting = true;

                effect.World = world;
                effect.Projection = camera.Projection;
                effect.View = camera.View;
                }
            mesh.Draw();
            }
        }

Here's the code I use to transform the triangles: In the class containing the information on the piece:

private void transformTriangles()   
    {
    for (int i=0; i<myTriangles.Length; i++)
        {
        transformedTriangles[i] = myTriangles[i].GetCopy();
        }
        foreach (Triangle t in transformedTriangles)
        {
        t.Transform(world);
        }
    }

myTriangles contains the triangles extracted from the model. These are stored in a custom Triangle class. I tested whether this data is accurate and it is.

GetCopy simply returns a new Triangle instance with the same data as the instance generating it.

world is simply the matrix I use to transform my model.

In the triangle class:

public void Transform(Matrix transformation)
    {
    vertices[0] = Vector3.Transform(vertices[0], transformation);
    vertices[1] = Vector3.Transform(vertices[1], transformation);
    vertices[2] = Vector3.Transform(vertices[2], transformation);
    }

My goal is for the shape on the right to match the shape on the left. Any suggestions are appreciated.

Thanks!

EDIT:

As per request, here is the whole Triangle class:

class Triangle
    {
    public Vector3[] vertices;
    public Vector3 normal;

    public Triangle(Vector3 A, Vector3 B, Vector3 C)
        {
        vertices = new Vector3[3];
        vertices[0] = A;
        vertices[1] = B;
        vertices[2] = C;
        calculateNormal();
        }

    public Triangle(Vector3[] vertices)
        {
        this.vertices = vertices;
        calculateNormal();
        }

    private void calculateNormal()
        {
        Vector3 AB = new Vector3();
        Vector3 AC = new Vector3();

        AB = Vector3.Subtract(vertices[1], vertices[0]);
        AC = Vector3.Subtract(vertices[2], vertices[0]);
        normal = Vector3.Cross(AB, AC);
        normal.Normalize();
        }

    private Vector3 project(Vector3 projectUnto, Vector3 toProject)
        {
        float multiplier = Vector3.Dot(projectUnto, toProject)/Vector3.Dot(projectUnto, projectUnto);
        return Vector3.Multiply(projectUnto, multiplier);
        }

    private Vector3? calculateIntersectionPoint(Ray r)
        {
        if (Vector3.Dot(r.Direction, normal) == 0)
            {
            return null;
            }

        Vector3 I, w;
        float multiplier;

        w = Vector3.Subtract(vertices[0], r.Position);
        multiplier = Vector3.Dot(w, normal)/Vector3.Dot(r.Direction, normal);
        I = Vector3.Add(r.Position, Vector3.Multiply(r.Direction, multiplier));

        return I;
        }

    private bool inside(Vector3? i)
        {
        if (i == null)
            {
            return false;
            }
        float a, b, c;
        Vector3 AB, AC, BC, v, w, u, AI, BI, CI;
        Vector3 I = (Vector3) i;
        AB = Vector3.Subtract(vertices[1], vertices[0]);
        AC = Vector3.Subtract(vertices[2], vertices[0]);
        BC = Vector3.Subtract(vertices[2], vertices[1]);
        AI = Vector3.Subtract(I, vertices[0]);
        BI = Vector3.Subtract(I, vertices[1]);
        CI = Vector3.Subtract(I, vertices[2]);

        v = Vector3.Subtract(AB, project(Vector3.Multiply(BC, -1), AB));
        u = Vector3.Subtract(BC, project(Vector3.Multiply(AC, -1), BC));
        w = Vector3.Subtract(Vector3.Multiply(AC, -1), project(AB, Vector3.Multiply(AC, -1)));

        a = 1 - Vector3.Dot(v, AI)/Vector3.Dot(v, AB);
        b = 1 - Vector3.Dot(u, BI)/Vector3.Dot(u, BC);
        c = 1 - Vector3.Dot(w, CI)/Vector3.Dot(w, Vector3.Multiply(AC, -1));

        return a >= 0 && a <= 1 && b >= 0 && b <= 1 && c >= 0 && c <= 1;
        }

    public float? Intersects(Ray ray)
        {
        Vector3? I = calculateIntersectionPoint(ray);
        if (I == null)
            {
            return null;
            }

        if (inside(I))
            {
            Vector3 i = (Vector3) I;
            return Vector3.Distance(ray.Position, i);
            }

        return null;
        }

    public Triangle GetCopy()
        {
        return new Triangle(vertices);
        }

    public void Transform(Matrix transformation)
        {
        vertices[0] = Vector3.Transform(vertices[0], transformation);
        vertices[1] = Vector3.Transform(vertices[1], transformation);
        vertices[2] = Vector3.Transform(vertices[2], transformation);
        }
    }

EDIT 2:

As per request, the code generating the ray. Couldn't be more standard if you ask me:

private Ray getRay()
        {
        Vector3 nearPoint = BoardViewPort.Unproject(new Vector3(ms.X, ms.Y, 0), cam.Projection, cam.View, Matrix.Identity);
        Vector3 farPoint = BoardViewPort.Unproject(new Vector3(ms.X, ms.Y, 1), cam.Projection, cam.View, Matrix.Identity);
        Vector3 direction = farPoint - nearPoint;
        direction.Normalize();
        return new Ray(nearPoint, direction);
        }

Solution

  • I simply wasn't recalculating the normal after the transformation. Adding the calculateNormal(); statement at the end of the Transform function solved the problem.