import { v3_dot } from "../../vec3/v3_dot.js";
import { assert } from "../../../assert.js";

/**
 * NOTE: adapted from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h
 * @source https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm (Möller and Trumbore, « Fast, Minimum Storage Ray-Triangle Intersection », Journal of Graphics Tools, vol. 2,‎ 1997, p. 21–28)
 * @param {SurfacePoint3} result
 * @param {number} origin_x
 * @param {number} origin_y
 * @param {number} origin_z
 * @param {number} direction_x
 * @param {number} direction_y
 * @param {number} direction_z
 * @param {number} ax
 * @param {number} ay
 * @param {number} az
 * @param {number} bx
 * @param {number} by
 * @param {number} bz
 * @param {number} cx
 * @param {number} cy
 * @param {number} cz
 * @returns {boolean}
 */
export function computeTriangleRayIntersection(
    result,
    origin_x, origin_y, origin_z,
    direction_x, direction_y, direction_z,
    ax, ay, az,
    bx, by, bz,
    cx, cy, cz
) {
    assert.isNumber(ax, 'ax');
    assert.isNumber(ay, 'ay');
    assert.isNumber(az, 'az');

    assert.isNumber(bx, 'bx');
    assert.isNumber(by, 'by');
    assert.isNumber(bz, 'bz');

    assert.isNumber(cx, 'cx');
    assert.isNumber(cy, 'cy');
    assert.isNumber(cz, 'cz');

    // nan checks
    assert.notNaN(ax, 'ax');
    assert.notNaN(ay, 'ay');
    assert.notNaN(az, 'az');

    assert.notNaN(bx, 'bx');
    assert.notNaN(by, 'by');
    assert.notNaN(bz, 'bz');

    assert.notNaN(cx, 'cx');
    assert.notNaN(cy, 'cy');
    assert.notNaN(cz, 'cz');

    // finate number check
    assert.isFinite(ax, 'ax');
    assert.isFinite(ay, 'ay');
    assert.isFinite(az, 'az');

    assert.isFinite(bx, 'bx');
    assert.isFinite(by, 'by');
    assert.isFinite(bz, 'bz');

    assert.isFinite(cx, 'cx');
    assert.isFinite(cy, 'cy');
    assert.isFinite(cz, 'cz');


    // edge1 = a - b
    const edge1_x = bx - ax;
    const edge1_y = by - ay;
    const edge1_z = bz - az;

    // edge2 = c - a
    const edge2_x = cx - ax;
    const edge2_y = cy - ay;
    const edge2_z = cz - az;

    // Compute triangle normal

    // normal = edge1 x edge2
    const normal_x = edge1_y * edge2_z - edge1_z * edge2_y;
    const normal_y = edge1_z * edge2_x - edge1_x * edge2_z;
    const normal_z = edge1_x * edge2_y - edge1_y * edge2_x

    let DdN = v3_dot(direction_x, direction_y, direction_z, normal_x, normal_y, normal_z);
    let sign = 1;

    if (DdN === 0) {
        // ray and the triangle normal are orthogonal, means that the ray can not pass penetrate triangle
        return false;
    } else if (DdN < 0) {
        sign = -1;
        DdN = -DdN;
    }

    // diff = origin - a
    const diff_x = origin_x - ax;
    const diff_y = origin_y - ay;
    const diff_z = origin_z - az;

    // edge3 = diff x edge2
    const edge3_x = diff_y * edge2_z - diff_z * edge2_y;
    const edge3_y = diff_z * edge2_x - diff_x * edge2_z;
    const edge3_z = diff_x * edge2_y - diff_y * edge2_x;

    // DdQxE2 = sign * ( direction . edge3)
    const DdQxE2 = sign * v3_dot(direction_x, direction_y, direction_z, edge3_x, edge3_y, edge3_z);

    if (DdQxE2 < 0) {
        // b1 < 0, no intersection
        return false;
    }

    const edge4_x = edge1_y * diff_z - edge1_z * diff_y;
    const edge4_y = edge1_z * diff_x - edge1_x * diff_z;
    const edge4_z = edge1_x * diff_y - edge1_y * diff_x;

    const DdE1xQ = sign * v3_dot(direction_x, direction_y, direction_z, edge4_x, edge4_y, edge4_z);

    if (DdE1xQ < 0) {
        // b2 < 0, no intersection
        return false;
    }

    if (DdQxE2 + DdE1xQ > DdN) {
        // b1+b2 > 1, no intersection
        return false;
    }

    // Line intersects triangle, check if ray does.
    const QdN = -sign * v3_dot(diff_x, diff_y, diff_z, normal_x, normal_y, normal_z);

    if (QdN < 0) {
        // t < 0, no intersection
        return false;

    }

    // Ray intersects triangle.
    const t = QdN / DdN;

    result.normal.set(
        normal_x,
        normal_y,
        normal_z
    );

    result.position.set(
        direction_x * t + origin_x,
        direction_y * t + origin_y,
        direction_z * t + origin_z,
    );

    return true;
}

