import { Epsilon, Scalar, Axis, Vector3, Camera, Plane } from '../loader/babylonjs-import';
import { copyVertex3ToRef, addVertexToRef } from './index';
import { Transforms } from './Transforms';
/**
 * Useful operations to perform on a plane.
 */
export class PlaneUtil {
    static _tmp = {
        vector3A: Vector3.Zero(),
        vector3B: Vector3.Zero(),
        vector3C: Vector3.Zero(),
        vector3D: Vector3.Zero(),
        vector3E: Vector3.Zero()
    };
    /**
     * Finds the intersection area of two axis aligned planes and returns it as 2 points.
     * @param center1 Center of first plane.
     * @param scale1 Scale of first plane.
     * @param center2 Center of second plane.
     * @param scale2 Scale of second plane.
     * @returns The intersection area of the 2 planes. undefined if there is none.
     */
    static planesIntersect(center1, scale1, center2, scale2) {
        //TODO: Since this currently only works in XZ plane, calculate rotation matrix to rotate planes to XZ plane to test for intersection.
        const plane1Min = new Vector3(center1.x - scale1.x / 2, 1, center1.z - scale1.z / 2);
        const plane1Max = new Vector3(center1.x + scale1.x / 2, 1, center1.z + scale1.z / 2);
        const plane2Min = new Vector3(center2.x - scale2.x / 2, 1, center2.z - scale2.z / 2);
        const plane2Max = new Vector3(center2.x + scale2.x / 2, 1, center2.z + scale2.z / 2);
        const x5 = Math.max(plane1Min.x, plane2Min.x);
        const y5 = Math.max(plane1Min.z, plane2Min.z);
        const x6 = Math.min(plane1Max.x, plane2Max.x);
        const y6 = Math.min(plane1Max.z, plane2Max.z);
        const minOverlap = new Vector3(x5, 0, y5);
        const maxOverlap = new Vector3(x6, 0, y6);
        if (x5 >= x6 || y5 >= y6) {
            return undefined;
        }
        return [minOverlap, maxOverlap];
    }
    /**
     * Given a center point on the plane calculate 4 points in
     * the shape of a square (on plane) where sides have size `scale`.
     * @param center Center of resulting square.
     * @param normal Normal of plane that square will be generated on.
     * @param camera Camera used to scale square.
     * @param scale Square size.
     * @param dst Square corner points.
     * @returns dst
     */
    static cameraScaledSquareOnPlaneToRef(center, normal, camera, scale, dst) {
        const { plane, size } = PlaneUtil.createPlaneFromCamera(center, normal, camera, scale);
        return PlaneUtil.squareOnPlaneToRef(plane, center, size, dst);
    }
    /**
     * Creates a plane aligned with the current camera, and a size that can be used to place points on the plane from center that reaches the edges of the camera frustum
     * @param center A point of the plane
     * @param normal The normal of the plane
     * @param camera The camera with which size should be scaled with to reach the edges of the frustum
     * @param scale Number to scale the returned size with.
     * @returns An object containing the newly created plane, and the size to move things away from the center in order to put them on the camera frustum edges
     */
    static createPlaneFromCamera(center, normal, camera, scale) {
        const centerVec3 = copyVertex3ToRef(center, PlaneUtil._tmp.vector3D);
        const normalVec3 = copyVertex3ToRef(normal, PlaneUtil._tmp.vector3E);
        const plane = Plane.FromPositionAndNormal(centerVec3, normalVec3);
        const size = this.cameraScaledSize(center, camera, scale);
        return { plane, size };
    }
    /**
     * Gives a size on a plane that scales with the distance of the camera.
     * @param center Point on the plane infront of the camera.
     * @param camera Camera to use when scaling.
     * @param scale Number to scale the size with.
     * @returns The camera scaled size.
     */
    static cameraScaledSize(center, camera, scale) {
        const centerVec3 = copyVertex3ToRef(center, PlaneUtil._tmp.vector3D);
        let size = 0;
        if (camera.mode === Camera.ORTHOGRAPHIC_CAMERA) {
            const frustumSize = camera.twinfinity.getOrthoFrustumSizeToRef({ x: 0, y: 0 });
            const minSize = Math.min(frustumSize.x, frustumSize.y);
            size = minSize / scale;
        }
        else {
            size = Math.abs(Vector3.Distance(camera.position, centerVec3)) / scale;
        }
        return size;
    }
    /**
     * Given a center point on the plane calculate 4 points in
     * the shape of a square (on plane) where sides have size `scale`
     * then convert the coordinates to the parent transforms coordinate system.
     * @param center Center of resulting square.
     * @param normal Normal of plane that square will be generated on.
     * @param camera Camera used to scale square.
     * @param scale Square size.
     * @param parent The parent TransformNode, this transform nodes world matrix will be used to transform the coordinates to local space.
     * @param dst Square corner points.
     * @returns dst
     */
    static cameraScaledSquareWithParentOnPlaneToRef(center, normal, camera, scale, parent, dst) {
        this.cameraScaledSquareOnPlaneToRef(center, normal, camera, scale, dst);
        Transforms.TransformPointsWithParent(parent, dst);
        return dst;
    }
    /**
     * Calculate a point on `plane` located at an angle of `theta` around point, P, on plane that is closest to (0,0,0).
     * @param theta Angle in radians in unit circle projected on plane around point P on plane closest to (0,0,0).
     * @param dst Point on plane is written to this object.
     * Point is located on unit circle project on plane around point P closest to (0,0,0).
     * @returns Reference to `dst`
     */
    static pointOnPlaneToRef(plane, theta, dst) {
        let w = PlaneUtil._tmp.vector3C;
        if (Scalar.WithinEpsilon(plane.normal.x, 0, Epsilon)) {
            Vector3.CrossToRef(plane.normal, Axis.X, w);
        }
        else {
            Vector3.CrossToRef(plane.normal, Axis.Z, w);
        }
        w = w.scale(Math.cos(theta)).add(Vector3.Cross(plane.normal, w).scale(Math.sin(theta)));
        // const t = PlaneUtil._tmp.vector3B;
        // Vector3.CrossToRef(plane.normal, w, t);
        // w.scaleInPlace(Math.cos(theta)).addInPlace(t).scaleInPlace(Math.sin(theta));
        copyVertex3ToRef(w, dst);
        return dst;
    }
    /**
     * Given a center point on the plane calculate 4 points in
     * the shape of a square (on plane) where sides have size `size`.
     * @param centerOnPlane Center of square must lie on plane
     * @param size Size of sides of square.
     * @param dst 4 corners of the calculated square is written to these objects.
     * @returns Reference to `dst`
     */
    static squareOnPlaneToRef(plane, centerOnPlane, size, dst) {
        // TODO Ensure that center is actually on the plane!
        // TODO Utilize size for square sides.
        const vecInPlane1 = PlaneUtil.pointOnPlaneToRef(plane, Math.PI / 4, PlaneUtil._tmp.vector3A);
        const vecInPlane3 = PlaneUtil.pointOnPlaneToRef(plane, (Math.PI * 3) / 4, PlaneUtil._tmp.vector3B);
        const vecInPlane2 = vecInPlane1.negate();
        const vecInPlane4 = vecInPlane3.negate();
        for (let i = 0; i < dst.length; ++i) {
            copyVertex3ToRef(centerOnPlane, dst[i]);
        }
        // ORDER MATTERS BECAUSE MANYFOLD PROBLEM!
        addVertexToRef(vecInPlane1.scaleInPlace(size), dst[0]);
        addVertexToRef(vecInPlane4.scaleInPlace(size), dst[1]);
        addVertexToRef(vecInPlane2.scaleInPlace(size), dst[2]);
        addVertexToRef(vecInPlane3.scaleInPlace(size), dst[3]);
        return dst;
    }
    /**
     * Validate that a set of given points lie in the provided plane.
     * @param plane Plane to check against.
     * @param points Array containing points to validate.
     * @returns `true` if points lie in plane, `false` otherwise.
     */
    static doesPointsLieInPlane(plane, points) {
        for (let i = 0; i < points.length; i++) {
            copyVertex3ToRef(points[i], PlaneUtil._tmp.vector3A);
            if (plane.signedDistanceTo(PlaneUtil._tmp.vector3A) >= Epsilon) {
                return false;
            }
        }
        return true;
    }
}
//# sourceMappingURL=plane.js.map