import { CoordinateTracker } from './CoordinateTracker';
import { copyVertex3ToRef, vertexBetweenToRef, calculateCentroidVertex3ToRef, equalsWithEpsilonVertex3, subtractVertexToRef } from '../math/index';
import { default as earcut_default } from 'earcut';
import { default as poly_default } from 'polylabel';
import { DynamicPolygonPoint } from './DynamicPolygonPoint';
import { DynamicPolygonPointValidationResult } from './DynamicPolygonPointParent';
import { isComplexPolygonVertex2 } from '../math/Vertex2';
import { PlaneUtil } from '../math/plane';
import { VertexDataUtils } from '../math/VertexDataUtil';
import { getTriangleArea } from '../math/Triangle';
import { Color4, Matrix, Mesh, Plane, PolygonMeshBuilder, Vector2, Vector3, VertexBuffer } from '../loader/babylonjs-import';
/**
 * Represents a dynamic polygon. It is possible to add, insert and remove points to it and
 * have those changes reflect its 3D visualization. Useful for tools that need to
 * calculate area.
 * @param <Point> Defaults to {@link DynamicPolygonPoint}. However, a custom class can be specified
 * which derives from {@link DynamicPolygonPoint}.
 */
export class DynamicPolygon {
    name;
    _api;
    dragPointPositioningFunction;
    static _tmp = {
        vector3A: Vector3.Zero(),
        vector3B: Vector3.Zero(),
        vector3C: Vector3.Zero(),
        vector3D: Vector3.Zero(),
        vector3E: Vector3.Zero()
    };
    /**
     * The drag point for this polygon.
     * @hidden
     * @internal
     */
    _dragPoint;
    _dragPointPreviousPosition;
    _interiorPointTracker;
    _polygonMesh;
    _interiorPoint = Vector3.Zero();
    _polygonSurfaceArea = 0;
    _polygonBottomArea = 0;
    _polygonVolume = 0;
    /* If this is set to <> 0 the polygon will be extruded to become a volume. **/
    height = 0;
    /**
     * Plane all points of the polygon must lie on.
     */
    _plane = new Plane(0, 0, 0, 0);
    _pointFactory;
    _rotMat = Matrix.Identity();
    _centroidInWorld = Vector3.Zero();
    static _renderingGroupOnTop = 2;
    _isEnabled = true;
    _flatVertexData;
    _vertexData;
    _parent;
    _onParentWorldMatrixUpdateObserver;
    get parent() {
        return this._parent;
    }
    /**
     * Sets the parent of the DynamicPolygon without keeping the position in world space.
     * @param node new parent for the DynamicPolygon.
     */
    set parent(node) {
        if (this._onParentWorldMatrixUpdateObserver && this.parent) {
            this.parent.onAfterWorldMatrixUpdateObservable.remove(this._onParentWorldMatrixUpdateObserver);
        }
        this._parent = node;
        this._polygonMesh.parent = node ?? null;
        this.registerParentWorldMatrixCallback();
        this._polygonMesh.computeWorldMatrix();
        for (const p of [...this.points()]) {
            p.recalculateWorldCoords = true;
        }
    }
    /**
     * @hidden
     * @internal
     */
    _dynamicPolygonPointTracker;
    /** Points to first point of the polygon.
     * @hidden
     * @internal
     */
    _head;
    /**
     * @hidden
     * @internal
     */
    _minPoints = 3;
    /**
     * Triggered when {@link DynamicPolygon.apply} is called or when camera moves around. By listening to this
     * event, it is possible to know when new polygon points are added, updated and removed. It is also possible
     * to see how far away the points are from the camera. And to see what position they have in 2D space (for example, to
     * add a HTML element to the DOM).
     */
    onPointTrackableScreen;
    /**
     * Triggered when {@link DynamicPolygon.apply} is called or when camera moves around. By listening to this
     * event it is possible to know when the area label X,Y position is updated (This always exists on a DynamicPolygon). It is also possible
     * to see how far away the points are from the camera. What position they have in 2D space (perhaps one wants
     * to add a HTML element to the DOM there.).
     */
    onAreaLabelTrackableScreen;
    /**
     * Polygon constructor, counter-clockwise polygon winding order.
     * @param name Name of polyline
     * @param _api {@link BimCoreApi} instance
     * @param pointFactory Factory method which is used to create the instances of generic class type `Point`.
     * @param recalculate2DEachFrame If `true` then it updates the 2D positions for each tracked 3D coordinate at the end of each frame.
     * @param parent If Babylon TransformNode is provided the dynamic polygon will attach as a child and follow its parent.
     * @param dragPointPositioningFunction Function to calculate the drag points position in the polygon.
     */
    constructor(name, _api, pointFactory, recalculate2DEachFrame = false, parent, dragPointPositioningFunction) {
        this.name = name;
        this._api = _api;
        this.dragPointPositioningFunction = dragPointPositioningFunction;
        this._dynamicPolygonPointTracker = new CoordinateTracker(this._api, recalculate2DEachFrame);
        this._interiorPointTracker = new CoordinateTracker(this._api, recalculate2DEachFrame);
        this.onPointTrackableScreen = this._dynamicPolygonPointTracker.onUpdateObservable;
        this.onAreaLabelTrackableScreen = this._interiorPointTracker.onUpdateObservable;
        this._pointFactory = pointFactory;
        this._parent = parent;
        this._polygonMesh = this.createMesh(name, this._api.viewer.scene, parent);
        //This is needed to update the DynamicPolygonPoints when the DynamicPolygon's parent has received an update to its world matrix.
        this.registerParentWorldMatrixCallback();
    }
    registerParentWorldMatrixCallback() {
        if (this.parent) {
            let previousMatrix = this.parent.getWorldMatrix().clone();
            this._onParentWorldMatrixUpdateObserver = this.parent.onAfterWorldMatrixUpdateObservable.add(() => {
                if (this.parent && !previousMatrix.equals(this.parent.getWorldMatrix()) && this._head) {
                    for (const p of [...this.points()]) {
                        p.recalculateWorldCoords = true;
                    }
                    previousMatrix = this.parent.getWorldMatrix().clone();
                    this._polygonMesh.computeWorldMatrix();
                    this._polygonBottomArea = this.getPolygonArea(this._flatVertexData);
                    if (this.height !== 0) {
                        this._polygonSurfaceArea = this.getPolygonArea(this._vertexData);
                        this._polygonVolume = this.getPolygonVolume(this._polygonBottomArea, this.height);
                    }
                    else {
                        this._polygonSurfaceArea = this._polygonBottomArea;
                        this._polygonVolume = 0;
                    }
                    const worldPos = calculateCentroidVertex3ToRef(this._head.points(DynamicPolygonPoint.IsNotVirtual), this._centroidInWorld);
                    const localToWorld = this._rotMat.clone().setTranslation(worldPos);
                    const worldToLocal = localToWorld.clone().invert();
                    this.getInteriorPointToRef(this._vertexData, localToWorld, worldToLocal, this._interiorPoint);
                }
            });
        }
    }
    /**
     * Get the world matrix of this DynamicPolygon
     * @returns the world matrix for this DynamicPolygon.
     */
    worldMatrix() {
        return this._polygonMesh.getWorldMatrix();
    }
    /**
     * Plane spanned by points. Only updated when {@link DynamicPolygon.build} or {@link DynamicPolygon.assignNewPolygonPoints} is called.
     * @return The Plane.
     */
    get plane() {
        return this._plane;
    }
    /**
     * @returns `true` if polygon has no points. Otherwise `false`.
     */
    get isEmpty() {
        for (const {} of this.points(DynamicPolygonPoint.IsNotVirtual)) {
            return false;
        }
        return true;
    }
    /**
     * Whether or not the mesh is disposed or not.
     * @return boolean.
     */
    get isDisposed() {
        return this._head === undefined;
    }
    /**
     * Polygon surface area. Only updated when {@link DynamicPolygon.apply} or {@link DynamicPolygon.build} is called.
     * @return The polygon surface area.
     */
    get surfaceArea() {
        return this._polygonSurfaceArea;
    }
    /**
     * Polygon bottom area. Only updated when {@link DynamicPolygon.apply} or {@link DynamicPolygon.build} is called.
     * Calculated with polygonArea function with a height of zero.
     * @return The polygon bottom area.
     */
    get bottomArea() {
        return this._polygonBottomArea;
    }
    /**
     * Polygon volume. Only updated when there exists a height and {@link DynamicPolygon.apply} or {@link DynamicPolygon.build} is called.
     * @return The polygon volume.
     */
    get volume() {
        return this._polygonVolume;
    }
    /**
     * Interior point. Only updated when {@link DynamicPolygon.apply} or {@link DynamicPolygon.build} is called.
     * @return The interior point.
     */
    get interiorPoint() {
        return this._interiorPoint;
    }
    /**
     * BabylonJS mesh.
     * @return mesh
     */
    get mesh() {
        return this._polygonMesh;
    }
    /**
     * If transformPointsToLocal is `true`, transforms all provided points according to this DynamicPolygon's parent. In either case, validate points and return the validation result
     * @param points The points to transform
     * @param transformPointsToLocal Whether to transform the points to the DynamicPolygon's parent local space before validating them
     * @returns The validation result
     * @internal NOTE: Internal API. Subject to change. Use of these APIs in production applications is not supported.
     */
    transformAndValidatePoints(points, transformPointsToLocal) {
        if (this.parent && transformPointsToLocal) {
            for (const p of points) {
                Vector3.TransformCoordinatesFromFloatsToRef(p.x, p.y, p.z, this._polygonMesh.getWorldMatrix().clone().invert(), DynamicPolygon._tmp.vector3A);
                p.x = DynamicPolygon._tmp.vector3A.x;
                p.y = DynamicPolygon._tmp.vector3A.y;
                p.z = DynamicPolygon._tmp.vector3A.z;
            }
        }
        const validationResult = DynamicPolygon.validatePoints(points);
        return validationResult;
    }
    /**
     * Assigns points to define the polygon, this method can be used instead of build which adds virtual points
     * @param dynamicPolygonPoints
     */
    assignNewPolygonPoints(dynamicPolygonPoints) {
        this._head = dynamicPolygonPoints[0];
        this._rotMat = DynamicPolygon.rotationAcordingToPointsOrientationToRef([...this._head.points(DynamicPolygonPoint.IsNotVirtual)], this._rotMat);
        DynamicPolygon.buildPlaneToRef(dynamicPolygonPoints, this.plane);
    }
    /**
     * Attempt to build a {@link DynamicPolygon} using the `points` parameter.
     * @param points {@link Vertex3} points.
     * @param transformPointsToLocal Wheter to transform the points to the DynamicPolygon's parent local space before using them to build
     * @returns returns a {@link DynamicPolygonPointValidationResult}.
     */
    build(points, transformPointsToLocal = true) {
        const validationResult = this.transformAndValidatePoints(points, transformPointsToLocal);
        if (validationResult !== DynamicPolygonPointValidationResult.Ok) {
            return validationResult;
        }
        this.clear();
        const assignPointProperties = (p) => {
            p._parent = this;
        };
        // Setup all the required virtual drag points. We place one virtual drag pont
        // between each point in options.points.
        const dynamicPolygonPoints = [];
        for (const p of points) {
            const fP = this.createPoint();
            assignPointProperties(fP);
            if (this.parent) {
                fP.setLocals(p.x, p.y, p.z);
            }
            else {
                copyVertex3ToRef(p, fP);
            }
            if (dynamicPolygonPoints.length > 0) {
                // ensure last virtual point points to next real point
                const lastVirtualPoint = dynamicPolygonPoints[dynamicPolygonPoints.length - 1];
                vertexBetweenToRef(lastVirtualPoint._previous, fP, lastVirtualPoint);
                lastVirtualPoint._next = fP;
                // ensure new point points back to last virtual point
                fP._previous = lastVirtualPoint;
            }
            dynamicPolygonPoints.push(fP);
            const virtualPoint = this.createPoint(true);
            assignPointProperties(virtualPoint);
            dynamicPolygonPoints.push(virtualPoint);
            // ensure new point points to next virtual point
            fP._next = virtualPoint;
            // ensure new virtual point points back to new point
            virtualPoint._previous = fP;
        }
        // connect first and last point. Last is a a virtual point
        // and first is a non virtual point
        const last = dynamicPolygonPoints[dynamicPolygonPoints.length - 1];
        const first = dynamicPolygonPoints[0];
        last._next = first;
        first._previous = last;
        vertexBetweenToRef(last._previous, first, last);
        this.assignNewPolygonPoints(dynamicPolygonPoints);
        this._dragPointPreviousPosition = undefined;
        if (this.dragPointPositioningFunction) {
            this._dragPoint = this.createPoint();
            assignPointProperties(this._dragPoint);
            const position = this.dragPointPositioningFunction(dynamicPolygonPoints);
            this._dragPoint.x = position.x;
            this._dragPoint.y = position.y;
            this._dragPoint.z = position.z;
            this._dragPointPreviousPosition = { x: this._dragPoint.x, y: this._dragPoint.y, z: this._dragPoint.z };
        }
        this.apply();
        for (const p of dynamicPolygonPoints) {
            this._dynamicPolygonPointTracker.track(p, p);
        }
        if (this._dragPoint) {
            this._dynamicPolygonPointTracker.track(this._dragPoint, this._dragPoint);
        }
        return DynamicPolygonPointValidationResult.Ok;
    }
    /**
     * Validates the specified `points`.
     * @param points {@link Vertex3} points.
     * @returns returns a {@link DynamicPolygonPointValidationResult}.
     */
    static validatePoints(points, minPoints = 3) {
        if (points.length < minPoints) {
            return DynamicPolygonPointValidationResult.NotEnoughPoints;
        }
        const p = new Plane(0, 0, 0, 0);
        DynamicPolygon.buildPlaneToRef(points, p);
        // // Validate that points lie in plane.
        if (!PlaneUtil.doesPointsLieInPlane(p, points)) {
            return DynamicPolygonPointValidationResult.PointsDoesNotLieInPlane;
        }
        // Rotate so that we can drop y axis.
        const rotMat = DynamicPolygon.rotationAcordingToPointsOrientationToRef(points, new Matrix());
        const worldPos = calculateCentroidVertex3ToRef(points.values(), Vector3.Zero());
        const localToWorld = rotMat.setTranslation(worldPos);
        const worldToLocal = localToWorld.clone().invert();
        const contors = points.map((p) => {
            const tmp = DynamicPolygon._tmp.vector3A;
            Vector3.TransformCoordinatesFromFloatsToRef(p.x, p.y, p.z, worldToLocal, tmp);
            return { x: tmp.x, y: tmp.z };
        });
        if (isComplexPolygonVertex2(contors)) {
            return DynamicPolygonPointValidationResult.ComplexPolygon;
        }
        return DynamicPolygonPointValidationResult.Ok;
    }
    /**
     * Validates the specified `points`.
     * @internal
     * @hidden
     * @param points {@link Vertex3} points.
     * @returns returns a {@link DynamicPolygonPointValidationResult}.
     */
    _validatePoints(points) {
        return DynamicPolygon.validatePoints(points);
    }
    /**
     * Sets isEnabled on the dynamicpolygon and sets isEnabled on the polygon mesh if it has any points.
     * @param b state to set
     */
    set isEnabled(b) {
        this._isEnabled = b;
        this._polygonMesh.setEnabled(b && !this.isEmpty);
    }
    /**
     * @returns `true` if the dynamic polygon is enabled otherwise `false`.
     */
    get isEnabled() {
        return this._isEnabled;
    }
    /**
     * Applies changes to polygon which occurred when
     * calling methods on {@link DynamicPolygonPoint.move},
     * {@link DynamicPolygonPoint.virtual} and {@link DynamicPolygonPoint.delete}.
     * It will also trigger {@link DynamicPolygon.onPointTrackableScreen} events.
     */
    apply() {
        this.throwOnDisposed();
        this._api.viewer.wakeRenderLoop();
        this.updateMeshFromPoints();
        this._polygonMesh.setEnabled(this.isEnabled && !this.isEmpty);
    }
    /**
     * Clears the polygon of all current points
     */
    clear() {
        this._dynamicPolygonPointTracker.clear();
        this._interiorPointTracker.clear();
        this.mesh.setEnabled(false);
        this._head = undefined;
    }
    /**
     * Disposes the {@link DynamicPolygon}. It is no longer useable after this call.
     */
    dispose() {
        this.clear();
        this._polygonMesh.dispose();
    }
    /**
     * Required by {@link DynamicPolygonPointParent}.
     * @hidden
     * @internal
     */
    _virtualPointFactory() {
        return this.createPoint(true);
    }
    /**
     * Creates a new Point instance, using the DynamicPolygon's PointFactory. Used by DynamicPolygonWithArbitraryPoints
     * @param isVirtual Determines if the created point should be virtual or not
     * @returns The new point
     * @internal
     * @hidden
     */
    createPoint(isVirtual = false) {
        return this._pointFactory(this._api, this.plane, isVirtual);
    }
    static buildPlaneToRef(points, plane) {
        if (points.length < 3) {
            throw new Error('Need at least 3 points to define a plane');
        }
        const p1 = copyVertex3ToRef(points[0], DynamicPolygon._tmp.vector3A);
        const p2 = copyVertex3ToRef(points[1], DynamicPolygon._tmp.vector3B);
        const edge1 = p1.subtractToRef(p2, DynamicPolygon._tmp.vector3C);
        edge1.normalizeToRef(edge1);
        const epsilon = 0.001;
        for (let i = 2; i < points.length; i++) {
            const pX = copyVertex3ToRef(points[i], DynamicPolygon._tmp.vector3D);
            const edge2 = p1.subtract(pX); ////p1.subtractToRef(pX, DynamicPolygon._tmp.vector3E);
            edge2.normalizeToRef(edge2);
            const angle = Math.acos(Vector3.Dot(edge1, edge2));
            if (angle > epsilon) {
                plane.copyFromPoints(p1, p2, pX);
                return;
            }
        }
        throw new Error('Every point used to build the plane lies on the same plane');
    }
    /**
     * @hidden
     * @internal
     * @param parent The point to constrain the other point to.
     * @param child The point that will be constrained based on the parent point.
     * @param parentPreviousPosition previous position of the parent point.
     */
    constrainChild(parent, child, parentPreviousPosition) {
        subtractVertexToRef(parentPreviousPosition, child, DynamicPolygon._tmp.vector3A);
        subtractVertexToRef(parent, DynamicPolygon._tmp.vector3A, child);
    }
    // TODO: Remove duplicate points.
    // TODO: Error handling. (Minimum requirement is 3 points)
    updateMeshFromPoints() {
        if (this._dragPoint && this.dragPointPositioningFunction) {
            const epsilon = 0.0001;
            if (this._dragPointPreviousPosition &&
                !equalsWithEpsilonVertex3(this._dragPointPreviousPosition, this._dragPoint, epsilon)) {
                const allPoints = [...this._head.points()];
                for (let i = 0; i < allPoints.length; i++) {
                    this.constrainChild(this._dragPoint, allPoints[i], this._dragPointPreviousPosition);
                }
            }
            const nonVirtualPoints = [
                ...this._head.points((p) => {
                    return !p.virtual();
                })
            ];
            const position = this.dragPointPositioningFunction(nonVirtualPoints);
            this._dragPoint.x = position.x;
            this._dragPoint.y = position.y;
            this._dragPoint.z = position.z;
            this._dragPointPreviousPosition = { x: this._dragPoint.x, y: this._dragPoint.y, z: this._dragPoint.z };
        }
        const worldPos = calculateCentroidVertex3ToRef(this._head.points(DynamicPolygonPoint.IsNotVirtual), this._centroidInWorld);
        // TODO optimize with temporary variables if performance is required
        const localToWorld = this._rotMat.clone().setTranslation(worldPos);
        const worldToLocal = localToWorld.clone().invert();
        const contours = [];
        for (const _p of this._head?.points(DynamicPolygonPoint.IsNotVirtual) ?? []) {
            const pointInObjectSpace = DynamicPolygon._tmp.vector3A;
            Vector3.TransformCoordinatesFromFloatsToRef(_p.localX, _p.localY, _p.localZ, worldToLocal, pointInObjectSpace);
            contours.push(new Vector2(pointInObjectSpace.x, pointInObjectSpace.z));
        }
        const polygonMeshBuilder = new PolygonMeshBuilder('not used', contours, undefined, earcut_default);
        this._vertexData = polygonMeshBuilder.buildVertexData(this.height);
        this._flatVertexData = polygonMeshBuilder.buildVertexData();
        const positions = this._vertexData.positions;
        const posLen = positions.length;
        let posIndex = 0;
        const pointInWorldspace = DynamicPolygon._tmp.vector3A;
        for (; posIndex < posLen; posIndex += 3) {
            Vector3.TransformCoordinatesFromFloatsToRef(positions[posIndex], positions[posIndex + 1] + this.height, positions[posIndex + 2], localToWorld, pointInWorldspace);
            positions[posIndex] = pointInWorldspace.x;
            positions[posIndex + 1] = pointInWorldspace.y;
            positions[posIndex + 2] = pointInWorldspace.z;
        }
        // TODO If we don't include translation in localToWorld and worldToLocal (and subtract it from the points) then we can simply
        // set position here.
        this._polygonMesh.setVerticesData(VertexBuffer.PositionKind, this._vertexData.positions, true);
        this._polygonMesh.setVerticesData(VertexBuffer.NormalKind, this._vertexData.normals, true);
        this._polygonMesh.setVerticesData(VertexBuffer.UVKind, this._vertexData.uvs, true);
        this._polygonMesh.setIndices(this._vertexData.indices);
        this.getInteriorPointToRef(this._vertexData, localToWorld, worldToLocal, this._interiorPoint);
        this._polygonBottomArea = this.getPolygonArea(this._flatVertexData);
        if (this.height !== 0) {
            this._polygonSurfaceArea = this.getPolygonArea(this._vertexData);
            this._polygonVolume = this.getPolygonVolume(this._polygonBottomArea, this.height);
        }
        else {
            this._polygonSurfaceArea = this._polygonBottomArea;
            this._polygonVolume = 0;
        }
        this._interiorPointTracker.track(this._interiorPoint, this._interiorPoint);
        if (this._dragPoint) {
            this._dynamicPolygonPointTracker.track(this._dragPoint, this._dragPoint);
        }
    }
    getInteriorPointToRef(vertexData, localToWorld, worldToLocal, dst) {
        const positions = vertexData.positions;
        const vec2Arr = [];
        // Throwing RangeError here if the positions has too few points to make up a polygon.
        if (positions.length < 9) {
            throw new RangeError('positions has too few vertices for a polygon.');
        }
        for (let i = 0; i < positions.length; i += 3) {
            const tmp = DynamicPolygon._tmp.vector3A;
            Vector3.TransformCoordinatesFromFloatsToRef(positions[i], positions[i + 1], positions[i + 2], this._polygonMesh.getWorldMatrix(), tmp);
            Vector3.TransformCoordinatesToRef(tmp, worldToLocal, tmp);
            vec2Arr.push([tmp.x, tmp.z]);
        }
        const interiorPoint = poly_default([vec2Arr]);
        Vector3.TransformCoordinatesFromFloatsToRef(interiorPoint[0], 0, interiorPoint[1], localToWorld, dst);
        return dst;
    }
    getPolygonArea(vertexData) {
        let ar = 0.0;
        // avoid allocations by reusing vertex objects in loop below. Needed because there may be
        // lots of triangles in the vertexData.
        const tmp1 = { x: 0, y: 0, z: 0 };
        const tmp2 = { x: 0, y: 0, z: 0 };
        const tmp3 = { x: 0, y: 0, z: 0 };
        const vertexDataProperty = 'positions';
        VertexDataUtils.forEachTriangle(vertexData, (p1, p2, p3) => {
            p1.xyzToRef(vertexDataProperty, tmp1);
            p2.xyzToRef(vertexDataProperty, tmp2);
            p3.xyzToRef(vertexDataProperty, tmp3);
            const worldMat = this._polygonMesh.getWorldMatrix();
            this.transformVertex3ToRef(tmp1, worldMat, tmp1);
            this.transformVertex3ToRef(tmp2, worldMat, tmp2);
            this.transformVertex3ToRef(tmp3, worldMat, tmp3);
            ar += getTriangleArea(tmp1, tmp2, tmp3);
            return true;
        });
        return ar;
    }
    getPolygonVolume(area, height) {
        return area * height * (this._polygonMesh.getWorldMatrix().getRow(1)?.y ?? 1);
    }
    transformVertex3ToRef(v, matrix, dst) {
        const tmpVec = Vector3.Zero();
        Vector3.TransformCoordinatesFromFloatsToRef(v.x, v.y, v.z, matrix, tmpVec);
        dst.x = tmpVec.x;
        dst.y = tmpVec.y;
        dst.z = tmpVec.z;
        return dst;
    }
    // RotationMatrix Given first 3 points orientation in world.
    static rotationAcordingToPointsOrientationToRef(points, dst) {
        const nonVirtualPoints = points;
        // BUG. Wont this stop working if one of the points is located on the line two
        // other points make up? Can we use the plane instead? Since plane does not
        // change unless this.build() is called we only need to calculate the matrix once?
        const p1 = copyVertex3ToRef(nonVirtualPoints[0], DynamicPolygon._tmp.vector3A);
        const p2 = copyVertex3ToRef(nonVirtualPoints[1], DynamicPolygon._tmp.vector3B);
        const p3 = copyVertex3ToRef(nonVirtualPoints[2], DynamicPolygon._tmp.vector3C);
        const v = p2.subtract(p1).normalize();
        const u = p2.subtract(p3).normalize();
        const vxu = Vector3.Cross(v, u).normalize(); // Normal from plane
        Vector3.CrossToRef(vxu, v, u);
        u.normalize();
        Matrix.FromXYZAxesToRef(v, vxu, u, dst);
        return dst;
    }
    throwOnDisposed() {
        if (this.isDisposed)
            throw new Error('Object is disposed');
    }
    createMesh(name, scene, parent) {
        const polygonMesh = new Mesh(name, scene, parent);
        polygonMesh.renderingGroupId = DynamicPolygon._renderingGroupOnTop; // Render on top of everything. Otherwise it is difficult to see the line.
        polygonMesh.edgesWidth = 4.0;
        polygonMesh.edgesColor = new Color4(0, 0, 1, 1);
        polygonMesh.setEnabled(true);
        return polygonMesh;
    }
    *points(predicate, backwards = false) {
        for (const p of this._head?.points(predicate, backwards) ?? []) {
            yield p;
        }
    }
}
//# sourceMappingURL=DynamicPolygon.js.map