/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Polyface
 */
import { Point2d } from "../geometry3d/Point2dVector2d";
import { Vector3d } from "../geometry3d/Point3dVector3d";
import { TriangleLocationDetail } from "../geometry3d/BarycentricTriangle";
import { Geometry } from "../Geometry";
import { PolygonLocationDetail, PolygonOps } from "../geometry3d/PolygonOps";
import { NumberArray } from "../geometry3d/PointHelpers";
/** Options for computing and processing facet intersection methods.
 * @see PolyfaceQuery.intersectRay3d
 * @public
 */
export class FacetIntersectOptions {
    /** distance tolerance for testing coincidence with facet boundary */
    distanceTolerance;
    /** fractional tolerance for snapping barycentric coordinates to a triangular facet edge */
    parameterTolerance;
    /** whether to compute the normal at the intersection point */
    needNormal;
    /** whether to compute the uv parameter at the intersection point */
    needParam;
    /** whether to compute the color at the intersection point */
    needColor;
    /** whether to compute the barycentric coordinates of the point for a convex facet */
    needBarycentricCoordinates;
    /** optional callback to accept an intersected facet */
    acceptIntersection;
    /** constructor with defaults */
    constructor() {
        this.distanceTolerance = Geometry.smallMetricDistance;
        this.parameterTolerance = Geometry.smallFraction;
    }
}
/**
 * A pair of FacetLocationDetail.
 * @public
 */
export class FacetLocationDetailPair {
    /** The first of the two details. */
    detailA;
    /** The second of the two details. */
    detailB;
    /** Constructor, captures inputs */
    constructor(detailA, detailB) {
        this.detailA = detailA;
        this.detailB = detailB;
    }
    /** Create a facet detail pair, capturing inputs. */
    static create(detailA, detailB) {
        return new FacetLocationDetailPair(detailA, detailB);
    }
}
/**
 * Implementation of `FacetLocationDetail` for a triangular facet.
 * @public
 */
export class TriangularFacetLocationDetail {
    _facetIndex;
    _detail;
    _normal;
    _param;
    _color;
    /** captures the detail if provided */
    constructor(facetIndex = -1, detail) {
        this._facetIndex = facetIndex;
        this._detail = detail ? detail : TriangleLocationDetail.create();
    }
    /** Invalidate this detail. */
    invalidate(deep = true) {
        this._facetIndex = -1;
        if (deep)
            this._detail.invalidate();
        this._normal = undefined;
        this._param = undefined;
        this._color = undefined;
    }
    /** Create a detail.
     * @param detail optional, copied if given
     * @param result optional pre-allocated object to fill and return
     */
    static create(facetIndex = -1, detail, result) {
        if (!result)
            result = new TriangularFacetLocationDetail();
        else
            result.invalidate(false); // shallow: detail might be owned by result!
        result._facetIndex = facetIndex;
        if (undefined !== detail && result._detail !== detail)
            result._detail.copyContentsFrom(detail);
        return result;
    }
    /** Create a detail, capturing inputs. */
    static createCapture(facetIndex, detail) {
        return new TriangularFacetLocationDetail(facetIndex, detail);
    }
    /** Get the facet index. */
    get facetIndex() {
        return this._facetIndex;
    }
    /** Get the edge count of this facet. */
    get edgeCount() {
        return 3;
    }
    /** Get the world coordinates of the point. */
    get point() {
        return this._detail.world;
    }
    /** Get the application-specific number. */
    get a() {
        return this._detail.a;
    }
    /** Get the projection of the point onto the closest facet edge. */
    get closestEdge() {
        return { startVertexIndex: this._detail.closestEdgeIndex, edgeParam: this._detail.closestEdgeParam };
    }
    /** Test validity of fields other than _detail. */
    get _isValid() {
        return this._facetIndex >= 0;
    }
    /** Whether this instance specifies a valid location. */
    get isValid() {
        return this._isValid && this._detail.isValid;
    }
    /** Whether the facet is convex. */
    get isConvex() {
        return true;
    }
    /** Whether the point is inside or on the polygon. */
    get isInsideOrOn() {
        return this._isValid && this._detail.isInsideOrOn;
    }
    /** Get the code that classifies the location of the point with respect to the facet. */
    get classify() {
        return this._detail.classify;
    }
    /** Clone the instance */
    clone() {
        const detail = new TriangularFacetLocationDetail();
        detail.copyContentsFrom(this);
        return detail;
    }
    /** Set the instance contents from the other detail.
     * @param other detail to clone
     */
    copyContentsFrom(other) {
        this._facetIndex = other._facetIndex;
        this._detail.copyContentsFrom(other._detail);
        this._normal = other._normal?.clone();
        this._param = other._param?.clone();
        this._color = other._color;
    }
    /** Get normal interpolated from facet data.
     * @param facetNormals used to compute the normal cache
     * @returns reference to cached normal
     */
    getNormal(facetNormals) {
        if (this._detail.isValid && undefined === this._normal && undefined !== facetNormals) {
            this._normal = Vector3d.create();
            const scales = [this._detail.local.x, this._detail.local.y, this._detail.local.z];
            facetNormals.linearCombination(scales, this._normal);
        }
        return this._normal;
    }
    /** Get uv parameter interpolated from facet data.
     * @param facetParams used to compute the uv parameter cache
     * @returns reference to cached uv parameter
     */
    getParam(facetParams) {
        if (this._detail.isValid && undefined === this._param && undefined !== facetParams) {
            this._param = Point2d.create();
            const scales = [this._detail.local.x, this._detail.local.y, this._detail.local.z];
            facetParams.linearCombination(scales, this._param);
        }
        return this._param;
    }
    /** Get color interpolated from facet data.
     * * Assumes barycentric coordinates are already computed in the TriangleLocationDetail member.
     * @param facetColors used to compute the color cache
     * @returns cached color
     */
    getColor(facetColors) {
        if (this._detail.isValid && undefined === this._color && undefined !== facetColors) {
            const scales = [this._detail.local.x, this._detail.local.y, this._detail.local.z];
            this._color = NumberArray.linearCombinationOfColors(facetColors, scales);
        }
        return this._color;
    }
    /** Get the barycentric coordinates of this location.
     * @returns cached barycentric coordinates
    */
    getBarycentricCoordinates() {
        return [this._detail.local.x, this._detail.local.y, this._detail.local.z];
    }
}
/**
 * Implementation of `FacetLocationDetail` for a general facet, which may or may not be convex.
 * * Facet vertex data interpolation is not available.
 * @public
 */
export class NonConvexFacetLocationDetail {
    _facetIndex;
    _edgeCount;
    _detail;
    /** captures the detail if provided */
    constructor(facetIndex = -1, edgeCount = 0, detail) {
        this._facetIndex = facetIndex;
        this._edgeCount = edgeCount;
        this._detail = detail ? detail : PolygonLocationDetail.create();
    }
    /** Invalidate this detail. */
    invalidate(deep = true) {
        this._facetIndex = -1;
        this._edgeCount = 0;
        if (deep)
            this._detail.invalidate();
    }
    /** Create a detail.
     * @param detail optional, copied if given
     * @param result optional pre-allocated object to fill and return
     */
    static create(facetIndex = -1, edgeCount = 0, detail, result) {
        if (!result)
            result = new NonConvexFacetLocationDetail();
        else
            result.invalidate(false); // shallow: detail might be owned by result!
        result._facetIndex = facetIndex;
        result._edgeCount = edgeCount;
        if (undefined !== detail && result._detail !== detail)
            result._detail.copyContentsFrom(detail);
        return result;
    }
    /** Create a detail, capturing inputs. */
    static createCapture(facetIndex, edgeCount, detail) {
        return new NonConvexFacetLocationDetail(facetIndex, edgeCount, detail);
    }
    /** Get the facet index. */
    get facetIndex() {
        return this._facetIndex;
    }
    /** Get the edge count of this facet. */
    get edgeCount() {
        return this._edgeCount;
    }
    /** Get the world coordinates of the point. */
    get point() {
        return this._detail.point;
    }
    /** Get the application-specific number. */
    get a() {
        return this._detail.a;
    }
    /** Get the projection of the point onto the closest facet edge. */
    get closestEdge() {
        return { startVertexIndex: this._detail.closestEdgeIndex, edgeParam: this._detail.closestEdgeParam };
    }
    /** Test validity of fields other than _detail. */
    get _isValid() {
        return this._facetIndex >= 0 && this._edgeCount >= 3;
    }
    /** Whether this instance specifies a valid location. */
    get isValid() {
        return this._isValid && this._detail.isValid;
    }
    /** Whether the facet is convex. Always returns false, as convexity is unknown to this detail. */
    get isConvex() {
        return false;
    }
    /** Whether the point is inside or on the polygon. */
    get isInsideOrOn() {
        return this._isValid && this._detail.isInsideOrOn;
    }
    /** Get the code that classifies the location of the point with respect to the facet. */
    get classify() {
        return this._detail.code;
    }
    /** Clone the instance */
    clone() {
        const detail = new NonConvexFacetLocationDetail();
        detail.copyContentsFrom(this);
        return detail;
    }
    /** Set the instance contents from the other detail.
     * @param other detail to clone
     */
    copyContentsFrom(other) {
        this._facetIndex = other._facetIndex;
        this._edgeCount = other._edgeCount;
        this._detail.copyContentsFrom(other._detail);
    }
    /** Interpolated data is not defined for a non-convex facet.
     * @returns undefined
    */
    getNormal() {
        return undefined;
    }
    /** Interpolated data is not defined for a non-convex facet.
     * @returns undefined
    */
    getParam() {
        return undefined;
    }
    /** Interpolated data is not defined for a non-convex facet.
     * @returns undefined
    */
    getColor() {
        return undefined;
    }
    /** Barycentric coordinates are not defined for a non-convex facet.
     * @returns undefined
     */
    getBarycentricCoordinates() {
        return undefined;
    }
}
/**
 * Implementation of `FacetLocationDetail` for a convex facet.
 * * If `edgeCount` is 3, `TriangularFacetLocationDetail` is more efficient.
 * @public
 */
export class ConvexFacetLocationDetail extends NonConvexFacetLocationDetail {
    _normal;
    _param;
    _color;
    _barycentricCoordinates;
    /** captures the detail if provided */
    constructor(facetIndex = -1, edgeCount = 0, detail) {
        super(facetIndex, edgeCount, detail);
    }
    /** Invalidate this detail. */
    invalidate(deep = true) {
        super.invalidate(deep);
        this._normal = undefined;
        this._param = undefined;
        this._color = undefined;
        this._barycentricCoordinates = undefined;
    }
    /** Create a detail.
     * @param detail optional, copied if given
     * @param result optional pre-allocated object to fill and return
     */
    static create(facetIndex = -1, edgeCount = 0, detail, result) {
        if (!result)
            result = new ConvexFacetLocationDetail();
        else
            result.invalidate(false); // shallow: detail might be owned by result!
        return super.create(facetIndex, edgeCount, detail, result);
    }
    /** Create a detail, capturing inputs. */
    static createCapture(facetIndex, edgeCount, detail) {
        return new ConvexFacetLocationDetail(facetIndex, edgeCount, detail);
    }
    /** Whether the facet is convex. */
    get isConvex() {
        return true;
    }
    /** Clone the instance */
    clone() {
        const detail = new ConvexFacetLocationDetail();
        detail.copyContentsFrom(this);
        return detail;
    }
    /** Set the instance contents from the other detail.
     * @param other detail to clone
     */
    copyContentsFrom(other) {
        super.copyContentsFrom(other);
        this._normal = other._normal?.clone();
        this._param = other._param?.clone();
        this._color = other._color;
        this._barycentricCoordinates = other._barycentricCoordinates?.slice();
    }
    /** Get normal interpolated from facet data.
     * @param facetNormals used to compute the normal cache
     * @param facetVertices used to compute the barycentric coordinate cache
     * @param distanceTolerance used to compute the barycentric coordinate cache
     * @returns reference to cached normal
     */
    getNormal(facetNormals, facetVertices, distanceTolerance = Geometry.smallMetricDistance) {
        if (this._detail.isValid && undefined === this._normal && undefined !== facetNormals) {
            const scales = this.getBarycentricCoordinates(facetVertices, distanceTolerance);
            if (undefined !== scales) {
                this._normal = Vector3d.create();
                facetNormals.linearCombination(scales, this._normal);
            }
        }
        return this._normal;
    }
    /** Get uv parameter interpolated from facet data.
     * @param facetParams used to compute the uv parameter cache
     * @param facetVertices used to compute the barycentric coordinate cache
     * @param distanceTolerance used to compute the barycentric coordinate cache
     * @returns reference to cached uv parameter
     */
    getParam(facetParams, facetVertices, distanceTolerance = Geometry.smallMetricDistance) {
        if (this._detail.isValid && undefined === this._param && undefined !== facetParams) {
            const scales = this.getBarycentricCoordinates(facetVertices, distanceTolerance);
            if (undefined !== scales) {
                this._param = Point2d.create();
                facetParams.linearCombination(scales, this._param);
            }
        }
        return this._param;
    }
    /** Get color interpolated from facet data.
     * @param facetColors used to compute the color cache
     * @param facetVertices used to compute the barycentric coordinate cache
     * @param distanceTolerance used to compute the barycentric coordinate cache
     * @returns cached color
     */
    getColor(facetColors, facetVertices, distanceTolerance = Geometry.smallMetricDistance) {
        if (this._detail.isValid && undefined === this._color && undefined !== facetColors) {
            const scales = this.getBarycentricCoordinates(facetVertices, distanceTolerance);
            if (undefined !== scales)
                this._color = NumberArray.linearCombinationOfColors(facetColors, scales);
        }
        return this._color;
    }
    /** Get the barycentric coordinates of this location, if they have been computed.
     * @param facetVertices used to compute the barycentric coordinate cache
     * @param distanceTolerance used to compute the barycentric coordinate cache
     * @returns cached barycentric coordinates
    */
    getBarycentricCoordinates(facetVertices, distanceTolerance = Geometry.smallMetricDistance) {
        if (this._detail.isValid && undefined === this._barycentricCoordinates && undefined !== facetVertices) {
            this._barycentricCoordinates = PolygonOps.convexBarycentricCoordinates(facetVertices, this._detail.point, distanceTolerance);
        }
        return this._barycentricCoordinates;
    }
}
//# sourceMappingURL=FacetLocationDetail.js.map