/** @internal NOTE: Internal APIs. Subject to change. Use of these APIs in production applications is not supported. */
import { Vector3, Axis } from '../loader/babylonjs-import';
export class Shapes {
    static _direction1 = Vector3.Zero();
    static _direction2 = Vector3.Zero();
    static _prevStartLeft = Vector3.Zero();
    static _prevStartRight = Vector3.Zero();
    static _line = Vector3.Zero();
    //TODO: Change this after vacation.
    static lineIntersects(x1, y1, x2, y2, x3, y3, x4, y4) {
        const denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
        const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom;
        const ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom;
        if (denom === 0) {
            return null;
        }
        return {
            x: x1 + ua * (x2 - x1),
            y: y1 + ua * (y2 - y1),
            seg1: ua >= 0 && ua <= 1,
            seg2: ub >= 0 && ub <= 1
        };
    }
    /**
     * Creates points to form the contour of a polygon of a certain thickness that follows a line of points
     * @param linePoints The points forming a poly line, they must be in sequence (a -> b -> c)
     * @param thickness The distance between the points that form the polygon contour
     * @returns The points forming the contour around the poly line
     */
    static createLinePolygonPoints(linePoints, thickness) {
        const lineNormals = [];
        for (let i = 0; i < linePoints.length; i++) {
            let dX;
            let dY;
            if (i === linePoints.length - 1) {
                dX = -(linePoints[i - 1].x - linePoints[i].x);
                dY = -(linePoints[i - 1].z - linePoints[i].z);
            }
            else {
                dX = linePoints[i + 1].x - linePoints[i].x;
                dY = linePoints[i + 1].z - linePoints[i].z;
            }
            let lineNormal = new Vector3(-dY, 0.0, dX);
            lineNormal = lineNormal.normalize();
            lineNormals.push(lineNormal);
        }
        const polygonPointsLeftSide = [];
        const polygonPointsRightSide = [];
        for (let i = 0; i < linePoints.length; i++) {
            const normal = i === 0 || i === linePoints.length - 1
                ? lineNormals[i]
                : lineNormals[i].add(lineNormals[i - 1]).normalize();
            const middlePoint = new Vector3(linePoints[i].x, linePoints[i].y, linePoints[i].z);
            if (i !== 0 && i !== linePoints.length - 1) {
                this._direction1.set(linePoints[i].x - linePoints[i - 1].x, linePoints[i].y - linePoints[i - 1].y, linePoints[i].z - linePoints[i - 1].z);
                this._direction1.normalize();
                this._direction2.set(linePoints[i].x - linePoints[i + 1].x, linePoints[i].y - linePoints[i + 1].y, linePoints[i].z - linePoints[i + 1].z);
                this._direction2.normalize();
                if (i === 0 || i === linePoints.length - 1) {
                    this._prevStartLeft = polygonPointsLeftSide[i - 1];
                    this._prevStartRight = polygonPointsRightSide[i - 1];
                }
                else {
                    this._line.set(linePoints[i].x - linePoints[i - 1].x, linePoints[i].y - linePoints[i - 1].y, linePoints[i].z - linePoints[i - 1].z);
                    const dir = Vector3.Cross(Axis.Y, this._line)
                        .normalize()
                        .scaleInPlace(thickness / 2);
                    this._prevStartLeft.set(linePoints[i - 1].x + dir.x, linePoints[i - 1].y + dir.y, linePoints[i - 1].z + dir.z);
                    this._prevStartRight.set(linePoints[i - 1].x - dir.x, linePoints[i - 1].y - dir.y, linePoints[i - 1].z - dir.z);
                }
                const n = normal.normalizeToNew();
                const line1Point2 = this._prevStartLeft.add(this._direction1);
                const intersection = this.lineIntersects(this._prevStartLeft.x, this._prevStartLeft.z, line1Point2.x, line1Point2.z, linePoints[i].x, linePoints[i].z, linePoints[i].x + n.x, linePoints[i].z + n.z);
                if (intersection !== null) {
                    polygonPointsLeftSide.push(new Vector3(intersection.x, middlePoint.y, intersection.y));
                }
                else {
                    polygonPointsLeftSide.push(middlePoint.add(normal.scale(-thickness / 2.0)));
                }
                this._prevStartRight.addToRef(this._direction1, line1Point2);
                const intersection2 = this.lineIntersects(this._prevStartRight.x, this._prevStartRight.z, line1Point2.x, line1Point2.z, linePoints[i].x, linePoints[i].z, linePoints[i].x - n.x, linePoints[i].z - n.z);
                if (intersection2 !== null) {
                    polygonPointsRightSide.push(new Vector3(intersection2.x, middlePoint.y, intersection2.y));
                }
                else {
                    polygonPointsRightSide.push(middlePoint.add(normal.scale(thickness / 2.0)));
                }
            }
            else {
                polygonPointsLeftSide.push(middlePoint.add(normal.scale(-thickness / 2.0)));
                polygonPointsRightSide.push(middlePoint.add(normal.scale(thickness / 2.0)));
            }
        }
        const polygonPoints = [];
        const pushPoints = function (points, reverse) {
            for (let i = 0; i < linePoints.length; i++) {
                const index = reverse ? linePoints.length - 1 - i : i;
                polygonPoints.push({ x: points[index].x, y: 0.0, z: points[index].z });
            }
        };
        pushPoints(polygonPointsLeftSide, false);
        pushPoints(polygonPointsRightSide, true);
        return polygonPoints;
    }
    /**
     * Creates 7 points which form the contour of an arrow
     * @param bluntPoint The point which forms the blunt end of the arrow
     * @param arrowPoint The point where the arrow will point from
     * @param lineThickness How thick the line should be
     * @param arrowHeadWidth How far out the arrow wings should be, in relation to the line thickness
     * @param arrowHeadHeight How far out the arrow point should project from arrowPoint, in relation to the line thickness
     * @returns The 7 contour points which form the arrow
     */
    static createArrowPolygonPoints(bluntPoint, arrowPoint, lineThickness, arrowHeadWidth, arrowHeadHeight) {
        const dX = arrowPoint.x - bluntPoint.x;
        const dY = arrowPoint.z - bluntPoint.z;
        const lineNormal = new Vector3(-dY, 0.0, dX);
        const bluntPointVec3 = new Vector3(bluntPoint.x, bluntPoint.y, bluntPoint.z);
        const arrowPointVec3 = new Vector3(arrowPoint.x, arrowPoint.y, arrowPoint.z);
        lineNormal.normalizeToRef(lineNormal);
        const lineDirection = arrowPointVec3.subtract(bluntPointVec3);
        lineDirection.normalizeToRef(lineDirection);
        const arrowLength = arrowPointVec3.subtract(bluntPointVec3).length();
        const renderOnlyArrowhead = arrowLength < arrowHeadHeight;
        const sizeMultiplier = renderOnlyArrowhead ? arrowLength / arrowHeadHeight : 1;
        const arrowHeadBottomCenterVec3 = arrowPointVec3.subtract(lineDirection.scale(arrowHeadHeight * sizeMultiplier));
        const bluntPointLeft = bluntPointVec3.add(lineNormal.scale(-lineThickness / 2.0));
        const bluntPointRight = bluntPointVec3.add(lineNormal.scale(lineThickness / 2.0));
        const arrowPointLeft = arrowHeadBottomCenterVec3.add(lineNormal.scale(-lineThickness / 2.0));
        const arrowPointRight = arrowHeadBottomCenterVec3.add(lineNormal.scale(lineThickness / 2.0));
        const arrowPointWingLeft = arrowHeadBottomCenterVec3.add(lineNormal.scale(-arrowHeadWidth * sizeMultiplier));
        const arrowPointWingRight = arrowHeadBottomCenterVec3.add(lineNormal.scale(arrowHeadWidth * sizeMultiplier));
        const arrowSpearPoint = arrowPointVec3;
        const polygonPointsVecs = [];
        if (!renderOnlyArrowhead) {
            polygonPointsVecs.push(bluntPointLeft);
            polygonPointsVecs.push(arrowPointLeft);
        }
        polygonPointsVecs.push(arrowPointWingLeft);
        polygonPointsVecs.push(arrowSpearPoint);
        polygonPointsVecs.push(arrowPointWingRight);
        if (!renderOnlyArrowhead) {
            polygonPointsVecs.push(arrowPointRight);
            polygonPointsVecs.push(bluntPointRight);
        }
        return polygonPointsVecs.map((vec3) => ({ x: vec3.x, y: vec3.y, z: vec3.z }));
    }
    /**
     * Creates a bunch of points that form the contours in a circle (so no center point is created)
     * @param circlePoint The center of the circle
     * @param radiusPoint An arbitrary point on the plane of the circle, it's distance from the circlePoint is used as the radius of the circle
     * @param lineThickness How thick the line representing the circle should be, a value less than or equal to 0 indicates that the circle should be completely filled
     * @param segments How many segments you want around circlePoint
     * @returns The points to use to form the contour around the circle
     */
    static createCirclePolygonPoints(circlePoint, radiusPoint, lineThickness, segments) {
        const radius = new Vector3(circlePoint.x, circlePoint.y, circlePoint.z)
            .subtractFromFloats(radiusPoint.x, radiusPoint.y, radiusPoint.z)
            .length();
        const innerPoints = [];
        const outerPoints = [];
        for (let i = 0; i <= segments; i++) {
            let unitCircleRatio = (i / segments) * Math.PI * 2.0;
            // We need to go all the way around the circle, but in order to avoid a complex polygon the last segment has an invisible gap, which is achieved by offsetting the final points in the circle a very slight amount that is hopefuly not visible
            if (i === segments) {
                const gap = 0.00005;
                unitCircleRatio = (i / segments - gap) * Math.PI * 2.0;
            }
            const x = Math.cos(unitCircleRatio);
            const z = Math.sin(unitCircleRatio);
            const outerX = x * radius;
            const outerZ = z * radius;
            outerPoints.push({ x: circlePoint.x + outerX, y: circlePoint.y, z: circlePoint.z + outerZ });
            // This is not an oversight for values of lineThickness less than or equal to 0, in those cases we choose to not have an inner circle, therefore making a completetely filled circle
            if (lineThickness > 0.0) {
                lineThickness = Math.min(radius * 0.999, lineThickness);
                const innerRadius = radius - lineThickness;
                const innerX = x * innerRadius;
                const innerZ = z * innerRadius;
                innerPoints.push({ x: circlePoint.x + innerX, y: circlePoint.y, z: circlePoint.z + innerZ });
            }
        }
        return [...outerPoints, ...innerPoints.reverse()];
    }
}
//# sourceMappingURL=Shapes.js.map