import { array_copy } from "../../../../collection/array/array_copy.js";
import { min2 } from "../../../../math/min2.js";
import { ConicRay } from "../../../ConicRay.js";
import { Miniball } from "../../../packing/miniball/Miniball.js";
import { PointSet } from "../../../packing/miniball/PointSet.js";
import { v3_angle_between } from "../../../vec3/v3_angle_between.js";

/**
 * Using Shirman and Abi-Ezzi method, compute bounding sphere of points, then build a cone from that
 * @see L.A. Shirman and S.S. Abi-Ezzi, The cone of normals technique for fast processing of curved patches, Computer Graphics Forum, 12 (1993), 261-272.
 * @param {ConicRay} result
 * @param {Iterable<TopoTriangle>} faces
 */
export function computeTriangleClusterNormalBoundingCone(result, faces) {
    const normal_data = [];
    let normal_count = 0;

    // extract face normals
    for (let face of faces) {

        const face_normal = face.normal;
        if (face_normal[0] === 0 && face_normal[1] === 0 && face_normal[1] === 0) {
            // skip
            continue;
        }

        const address = normal_count * 3;

        array_copy(face_normal, 0, normal_data, address, 3);

        normal_count++;
    }

    const result_direction = result.direction;
    if (normal_count === 0) {
        // no valid normals, produce an arbitrary result
        result_direction.set(1, 0, 0);
        result.angle = 0;
        return;
    }

    const point_set = new PointSet(normal_count, 3, normal_data);

    const miniball = new Miniball(point_set);

    const miniball_center = miniball.center();

    if (Number.isNaN(miniball_center[0]) || Number.isNaN(miniball_center[1]) || Number.isNaN(miniball_center[2])) {
        // something went wrong, center is undefined, fall-back to an open cone
        result_direction.set(1, 0, 0);
        result.angle = Math.PI;
        return;
    }

    result_direction.fromArray(miniball_center, 0);
    const center_vector_magnitude = result_direction.length();

    if (center_vector_magnitude === 0) {
        // degenerate, pointing in all directions
        result_direction.set(1, 0, 0);
        result.angle = Math.PI;
        return;
    }

    result_direction.multiplyScalar(1 / center_vector_magnitude); // normalize direction vector

    // include normals into the cone
    let max_angle = 0;
    for (let i = 0; i < normal_count; i++) {
        const i3 = i * 3;

        const normal_x = normal_data[i3];
        const normal_y = normal_data[i3];
        const normal_z = normal_data[i3];

        const angle = v3_angle_between(
            normal_x, normal_y, normal_z,
            result_direction.x, result_direction.y, result_direction.z
        );

        if (angle > max_angle) {
            max_angle = angle;
        }
    }

    result.angle = min2(max_angle, Math.PI);
}
