// https://github.com/tldraw/tldraw/blob/31f0f02adf58b909f59764f62de09e97542eb2b1/packages/core/src/utils/utils.ts
// Credits to tldraw
import { PointLocation } from './point-location.js';
import { Vec } from './vec.js';
export const EPSILON = 1e-12;
export const MACHINE_EPSILON = 1.12e-16;
export const CURVETIME_EPSILON = 1e-8;
function square(num) {
    return num * num;
}
function sumSqr(v, w) {
    return square(v[0] - w[0]) + square(v[1] - w[1]);
}
function distToSegmentSquared(p, v, w) {
    const l2 = sumSqr(v, w);
    if (l2 == 0)
        return sumSqr(p, v);
    let t = ((p[0] - v[0]) * (w[0] - v[0]) + (p[1] - v[1]) * (w[1] - v[1])) / l2;
    t = Math.max(0, Math.min(1, t));
    return sumSqr(p, [v[0] + t * (w[0] - v[0]), v[1] + t * (w[1] - v[1])]);
}
function distToSegment(p, v, w) {
    return Math.sqrt(distToSegmentSquared(p, v, w));
}
export function isPointIn(a, x, y) {
    return a.x <= x && x <= a.x + a.w && a.y <= y && y <= a.y + a.h;
}
export function intersects(a, b) {
    return (a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y);
}
export function almostEqual(a, b, epsilon = 0.0001) {
    return Math.abs(a - b) < epsilon;
}
export function isVecZero(v) {
    return v.every(n => isZero(n));
}
export function isZero(x) {
    return x >= -EPSILON && x <= EPSILON;
}
export function pointAlmostEqual(a, b, _epsilon = 0.0001) {
    return a.length === b.length && a.every((v, i) => almostEqual(v, b[i]));
}
export function clamp(n, min, max) {
    return Math.max(min, typeof max !== 'undefined' ? Math.min(n, max) : n);
}
export function pointInEllipse(A, C, rx, ry, rotation = 0) {
    const cos = Math.cos(rotation);
    const sin = Math.sin(rotation);
    const delta = Vec.sub(A, C);
    const tdx = cos * delta[0] + sin * delta[1];
    const tdy = sin * delta[0] - cos * delta[1];
    return (tdx * tdx) / (rx * rx) + (tdy * tdy) / (ry * ry) <= 1;
}
export function pointInPolygon(p, points) {
    let wn = 0; // winding number
    points.forEach((a, i) => {
        const b = points[(i + 1) % points.length];
        if (a[1] <= p[1]) {
            if (b[1] > p[1] && Vec.cross(a, b, p) > 0) {
                wn += 1;
            }
        }
        else if (b[1] <= p[1] && Vec.cross(a, b, p) < 0) {
            wn -= 1;
        }
    });
    return wn !== 0;
}
export function pointOnEllipse(point, rx, ry, threshold) {
    // slope of point
    const t = point[1] / point[0];
    const squaredX = (square(rx) * square(ry)) / (square(rx) * square(t) + square(ry));
    const squaredY = (square(rx) * square(ry) - square(ry) * squaredX) / square(rx);
    return (Math.abs(Math.sqrt(square(point[1]) + square(point[0])) -
        Math.sqrt(squaredX + squaredY)) < threshold);
}
export function pointOnPolygonStoke(p, points, threshold) {
    for (let i = 0; i < points.length; ++i) {
        const next = i + 1 === points.length ? 0 : i + 1;
        if (distToSegment(p, points[i], points[next]) <= threshold) {
            return true;
        }
    }
    return false;
}
export function getBoundsFromPoints(points, rotation = 0) {
    let minX = Infinity;
    let minY = Infinity;
    let maxX = -Infinity;
    let maxY = -Infinity;
    if (points.length < 2) {
        minX = 0;
        minY = 0;
        maxX = 1;
        maxY = 1;
    }
    else {
        for (const [x, y] of points) {
            minX = Math.min(x, minX);
            minY = Math.min(y, minY);
            maxX = Math.max(x, maxX);
            maxY = Math.max(y, maxY);
        }
    }
    if (rotation !== 0) {
        return getBoundsFromPoints(points.map(pt => Vec.rotWith(pt, [(minX + maxX) / 2, (minY + maxY) / 2], rotation)));
    }
    return {
        minX,
        minY,
        maxX,
        maxY,
        width: Math.max(1, maxX - minX),
        height: Math.max(1, maxY - minY),
    };
}
export function getSvgPathFromStroke(points, closed = true) {
    const len = points.length;
    if (len < 4) {
        return ``;
    }
    let a = points[0];
    let b = points[1];
    const c = points[2];
    let result = `M${a[0].toFixed(2)},${a[1].toFixed(2)} Q${b[0].toFixed(2)},${b[1].toFixed(2)} ${average(b[0], c[0]).toFixed(2)},${average(b[1], c[1]).toFixed(2)} T`;
    for (let i = 2, max = len - 1; i < max; i++) {
        a = points[i];
        b = points[i + 1];
        result += `${average(a[0], b[0]).toFixed(2)},${average(a[1], b[1]).toFixed(2)} `;
    }
    if (closed) {
        result += 'Z';
    }
    return result;
}
function average(a, b) {
    return (a + b) / 2;
}
export function lineIntersects(sp, ep, sp2, ep2, infinite = false) {
    const v1 = Vec.sub(ep, sp);
    const v2 = Vec.sub(ep2, sp2);
    const cross = Vec.cpr(v1, v2);
    // Avoid divisions by 0, and errors when getting too close to 0
    if (almostEqual(cross, 0, MACHINE_EPSILON))
        return null;
    const d = Vec.sub(sp, sp2);
    let u1 = Vec.cpr(v2, d) / cross;
    const u2 = Vec.cpr(v1, d) / cross, 
    // Check the ranges of the u parameters if the line is not
    // allowed to extend beyond the definition points, but
    // compare with EPSILON tolerance over the [0, 1] bounds.
    epsilon = /*#=*/ EPSILON, uMin = -epsilon, uMax = 1 + epsilon;
    if (infinite || (uMin < u1 && u1 < uMax && uMin < u2 && u2 < uMax)) {
        // Address the tolerance at the bounds by clipping to
        // the actual range.
        if (!infinite) {
            u1 = clamp(u1, 0, 1);
        }
        return Vec.lrp(sp, ep, u1);
    }
    return null;
}
//reference https://www.xarg.org/book/computer-graphics/line-segment-ellipse-intersection/
export function lineEllipseIntersects(A, B, C, rx, ry, rad = 0) {
    A = Vec.rot(Vec.sub(A, C), -rad);
    B = Vec.rot(Vec.sub(B, C), -rad);
    rx *= rx;
    ry *= ry;
    const rst = [];
    const v = Vec.sub(B, A);
    const a = rx * v[1] * v[1] + ry * v[0] * v[0];
    const b = 2 * (rx * A[1] * v[1] + ry * A[0] * v[0]);
    const c = rx * A[1] * A[1] + ry * A[0] * A[0] - rx * ry;
    const D = b * b - 4 * a * c; // Discriminant
    if (D >= 0) {
        const sqrtD = Math.sqrt(D);
        const t1 = (-b + sqrtD) / (2 * a);
        const t2 = (-b - sqrtD) / (2 * a);
        if (0 <= t1 && t1 <= 1)
            rst.push(Vec.add(Vec.rot(Vec.add(Vec.mul(v, t1), A), rad), C));
        if (0 <= t2 && t2 <= 1 && Math.abs(t1 - t2) > 1e-16)
            rst.push(Vec.add(Vec.rot(Vec.add(Vec.mul(v, t2), A), rad), C));
    }
    if (rst.length === 0)
        return null;
    return rst.map(v => {
        const pl = new PointLocation(v);
        const normalVector = Vec.uni(Vec.divV(Vec.sub(v, C), [rx * rx, ry * ry]));
        pl.tangent = [-normalVector[1], normalVector[0]];
        return pl;
    });
}
export function linePolygonIntersects(sp, ep, points) {
    const result = [];
    const len = points.length;
    for (let i = 0; i < len; i++) {
        const p = points[i];
        const p2 = points[(i + 1) % len];
        const rst = lineIntersects(sp, ep, p, p2);
        if (rst) {
            const v = new PointLocation(rst);
            v.tangent = Vec.normalize(Vec.sub(p2, p));
            result.push(v);
        }
    }
    return result.length ? result : null;
}
export function polygonNearestPoint(points, point) {
    const len = points.length;
    let rst = [];
    let dis = Infinity;
    for (let i = 0; i < len; i++) {
        const p = points[i];
        const p2 = points[(i + 1) % len];
        const temp = Vec.nearestPointOnLineSegment(p, p2, point, true);
        const curDis = Vec.dist(temp, point);
        if (curDis < dis) {
            dis = curDis;
            rst = temp;
        }
    }
    return rst;
}
export function polygonPointDistance(points, point) {
    const nearest = polygonNearestPoint(points, point);
    return Vec.dist(nearest, point);
}
export function polygonGetPointTangent(points, point) {
    const len = points.length;
    for (let i = 0; i < len; i++) {
        const p = points[i];
        const p2 = points[(i + 1) % len];
        if (isPointOnLineSegment(point, [p, p2])) {
            return Vec.normalize(Vec.sub(p2, p));
        }
    }
    return [0, 0];
}
export function isPointOnLineSegment(point, line) {
    const [sp, ep] = line;
    const v1 = Vec.sub(point, sp);
    const v2 = Vec.sub(point, ep);
    return almostEqual(Vec.cpr(v1, v2), 0, 0.01) && Vec.dpr(v1, v2) <= 0;
}
export function linePolylineIntersects(sp, ep, points) {
    const result = [];
    const len = points.length;
    for (let i = 0; i < len - 1; i++) {
        const p = points[i];
        const p2 = points[i + 1];
        const rst = lineIntersects(sp, ep, p, p2);
        if (rst) {
            result.push(new PointLocation(rst, Vec.normalize(Vec.sub(p2, p))));
        }
    }
    return result.length ? result : null;
}
export function polyLineNearestPoint(points, point) {
    const len = points.length;
    let rst = [];
    let dis = Infinity;
    for (let i = 0; i < len - 1; i++) {
        const p = points[i];
        const p2 = points[i + 1];
        const temp = Vec.nearestPointOnLineSegment(p, p2, point, true);
        const curDis = Vec.dist(temp, point);
        if (curDis < dis) {
            dis = curDis;
            rst = temp;
        }
    }
    return rst;
}
export function sign(number) {
    return number > 0 ? 1 : -1;
}
export function getPointFromBoundsWithRotation(bounds, point) {
    const { x, y, w, h, rotate } = bounds;
    if (!rotate)
        return point;
    const cx = x + w / 2;
    const cy = y + h / 2;
    const m = new DOMMatrix()
        .translateSelf(cx, cy)
        .rotateSelf(rotate)
        .translateSelf(-cx, -cy);
    const p = new DOMPoint(...point).matrixTransform(m);
    return [p.x, p.y];
}
export function rotatePoints(points, center, rotate) {
    const rad = toRadian(rotate);
    return points.map(p => Vec.add(center, Vec.rot(Vec.sub(p, center), rad)));
}
export function getPointsFromBoundsWithRotation(bounds, getPoints = ({ x, y, w, h }) => [
    // left-top
    [x, y],
    // right-top
    [x + w, y],
    // right-bottom
    [x + w, y + h],
    // left-bottom
    [x, y + h],
]) {
    const { rotate } = bounds;
    let points = getPoints(bounds);
    if (rotate) {
        const { x, y, w, h } = bounds;
        const cx = x + w / 2;
        const cy = y + h / 2;
        const m = new DOMMatrix()
            .translateSelf(cx, cy)
            .rotateSelf(rotate)
            .translateSelf(-cx, -cy);
        points = points.map(point => {
            const { x, y } = new DOMPoint(...point).matrixTransform(m);
            return [x, y];
        });
    }
    return points;
}
export function getQuadBoundsWithRotation(bounds) {
    const { x, y, w, h, rotate } = bounds;
    const rect = new DOMRect(x, y, w, h);
    if (!rotate)
        return rect;
    return new DOMQuad(...getPointsFromBoundsWithRotation(bounds).map(point => new DOMPoint(...point))).getBounds();
}
export function getBoundsWithRotation(bounds) {
    const { x, y, width: w, height: h } = getQuadBoundsWithRotation(bounds);
    return { x, y, w, h };
}
export function normalizeDegAngle(angle) {
    if (angle < 0)
        angle += 360;
    angle %= 360;
    return angle;
}
export function toRadian(angle) {
    return (angle * Math.PI) / 180;
}
export function toDegree(radian) {
    return (radian * 180) / Math.PI;
}
// 0 means x axis, 1 means y axis
export function isOverlap(line1, line2, axis, strict = true) {
    const less = strict
        ? (a, b) => a < b
        : (a, b) => a <= b;
    return !(less(Math.max(line1[0][axis], line1[1][axis]), Math.min(line2[0][axis], line2[1][axis])) ||
        less(Math.max(line2[0][axis], line2[1][axis]), Math.min(line1[0][axis], line1[1][axis])));
}
export function getCenterAreaBounds(bounds, ratio) {
    const { x, y, w, h, rotate } = bounds;
    const cx = x + w / 2;
    const cy = y + h / 2;
    const nw = w * ratio;
    const nh = h * ratio;
    return {
        x: cx - nw / 2,
        y: cy - nh / 2,
        w: nw,
        h: nh,
        rotate,
    };
}
//# sourceMappingURL=math-utils.js.map