0
votes

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);
        }
1
Is Triangle a class or struct? Please show your collision code.Nico Schertler
Honestly, I have no idea what kind of fancy stuff you do there. Haven't seen someone doing it like this before. Especially the inside method seems to do way too much. Take a look here to see how to calculate barycentric coordinates. I would strongly advice to clean your maths up (or at least write unit tests to test it sufficiently). That's probably the reason why your ray transformation did not work in the first place. And use reasonable variable names!Nico Schertler
Btw, you know that the usual operators like *, - and + are overloaded for vectors, right? Use those instead of Vector3.Subtract etc. That will make your code more readable.Nico Schertler
The math's sound. It worked perfectly for the untransformed model.Zoltán Király
Then what about the ray. Could you add the code how you generated it?Nico Schertler

1 Answers

0
votes

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