import {} from "./Geometry.js";
const epsilon = 1e-6;
export class ShadowProjectionBuilder {
    projectBounds(options) {
        const receiverPlaneY = options.receiverPlaneY ?? 0;
        const direction = normalize(options.lightDirection);
        if (Math.abs(direction[1]) < epsilon) {
            throw new Error("Shadow projection requires a light direction that intersects the receiver plane.");
        }
        const projected = corners(options.casterBounds)
            .map((point) => projectPointToPlane(point, direction, receiverPlaneY))
            .filter((point) => isFiniteVec3(point));
        if (projected.length === 0) {
            throw new Error("Shadow projection produced no finite points.");
        }
        const hull2d = convexHull(projected.map((point) => [point[0], point[2]]));
        const points = hull2d.map(([x, z]) => [x, receiverPlaneY, z]);
        return {
            points,
            bounds: boundsFromPoints(points)
        };
    }
}
function projectPointToPlane(point, direction, receiverPlaneY) {
    const t = (receiverPlaneY - point[1]) / direction[1];
    return [point[0] + direction[0] * t, receiverPlaneY, point[2] + direction[2] * t];
}
function corners(bounds) {
    const min = bounds.min;
    const max = bounds.max;
    return [
        [min[0], min[1], min[2]],
        [max[0], min[1], min[2]],
        [max[0], max[1], min[2]],
        [min[0], max[1], min[2]],
        [min[0], min[1], max[2]],
        [max[0], min[1], max[2]],
        [max[0], max[1], max[2]],
        [min[0], max[1], max[2]]
    ];
}
function boundsFromPoints(points) {
    const min = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY];
    const max = [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY];
    for (const point of points) {
        min[0] = Math.min(min[0], point[0]);
        min[1] = Math.min(min[1], point[1]);
        min[2] = Math.min(min[2], point[2]);
        max[0] = Math.max(max[0], point[0]);
        max[1] = Math.max(max[1], point[1]);
        max[2] = Math.max(max[2], point[2]);
    }
    return { min, max };
}
function convexHull(points) {
    const sorted = [...new Map(points.map((point) => [`${point[0]},${point[1]}`, point])).values()].sort((a, b) => a[0] - b[0] || a[1] - b[1]);
    if (sorted.length <= 1) {
        return sorted;
    }
    const lower = [];
    for (const point of sorted) {
        while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], point) <= 0) {
            lower.pop();
        }
        lower.push(point);
    }
    const upper = [];
    for (let index = sorted.length - 1; index >= 0; index -= 1) {
        const point = sorted[index];
        while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], point) <= 0) {
            upper.pop();
        }
        upper.push(point);
    }
    return [...lower.slice(0, -1), ...upper.slice(0, -1)];
}
function cross(origin, a, b) {
    return (a[0] - origin[0]) * (b[1] - origin[1]) - (a[1] - origin[1]) * (b[0] - origin[0]);
}
function normalize(value) {
    const length = Math.hypot(value[0], value[1], value[2]);
    if (length <= epsilon) {
        throw new Error("Shadow projection light direction cannot be zero.");
    }
    return [value[0] / length, value[1] / length, value[2] / length];
}
function isFiniteVec3(value) {
    return Number.isFinite(value[0]) && Number.isFinite(value[1]) && Number.isFinite(value[2]);
}
//# sourceMappingURL=ShadowProjection.js.map