Search code examples
javagraphics3draytracing

Ray-triangle intersection algorithm not working


I am writing a raytracer using Java, but I ran into an issue with intersections between rays and triangles. I am using the algorithm given at Scratchapixel, but it is not working properly.

I am testing it using the following code:

Face3D triangle = new Face3D(
        new Vector3D(-1, -1, 0),
        new Vector3D(1, -1, 0),
        new Vector3D(0, 1, 0)
);
Ray3D ray = new Ray3D(
        new Vector3D(0, 0, -1),
        new Vector3D(0, 0, 1)
);
log.info(triangle.getNormal());
log.info(triangle.collision(ray));

The expected output would be 0,0,0, but instead it is returning null (no collision).

Full output:

17:31:56.013 [main] INFO  org.jrender.Main - Vector3D{x=1.0, y=0.0, z=0.0}
17:31:56.017 [main] INFO  org.jrender.space.Face3D - angle: 0.0
17:31:56.018 [main] INFO  org.jrender.Main - null

Collision method:

public class Face3D {
        // ... getters/setters etc.
        public Vector3D collision(Ray3D ray) {
        if (vertices.size() != 3)
            throw new UnsupportedOperationException("Normal can only be calculated on triangles");

        double angle = VectorUtils.dotProduct(normal, ray.getDirection());
        log.info("angle: " + angle);
        if (Math.abs(angle) < Constants.EPSILON) return null; // Constants.EPSILON = 0.001

        double d = VectorUtils.dotProduct(normal, v0);
        double t = (VectorUtils.dotProduct(normal, ray.getPosition()) + d) / angle;
        log.info("d: " + d + "; t: " + t);
        if (t < 0) return null;

        Vector3D intersection = ray.getPosition().copy().add(ray.getDirection().copy().multiply(t));
        log.info("intersection: " + intersection);
        Vector3D perpendicular;
        Vector3D edge;
        Vector3D distIntersection;

        edge = v1.copy().subtract(v0);
        distIntersection = intersection.copy().subtract(v0);
        perpendicular = VectorUtils.crossProduct(edge, distIntersection);
        if (VectorUtils.dotProduct(normal, perpendicular) < 0) return null;

        edge = v2.copy().subtract(v1);
        distIntersection = intersection.copy().subtract(v1);
        perpendicular = VectorUtils.crossProduct(edge, distIntersection);
        if (VectorUtils.dotProduct(normal, perpendicular) < 0) return null;

        edge = v0.copy().subtract(v2);
        distIntersection = intersection.copy().subtract(v2);
        perpendicular = VectorUtils.crossProduct(edge, distIntersection);
        if (VectorUtils.dotProduct(normal, perpendicular) < 0) return null;

        return intersection;
    }
}

Note: I am using vector.copy().subtract(), etc. because I have made the mathematical operations in-place, so making a copy is necessary for math operations

Utility methods that I have used

public class Vector3D {
    // ... getters/setters, etc.
    public Vector3D normalize() {
        return divide(Math.sqrt(x * x + y * y + z * z));
    }

    public Vector3D add(Vector3D v) {
        x += v.x;
        y += v.y;
        z += v.z;
        return this;
    }

    public Vector3D subtract(Vector3D v) {
        x -= v.x;
        y -= v.y;
        z -= v.z;
        return this;
    }

    public Vector3D multiply(double fac) {
        x *= fac;
        y *= fac;
        z *= fac;
        return this;
    }

    public Vector3D divide(double fac) {
        x /= fac;
        y /= fac;
        z /= fac;
        return this;
    }

    public Vector3D copy() {
        return new Vector3D(x, y, z);
    }
}
public class VectorUtils {
    public static Vector3D crossProduct(Vector3D first, Vector3D second) {
        return new Vector3D(
                first.getY() * second.getZ() - first.getZ() * second.getY(),
                first.getZ() * second.getX() - first.getX() * second.getZ(),
                first.getX() * second.getY() - first.getY() * second.getX()
        );
    }

    public static double dotProduct(Vector3D first, Vector3D second) {
        return first.getX() * second.getX() +
                first.getY() * second.getY() +
                first.getZ() * second.getZ();
    }
}

Solution

  • The issue was quite simple, I had my cross product implementation wrong, and after that I had to change one line of code.

    I changed

    double t = (VectorUtils.dotProduct(normal, ray.getPosition()) + d) / angle;
    

    to

    double t = (-VectorUtils.dotProduct(normal, ray.getPosition()) + d) / angle;
    

    and it worked.