/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Bspline
 */
import { GeometryQuery } from "../curve/GeometryQuery";
import { AxisOrder, Geometry } from "../Geometry";
import { Matrix3d } from "../geometry3d/Matrix3d";
import { Plane3dByOriginAndVectors } from "../geometry3d/Plane3dByOriginAndVectors";
import { Point3d } from "../geometry3d/Point3dVector3d";
import { Point3dArray, Point4dArray } from "../geometry3d/PointHelpers";
import { Transform } from "../geometry3d/Transform";
import { Point4d } from "../geometry4d/Point4d";
import { BSplineWrapMode, KnotVector } from "./KnotVector";
/**
 * UVSelect is an integer indicating uDirection (0) or vDirection (1) in a bspline surface parameterization.
 * @public
 */
export var UVSelect;
(function (UVSelect) {
    /** index of u direction */
    UVSelect[UVSelect["uDirection"] = 0] = "uDirection";
    /**
     * index of v direction
     * @deprecated in 4.3.0 - will not be removed until after 2026-06-13. Use vDirection instead.
     */
    UVSelect[UVSelect["VDirection"] = 1] = "VDirection";
    /** index of v direction */
    UVSelect[UVSelect["vDirection"] = 1] = "vDirection";
})(UVSelect || (UVSelect = {}));
/**
 * Enumeration of how weights are carried
 * * UnWeighted (0) -- there are no weights
 * * WeightsAlreadyAppliedToCoordinates (1) -- for real point (x,y,z) the homogeneous point (wx,wy,wx,w) is stored as (wx,wy,wz,w)
 * * WeightsSeparateFromCoordinates (2) -- for real point (x,y,z) the homogeneous point (wx,wy,wx,w) is stored as (x,y,z,w)
 *   * Note that "internal" computations never use WeightsSeparateFromCoordinates.
 *   * WeightsSeparateFromCoordinates is only useful as input or output state in serializer.
 * @public
 */
export var WeightStyle;
(function (WeightStyle) {
    /** There are no weights. */
    WeightStyle[WeightStyle["UnWeighted"] = 0] = "UnWeighted";
    /**
     * * Data is weighted.
     * * The point with normalized coordinate `[x,y,z]` and weight `w` is stored as `[x*w,y*w,z*w,w]`
     * */
    WeightStyle[WeightStyle["WeightsAlreadyAppliedToCoordinates"] = 1] = "WeightsAlreadyAppliedToCoordinates";
    /**
     * * Data is weighted.
     * * The point with normalized coordinate `[x,y,z]` and weight `w` is stored as `[x,y,z,w]`
     * */
    WeightStyle[WeightStyle["WeightsSeparateFromCoordinates"] = 2] = "WeightsSeparateFromCoordinates";
})(WeightStyle || (WeightStyle = {}));
/** Bspline knots and poles for 2d-to-Nd.
 * * This abstract class in not independently instantiable -- GeometryQuery methods must be implemented by derived classes.
 * @public
 */
export class BSpline2dNd extends GeometryQuery {
    /** String name for schema properties */
    geometryCategory = "bsurf";
    /** Array of (exactly 2) knot vectors for the u, v directions */
    knots;
    /** flat array of coordinate data, blocked by poleDimension and row */
    coffs;
    /**
     * Number of components per pole.
     * * 3 for conventional xyz surface
     * * 4 for weighted (wx, wy, wz, w) surface.
     */
    poleDimension;
    _numPoles;
    /** Return the degree (one less than order) for the `select` direction (0 or 1) */
    degreeUV(select) { return this.knots[select].degree; }
    /** Return the order (one more than degree) for the `select` direction (0 or 1) */
    orderUV(select) { return this.knots[select].degree + 1; }
    /** Return the number of spans (INCLUDING NULL SPANS) for the `select` direction (0 or 1) */
    numSpanUV(select) { return this._numPoles[select] - this.knots[select].degree; }
    /** Return the total number of poles (product of x and y pole counts) */
    numPolesTotal() { return this.coffs.length / this.poleDimension; }
    /** Return the number of poles for the `select` direction (0 or 1) */
    numPolesUV(select) { return this._numPoles[select]; }
    /** Return the step between adjacent poles for the `select` direction (0 or 1) */
    poleStepUV(select) { return select === 0 ? 1 : this._numPoles[0]; }
    /** Confirm that order and pole counts agree for both u and v directions */
    static validOrderAndPoleCounts(orderU, numPolesU, orderV, numPolesV, numUV) {
        if (orderU < 2 || numPolesU < orderU)
            return false;
        if (orderV < 2 || numPolesV < orderV)
            return false;
        if (numPolesU * numPolesV !== numUV)
            return false;
        return true;
    }
    /** Get the indexed Point3d.
     * * (IMPORTANT) This assumes this is an xyz surface.  Data will be incorrect if this is an xyzw surface.
     * @param i index in [0, numPolesU)
     * @param j index in [0, numPolesV)
     */
    getPoint3dPole(i, j, result) {
        return Point3d.createFromPacked(this.coffs, i + j * this._numPoles[0], result);
    }
    /** Get the indexed Point3d, projecting the weight away to get to xyz.
     * * (IMPORTANT) This assumes this is an xyzw surface.  Data will be incorrect if this is an xyz surface.
     * @param i index in [0, numPolesU)
     * @param j index in [0, numPolesV)
     */
    getPoint3dPoleXYZW(i, j, result) {
        return Point3d.createFromPackedXYZW(this.coffs, i + j * this._numPoles[0], result);
    }
    /** Get the indexed Point4d.
     * * (IMPORTANT) This assumes this is an xyzw surface.  Data will be incorrect if this is an xyz surface.
     * @param i index in [0, numPolesU)
     * @param j index in [0, numPolesV)
     */
    getPoint4dPole(i, j, result) {
        return Point4d.createFromPacked(this.coffs, (i + j * this._numPoles[0]) * 4, result);
    }
    /**
     * Return 0 for 0 input, 1 for any nonzero input.
     * @param value numeric value to convert to strict 0 or 1.
     */
    numberToUVSelect(value) { return value === 0 ? 0 : 1; }
    /** extend a range, treating each block as simple XYZ */
    extendRangeXYZ(rangeToExtend, transform) {
        const buffer = this.coffs;
        const pd = this.poleDimension;
        const n = buffer.length + 1 - pd;
        if (transform) {
            for (let i0 = 0; i0 < n; i0 += pd)
                rangeToExtend.extendTransformedXYZ(transform, buffer[i0], buffer[i0 + 1], buffer[i0 + 2]);
        }
        else {
            for (let i0 = 0; i0 < n; i0 += pd)
                rangeToExtend.extendXYZ(buffer[i0], buffer[i0 + 1], buffer[i0 + 2]);
        }
    }
    /** extend a range, treating each block as homogeneous xyzw, with weight at offset 3 */
    extendRangeXYZH(rangeToExtend, transform) {
        const buffer = this.coffs;
        const pd = this.poleDimension;
        const n = buffer.length + 1 - pd;
        let w = 0;
        let divW = 0;
        if (transform) {
            for (let i0 = 0; i0 < n; i0 += pd) {
                w = buffer[i0 + 3];
                if (w !== 0.0) {
                    divW = 1.0 / w;
                    rangeToExtend.extendTransformedXYZ(transform, buffer[i0] * divW, buffer[i0 + 1] * divW, buffer[i0 + 2] * divW);
                }
            }
        }
        else {
            for (let i0 = 0; i0 < n; i0 += pd) {
                w = buffer[i0 + 3];
                if (w !== 0.0) {
                    divW = 1.0 / w;
                    rangeToExtend.extendXYZ(buffer[i0] * divW, buffer[i0 + 1] * divW, buffer[i0 + 2] * divW);
                }
            }
        }
    }
    /**
     * evaluate the surface at u and v fractions. Return a (squared, right handed) coordinate frame at that point on the surface.
     * @param fractionU u parameter
     * @param fractionV v parameter
     * @param result undefined if surface derivatives are parallel (or either alone is zero)
     */
    fractionToRigidFrame(fractionU, fractionV, result) {
        const skewVectors = this.fractionToPointAndDerivatives(fractionU, fractionV);
        if (!skewVectors)
            return undefined;
        const axes = Matrix3d.createColumnsInAxisOrder(AxisOrder.XYZ, skewVectors.vectorU, skewVectors.vectorV, undefined);
        const axes1 = Matrix3d.createRigidFromMatrix3d(axes, AxisOrder.XYZ, axes);
        if (axes1)
            result = Transform.createOriginAndMatrix(skewVectors.origin, axes1, result);
        return result;
    }
    /** a scratch array sized for `order` numbers */
    _basisBufferUV; //  basis function buffers for u, v directions.   ALLOCATED BY CTOR FOR FREQUENT REUSE
    /** a scratch array sized for `order` numbers */
    _basisBuffer1UV; // basis function buffers for u, v directions.   ALLOCATED BY CTOR FOR FREQUENT REUSE
    /** a scratch array sized for one pole */
    _poleBuffer; // one set of target values.  ALLOCATED BY CTOR FOR FREQUENT REUSE
    /** array of 2 scratch array, each sized for one pole
     * * used in derivative evaluations, with respective u and v derivatives in the respective arrays.
    */
    _poleBuffer1UV; // one set of target values.  ALLOCATED BY CTOR FOR FREQUENT REUSE
    /**
     * initialize arrays for given spline dimensions.
     * coffs length must be poleLength * numPolesU * numPolesV    !!!!
     */
    constructor(numPolesU, numPolesV, poleLength, knotsU, knotsV, coffs) {
        super();
        const orderU = knotsU.degree + 1;
        const orderV = knotsV.degree + 1;
        this.knots = [knotsU, knotsV];
        this.coffs = coffs;
        this.poleDimension = poleLength;
        this._basisBufferUV = [new Float64Array(orderU), new Float64Array(orderV)];
        this._basisBuffer1UV = [new Float64Array(orderU), new Float64Array(orderV)];
        this._numPoles = [numPolesU, numPolesV];
        this._poleBuffer = new Float64Array(poleLength);
        this._poleBuffer1UV = [new Float64Array(poleLength), new Float64Array(poleLength)];
    }
    /**
     * Map a position, specified as (uv direction, bezier span, fraction within the bezier), to an overall knot value.
     * @param select selector indicating U or V direction.
     * @param span index of bezier span
     * @param localFraction fractional coordinate within the bezier span
     */
    spanFractionToKnot(select, span, localFraction) {
        return this.knots[select].spanFractionToKnot(span, localFraction);
    }
    /** Evaluate basis functions given
     * * choice of u or v
     * * span index
     * * local fraction within the span.
     * @returns true if and only if output arrays are sufficiently sized
     */
    spanFractionsToBasisFunctions(select, spanIndex, spanFraction, f, df) {
        spanIndex = Geometry.clampToStartEnd(spanIndex, 0, this.numSpanUV(select));
        const knotIndex0 = spanIndex + this.degreeUV(select) - 1;
        const globalKnot = this.knots[select].baseKnotFractionToKnot(knotIndex0, spanFraction);
        return df ?
            this.knots[select].evaluateBasisFunctions1(knotIndex0, globalKnot, f, df) :
            this.knots[select].evaluateBasisFunctions(knotIndex0, globalKnot, f);
    }
    /** sum poles by the weights in the basisBuffer, using poles for given span */
    sumPoleBufferForSpan(spanIndexU, spanIndexV) {
        const poleBuffer = this._poleBuffer;
        const coffs = this.coffs;
        poleBuffer.fill(0);
        const m = this.poleDimension;
        const stepV = this.poleDimension * this._numPoles[0];
        let kU = m * spanIndexU + spanIndexV * stepV;
        let g = 0;
        for (const fV of this._basisBufferUV[1]) {
            let k = kU;
            for (const fU of this._basisBufferUV[0]) {
                g = fU * fV;
                for (let j = 0; j < m; j++) {
                    poleBuffer[j] += g * coffs[k++];
                }
            }
            kU += stepV;
        }
    }
    /**
     * sum poles by the weights in the basisBuffer, using poles for given span
     * @deprecated in 4.3.0 - will not be removed until after 2026-06-13. Use sumPoleBufferDerivativesForSpan instead.
     */
    sumpoleBufferDerivativesForSpan(spanIndexU, spanIndexV) {
        return this.sumPoleBufferDerivativesForSpan(spanIndexU, spanIndexV);
    }
    /** sum derivatives by the weights in the basisBuffer, using poles for given span */
    sumPoleBufferDerivativesForSpan(spanIndexU, spanIndexV) {
        const poleBuffer1U = this._poleBuffer1UV[0];
        const poleBuffer1V = this._poleBuffer1UV[1];
        poleBuffer1U.fill(0);
        poleBuffer1V.fill(0);
        const m = this.poleDimension;
        const stepV = this.poleDimension * this._numPoles[0];
        let kU = m * spanIndexU + spanIndexV * stepV;
        // U partial derivatives ...
        let g = 0;
        for (const fV of this._basisBufferUV[1]) {
            let k = kU;
            for (const fU of this._basisBuffer1UV[0]) {
                g = fU * fV;
                for (let j = 0; j < m; j++) {
                    poleBuffer1U[j] += g * this.coffs[k++];
                }
            }
            kU += stepV;
        }
        // V partial derivatives ...
        kU = m * spanIndexU + spanIndexV * stepV;
        for (const fV of this._basisBuffer1UV[1]) {
            let k = kU;
            for (const fU of this._basisBufferUV[0]) {
                g = fU * fV;
                for (let j = 0; j < m; j++) {
                    poleBuffer1V[j] += g * this.coffs[k++];
                }
            }
            kU += stepV;
        }
    }
    /**
     * Evaluate the _basisBuffer, _poleBuffer and (optionally) _basisBuffer1 and _poleBuffer1 arrays at given knot.
     *
     * @param u u knot value
     * @param v v not value
     * @param numDerivative number of derivatives needed
     */
    evaluateBuffersAtKnot(u, v, numDerivative = 0) {
        const knotIndex0U = this.knots[0].knotToLeftKnotIndex(u);
        const knotIndex0V = this.knots[1].knotToLeftKnotIndex(v);
        const poleIndex0U = knotIndex0U - this.degreeUV(0) + 1;
        const poleIndex0V = knotIndex0V - this.degreeUV(1) + 1;
        if (numDerivative < 1) {
            this.knots[0].evaluateBasisFunctions(knotIndex0U, u, this._basisBufferUV[0]);
            this.knots[1].evaluateBasisFunctions(knotIndex0V, v, this._basisBufferUV[1]);
            this.sumPoleBufferForSpan(poleIndex0U, poleIndex0V);
        }
        else {
            this.knots[0].evaluateBasisFunctions1(knotIndex0U, u, this._basisBufferUV[0], this._basisBuffer1UV[0]);
            this.knots[1].evaluateBasisFunctions1(knotIndex0V, v, this._basisBufferUV[1], this._basisBuffer1UV[1]);
            this.sumPoleBufferForSpan(poleIndex0U, poleIndex0V);
            this.sumPoleBufferDerivativesForSpan(poleIndex0U, poleIndex0V);
        }
    }
    // Swap numSwap entries in coffs, starting at i0 and i1 (absolute indices -- not blocks)
    swapBlocks(i0, i1, numSwap) {
        let a;
        for (let i = 0; i < numSwap; i++) {
            a = this.coffs[i0 + i];
            this.coffs[i0 + i] = this.coffs[i1 + i];
            this.coffs[i1 + i] = a;
        }
    }
    /**
     * Reverse the parameter direction for either u or v.
     * @param select direction to reverse -- 0 for u, 1 for v.
     */
    reverseInPlace(select) {
        const m = this.poleDimension;
        const numU = this.numPolesUV(0);
        const numV = this.numPolesUV(1);
        if (select === 0) {
            // reverse within rows.
            for (let j = 0; j < numV; j++) {
                const rowStart = j * numU * m;
                for (let i0 = 0, i1 = numU - 1; i0 < i1; i0++, i1--) {
                    this.swapBlocks(rowStart + i0 * m, rowStart + i1 * m, m);
                }
            }
        }
        else {
            // swap full rows ..
            const numPerRow = m * numU;
            for (let i0 = 0, i1 = (numV - 1) * numPerRow; i0 < i1; i0 += numPerRow, i1 -= numPerRow) {
                this.swapBlocks(i0, i1, numPerRow);
            }
        }
        this.knots[select].reflectKnots();
    }
    /**
     * Get the flag indicating the surface might be suitable for having wrapped "closed" interpretation.
     */
    getWrappable(select) {
        return this.knots[select].wrappable;
    }
    /**
     * Set the flag indicating the surface might be suitable for having wrapped "closed" interpretation.
     */
    setWrappable(select, value) {
        this.knots[select].wrappable = value;
    }
    /**
     * Test if leading and trailing blocks of points match in a given direction.
     * @param data packed array of points in row-major order (numRows x numColumns x dimension numbers)
     * @param numRows number of rows of points in the array
     * @param numColumns number of columns of points in the array (equal to the number of points in each row)
     * @param dimension point dimension (e.g., 2,3,4)
     * @param blockLength number of leading/trailing points to check
     * @param select 0 to test first/last columns of points; 1 to test first/last rows of points
     * @returns true if coordinates matched
     */
    static isWrappedGrid(data, numRows, numColumns, dimension, blockLength, select) {
        const rowToRowStep = numColumns * dimension;
        if (UVSelect.uDirection === select) {
            // Test the contiguous block at the start/end of each row
            const numTest = dimension * blockLength;
            for (let row = 0; row < numRows; row++) {
                const i0 = row * rowToRowStep;
                const i1 = i0 + rowToRowStep - numTest;
                for (let i = 0; i < numTest; i++) {
                    if (!Geometry.isSameCoordinate(data[i0 + i], data[i1 + i]))
                        return false;
                }
            }
        }
        else {
            // Test the entire multi-row contiguous block at the start/end of the array
            const numTest = blockLength * rowToRowStep;
            const i1 = numRows * numColumns * dimension - numTest;
            for (let i = 0; i < numTest; i++) {
                if (!Geometry.isSameCoordinate(data[i], data[i1 + i]))
                    return false;
            }
        }
        return true;
    }
    /**
     * Test if `degree` leading and trailing (one of U or V) blocks match, as if the data is a non-periodic physically closed spline in the selected direction.
     * @param select select U or V direction
     * @returns true if coordinates matched.
     */
    testClosableGrid(select, mode) {
        if (mode === undefined)
            mode = this.knots[select].wrappable;
        if (mode === BSplineWrapMode.OpenByAddingControlPoints) // the last degree poles equal the first degree poles
            return BSpline2dNd.isWrappedGrid(this.coffs, this.numPolesUV(UVSelect.vDirection), this.numPolesUV(UVSelect.uDirection), this.poleDimension, this.degreeUV(select), select);
        if (mode === BSplineWrapMode.OpenByRemovingKnots) // the last pole equals the first pole
            return BSpline2dNd.isWrappedGrid(this.coffs, this.numPolesUV(UVSelect.vDirection), this.numPolesUV(UVSelect.uDirection), this.poleDimension, 1, select);
        return false;
    }
    /**
     * Test knots and control points to determine if it is possible to close (aka "wrap") the surface in the selected parametric direction.
     * @param select select U or V direction
     * @return whether the surface can be wrapped in the given parametric direction.
     */
    isClosable(select) {
        return BSplineWrapMode.None !== this.isClosableSurface(select);
    }
    /**
     * Test knots and control points to determine if it is possible to close (aka "wrap") the surface in the selected parametric direction.
     * @param select select U or V direction
     * @return the manner of closing. See `BSplineWrapMode` for particulars of each mode.
     */
    isClosableSurface(select) {
        const mode = this.knots[select].wrappable;
        if (mode === BSplineWrapMode.None)
            return BSplineWrapMode.None;
        if (!this.knots[select].testClosable(mode))
            return BSplineWrapMode.None;
        if (!this.testClosableGrid(select, mode))
            return BSplineWrapMode.None;
        return mode;
    }
}
/**  BSplineSurface3d is a parametric surface in xyz space.
 * * This (BSplineSurface3d) is an unweighted surface.   Use the separate class BSplineSurface3dH for a weighted surface.
 *
 * The various static "create" methods have subtle differences in how grid sizes are conveyed:
 * | Method | control point array | counts |
 * | create | flat array of [x,y,z] | arguments numPolesU, numPolesV |
 * | createGrid | array of array of [x,y,z ] | There are no `numPolesU` or `numPolesV` args. The counts are conveyed by the deep arrays |
 * @public
 */
export class BSplineSurface3d extends BSpline2dNd {
    /** Test if `other` is an instance of `BSplineSurface3d */
    isSameGeometryClass(other) { return other instanceof BSplineSurface3d; }
    /** Apply the transform to the poles */
    tryTransformInPlace(transform) { Point3dArray.multiplyInPlace(transform, this.coffs); return true; }
    /** Return a pole by u and v indices */
    getPole(i, j, result) {
        return this.getPoint3dPole(i, j, result);
    }
    constructor(numPolesU, numPolesV, knotsU, knotsV, coffs) {
        super(numPolesU, numPolesV, 3, knotsU, knotsV, coffs);
    }
    /**
     * Return control points json arrays.
     * * if `flatArray===true`, each point appears as an array [x,y,z] in row-major order of a containing array.
     * * if `flatArray===false` each row of points is an an array of [x,y,z] in an array.  Each of these row arrays is in the result array.
     */
    getPointArray(flatArray = true) {
        if (flatArray)
            return Point3dArray.unpackNumbersToNestedArrays(this.coffs, 3);
        return Point3dArray.unpackNumbersToNestedArraysIJK(this.coffs, 3, this.numPolesUV(0));
    }
    /**
     * Return control points json arrays.
     * * Each row of points is an an array.
     * * Within the array for each row, each point is an array [x,y,z]
     */
    getPointGridJSON() {
        const result = {
            points: Point3dArray.unpackNumbersToNestedArraysIJK(this.coffs, 3, this.numPolesUV(0)),
            weightStyle: WeightStyle.UnWeighted,
            numCartesianDimensions: 3,
        };
        return result;
    }
    /** Return a simple array of the control points coordinates */
    copyPointsFloat64Array() { return this.coffs.slice(); }
    /**
     * return a simple array form of the knots.  optionally replicate the first and last
     * in classic over-clamped manner
     */
    copyKnots(select, includeExtraEndKnot) { return this.knots[select].copyKnots(includeExtraEndKnot); }
    /**
     * Create a bspline surface.
     * * This `create` variant takes control points in a "flattened" array, with
     *  points from succeeding U rows packed together in one array.  Use `createGrid` if the points are in
     *  a row-by-row grid structure
     * * knotArrayU and knotArrayV are optional -- uniform knots are implied if they are omitted (undefined).
     * * When knots are given, two knot count conditions are recognized:
     *     * If poleArray.length + order == knotArray.length, the first and last are assumed to be the
     *      extraneous knots of classic clamping.
     *     * If poleArray.length + order == knotArray.length + 2, the knots are in modern form that does not have
     *      the classic unused first and last knot.
     * @param controlPointArray Array of points, ordered along the U direction.
     * @param numPoleU number of poles in each row
     * @param orderU order for the U direction polynomial (`order` is one more than the `degree`.  "cubic" polynomial is order 4.)
     * @param knotArrayU knots for the V direction.  See note above about knot counts.
     * @param numPoleV number of rows of poles
     * @param orderV order for the V direction polynomial (`order` is one more than the `degree`.  "cubic" polynomial is order 4.)
     * @param knotArrayV knots for the V direction.  See note above about knot counts.
     */
    static create(controlPointArray, numPolesU, orderU, knotArrayU, numPolesV, orderV, knotArrayV) {
        let numPoles = controlPointArray.length;
        if (controlPointArray instanceof Float64Array)
            numPoles /= 3;
        if (!this.validOrderAndPoleCounts(orderU, numPolesU, orderV, numPolesV, numPoles))
            return undefined;
        // shift knots-of-interest limits for over-clamped case ...
        const numKnotsU = knotArrayU ? knotArrayU.length : numPolesU + orderU - 2;
        const numKnotsV = knotArrayV ? knotArrayV.length : numPolesV + orderV - 2;
        const skipFirstAndLastU = (numPolesU + orderU === numKnotsU);
        const skipFirstAndLastV = (numPolesV + orderV === numKnotsV);
        const knotsU = knotArrayU ?
            KnotVector.create(knotArrayU, orderU - 1, skipFirstAndLastU) :
            KnotVector.createUniformClamped(numPolesU, orderU - 1, 0.0, 1.0);
        const knotsV = knotArrayV ?
            KnotVector.create(knotArrayV, orderV - 1, skipFirstAndLastV) :
            KnotVector.createUniformClamped(numPolesV, orderV - 1, 0.0, 1.0);
        const coffs = new Float64Array(3 * numPolesU * numPolesV);
        if (controlPointArray instanceof Float64Array) {
            let i = 0;
            for (const coordinate of controlPointArray) {
                coffs[i++] = coordinate;
            }
        }
        else {
            let i = 0;
            for (const p of controlPointArray) {
                coffs[i++] = p.x;
                coffs[i++] = p.y;
                coffs[i++] = p.z;
            }
        }
        const surface = new BSplineSurface3d(numPolesU, numPolesV, knotsU, knotsV, coffs);
        return surface;
    }
    /**
     * Create a bspline surface.
     * * This `create` variant takes control points in a "grid" array, with the points from
     *   each grid row `[rowIndex]` being an independent array `points[rowIndex][indexAlongRow][x,y,z]`
     * * knotArrayU and knotArrayV are optional -- uniform knots are implied if they are omitted (undefined).
     * * When knots are given, two knot count conditions are recognized in each direction:
     *    * If poleArray.length + order == knotArray.length, the first and last are assumed to be the
     *      extraneous knots of classic clamping.
     *    * If poleArray.length + order == knotArray.length + 2, the knots are in modern form that does not have
     *      the classic unused first and last knot.
     * @param points Array of points, ordered along the U direction.
     * @param orderU order for the U direction polynomial (`order` is one more than the `degree`.  "cubic" polynomial is order 4.)
     * @param knotArrayU knots for the V direction.  See note above about knot counts.
     * @param orderV order for the V direction polynomial (`order` is one more than the `degree`.  "cubic" polynomial is order 4.)
     * @param knotArrayV knots for the V direction.  See note above about knot counts.
     */
    static createGrid(points, orderU, knotArrayU, orderV, knotArrayV) {
        const numPolesV = points.length;
        const numPolesU = points[0].length;
        const numPoles = numPolesU * numPolesV;
        if (3 !== points[0][0].length)
            return undefined;
        if (!this.validOrderAndPoleCounts(orderU, numPolesU, orderV, numPolesV, numPoles))
            return undefined;
        // shift knots-of-interest limits for overclamped case ...
        const numKnotsU = knotArrayU ? knotArrayU.length : numPolesU + orderU - 2;
        const numKnotsV = knotArrayV ? knotArrayV.length : numPolesV + orderV - 2;
        const skipFirstAndLastU = (numPolesU + orderU === numKnotsU);
        const skipFirstAndLastV = (numPolesV + orderV === numKnotsV);
        const knotsU = knotArrayU ?
            KnotVector.create(knotArrayU, orderU - 1, skipFirstAndLastU) :
            KnotVector.createUniformClamped(numPolesU, orderU - 1, 0.0, 1.0);
        const knotsV = knotArrayV ?
            KnotVector.create(knotArrayV, orderV - 1, skipFirstAndLastV) :
            KnotVector.createUniformClamped(numPolesV, orderV - 1, 0.0, 1.0);
        const coffs = new Float64Array(3 * numPolesU * numPolesV);
        let i = 0;
        for (const row of points) {
            for (const xyz of row) {
                coffs[i++] = xyz[0];
                coffs[i++] = xyz[1];
                coffs[i++] = xyz[2];
            }
        }
        return new BSplineSurface3d(numPolesU, numPolesV, knotsU, knotsV, coffs);
    }
    /**
     * Return a complete copy of the bspline surface.
     */
    clone() {
        const knotVector1U = this.knots[0].clone();
        const knotVector1V = this.knots[1].clone();
        const surface1 = new BSplineSurface3d(this.numPolesUV(0), this.numPolesUV(1), knotVector1U, knotVector1V, this.coffs.slice());
        return surface1;
    }
    /**
     * Return a complete copy of the bspline surface, with a transform applied to the control points.
     * @param transform transform to apply to the control points
     */
    cloneTransformed(transform) {
        const surface1 = this.clone();
        surface1.tryTransformInPlace(transform);
        return surface1;
    }
    /** Evaluate at a position given by u and v coordinates in knot space.
     * @param u u value, in knot range.
     * @param v v value in knot range.
   * @returns Return the xyz coordinates on the surface.
     */
    knotToPoint(u, v) {
        this.evaluateBuffersAtKnot(u, v);
        return Point3d.createFrom(this._poleBuffer);
    }
    /** Evaluate at a position given by a knot value.  */
    knotToPointAndDerivatives(u, v, result) {
        this.evaluateBuffersAtKnot(u, v, 1);
        return Plane3dByOriginAndVectors.createOriginAndVectorsArrays(this._poleBuffer, this._poleBuffer1UV[0], this._poleBuffer1UV[1], result);
    }
    /** Evaluate at a position given by fractional coordinate in each direction.
       * @param fractionU u coordinate, as a fraction of the knot range.
       * @param fractionV v coordinate, as a fraction of the knot range.
     * @returns Return the xyz coordinates on the surface.
     */
    fractionToPoint(fractionU, fractionV) {
        return this.knotToPoint(this.knots[0].fractionToKnot(fractionU), this.knots[1].fractionToKnot(fractionV));
    }
    /**
     * evaluate the surface at u and v fractions.
     * @returns plane with origin at the surface point, direction vectors are derivatives in the u and v directions.
     * @param fractionU u coordinate, as a fraction of the knot range.
     * @param fractionV v coordinate, as a fraction of the knot range.
     * @param result optional pre-allocated object for return values.
     * @returns Returns point and derivative directions.
     */
    fractionToPointAndDerivatives(fractionU, fractionV, result) {
        const knotU = this.knots[0].fractionToKnot(fractionU);
        const knotV = this.knots[1].fractionToKnot(fractionV);
        return this.knotToPointAndDerivatives(knotU, knotV, result);
    }
    /** Implementation of the UVSurface interface; allows `PolyfaceBuilder.addUVGridBody` to facet this B-spline surface. */
    uvFractionToPoint(u, v) {
        return this.fractionToPoint(u, v);
    }
    /** Implementation of the UVSurface interface; allows `PolyfaceBuilder.addUVGridBody` to facet this B-spline surface. */
    uvFractionToPointAndTangents(u, v, result) {
        return this.fractionToPointAndDerivatives(u, v, result);
    }
    /** test for identical counts and near-equal coordinates */
    isAlmostEqual(other) {
        if (other instanceof BSplineSurface3d) {
            return this.knots[0].isAlmostEqual(other.knots[0])
                && this.knots[1].isAlmostEqual(other.knots[1])
                && Point3dArray.isAlmostEqual(this.coffs, other.coffs);
        }
        return false;
    }
    /** Test if all poles are in a plane */
    isInPlane(plane) {
        return Point3dArray.isCloseToPlane(this.coffs, plane);
    }
    /** Second step of double dispatch:  call `handler.handleBSplineSurface3d(this)` */
    dispatchToGeometryHandler(handler) {
        return handler.handleBSplineSurface3d(this);
    }
    /** Extend the range to include all poles
     * * This is not a tight range.
     */
    extendRange(rangeToExtend, transform) {
        this.extendRangeXYZ(rangeToExtend, transform);
    }
}
/**  BSpline Surface in xyzw homogeneous space
 * @public
 */
export class BSplineSurface3dH extends BSpline2dNd {
    /** Test if `other` is an instance of `BSplineSurface3dH */
    isSameGeometryClass(other) { return other instanceof BSplineSurface3dH; }
    /** Apply the transform to the poles */
    tryTransformInPlace(transform) {
        Point4dArray.multiplyInPlace(transform, this.coffs);
        return true;
    }
    /** Return a pole by u and v indices */
    getPole(i, j, result) {
        return this.getPoint3dPoleXYZW(i, j, result);
    }
    constructor(numPolesU, numPolesV, knotsU, knotsV, coffs) {
        super(numPolesU, numPolesV, 4, knotsU, knotsV, coffs);
    }
    /** Unpack the control points to a Point4d array of form [wx,wy,wz,w]. */
    copyPoints4d() { return Point4dArray.unpackToPoint4dArray(this.coffs); }
    /**
     * Unpack the control points to a Point3d array and an array of weights.
     * @param points output xyz, weighted by default formatter
     * @param weights output weights
     * @param formatter optional xyz formatter. By default, returns a Point3d of form [wx,wy,wz].
     */
    copyPointsAndWeights(points, weights, formatter = (x, y, z) => Point3d.create(x, y, z)) {
        Point4dArray.unpackFloat64ArrayToPointsAndWeights(this.coffs, points, weights, formatter);
    }
    /**
     * Copy the control points to a packed 3D array.
     * @param unweight if true, output array has form x,y,z; if false, output array has form wx,wy,wz.
     */
    copyXYZToFloat64Array(unweight) {
        const numPoints = Math.floor(this.coffs.length / 4);
        const result = new Float64Array(numPoints * 3);
        let j = 0;
        for (let i = 0; i < numPoints; i++) {
            const ix = i * 4;
            if (unweight) {
                const dw = 1.0 / this.coffs[ix + 3];
                result[j++] = this.coffs[ix] * dw;
                result[j++] = this.coffs[ix + 1] * dw;
                result[j++] = this.coffs[ix + 2] * dw;
            }
            else {
                result[j++] = this.coffs[ix];
                result[j++] = this.coffs[ix + 1];
                result[j++] = this.coffs[ix + 2];
            }
        }
        return result;
    }
    /** unpack from xyzw xyzw ... to packed weights
     */
    copyWeightsToFloat64Array() {
        const numPoints = Math.floor(this.coffs.length / 4);
        const result = new Float64Array(numPoints);
        let i = 0;
        let j = 0;
        for (; i < numPoints; i++) {
            result[j++] = this.coffs[4 * i + 3];
        }
        return result;
    }
    /**
     * return a simple array form of the knots.  optionally replicate the first and last
     * in classic over-clamped manner
     */
    copyKnots(select, includeExtraEndKnot) { return this.knots[select].copyKnots(includeExtraEndKnot); }
    /**
     * Create a weighted bspline surface, with control points and weights each organized as flattened arrays continuing from one U row to the next.
     * * Use `createGrid` if the control points are in a deeper grid array structure.
     * * knotArrayU and knotArrayV are optional -- uniform knots are implied if they are omitted (undefined).
     * * When knots are given, two knot count conditions are recognized:
     *    * If poleArray.length + order == knotArray.length, the first and last are assumed to be the
     *      extraneous knots of classic clamping.
     *    * If poleArray.length + order == knotArray.length + 2, the knots are in modern form that does not have
     *      the classic unused first and last knot.
     * @param controlPointArray Array of [wx,wy,wz] points, ordered along the U direction.
     * @param weightArray array of weights, ordered along the U direction. If undefined, unit weights are installed.
     * @param numPolesU number of poles in each row in the U direction.
     * @param orderU order for the U direction polynomial (`order` is one more than the `degree`.  "cubic" polynomial is order 4.)
     * @param knotArrayU optional knots for the V direction.  See note above about knot counts.
     * @param numPolesV number of poles in each column in the V direction (the number of rows).
     * @param orderV order for the V direction polynomial (`order` is one more than the `degree`.  "cubic" polynomial is order 4.)
     * @param knotArrayV optional knots for the V direction.  See note above about knot counts.
     */
    static create(controlPointArray, weightArray, numPolesU, orderU, knotArrayU, numPolesV, orderV, knotArrayV) {
        const numPoles = numPolesU * numPolesV;
        if (!this.validOrderAndPoleCounts(orderU, numPolesU, orderV, numPolesV, numPoles))
            return undefined;
        const numKnotsU = knotArrayU ? knotArrayU.length : numPolesU + orderU - 2;
        const numKnotsV = knotArrayV ? knotArrayV.length : numPolesV + orderV - 2;
        const skipFirstAndLastU = (numPolesU + orderU === numKnotsU);
        const skipFirstAndLastV = (numPolesV + orderV === numKnotsV);
        const knotsU = knotArrayU ?
            KnotVector.create(knotArrayU, orderU - 1, skipFirstAndLastU) :
            KnotVector.createUniformClamped(numPolesU, orderU - 1, 0.0, 1.0);
        const knotsV = knotArrayV ?
            KnotVector.create(knotArrayV, orderV - 1, skipFirstAndLastV) :
            KnotVector.createUniformClamped(numPolesV, orderV - 1, 0.0, 1.0);
        if (undefined === weightArray)
            weightArray = Array(numPoles).fill(1.0); // unit weights
        const coffs = Point4dArray.packPointsAndWeightsToFloat64Array(controlPointArray, weightArray);
        if (coffs === undefined || coffs.length !== 4 * numPolesU * numPolesV)
            return undefined;
        const surface = new BSplineSurface3dH(numPolesU, numPolesV, knotsU, knotsV, coffs);
        return surface;
    }
    /**
     * Create a bspline surface with given knots.
     * * This `create` variant takes control points in a "grid" array, with the points from
     *   each grid row `[rowIndex]` being an independent array `points[rowIndex][indexAlongRow][x,y,z,w]`
     * * knotArrayU and knotArrayV are optional -- uniform knots are implied if they are omitted (undefined).
     * * When knots are given, two count conditions are recognized in each direction:
     *    * If poleArray.length + order == knotArray.length, the first and last are assumed to be the
     *      extraneous knots of classic clamping.
     *    * If poleArray.length + order == knotArray.length + 2, the knots are in modern form that does not have
     *      the classic unused first and last knot.
     * @param xyzwGrid Array of points, ordered along the U direction.
     * @param weightStyle how the points are weighted
     * @param orderU order for the U direction polynomial (`order` is one more than the `degree`.  "cubic" polynomial is order 4.)
     * @param knotArrayU knots for the V direction.  See note above about knot counts.
     * @param orderV order for the V direction polynomial (`order` is one more than the `degree`.  "cubic" polynomial is order 4.)
     * @param knotArrayV knots for the V direction.  See note above about knot counts.
     */
    static createGrid(xyzwGrid, weightStyle, orderU, knotArrayU, orderV, knotArrayV) {
        const numPolesV = xyzwGrid.length;
        const numPolesU = xyzwGrid[0].length;
        const numPoles = numPolesU * numPolesV;
        if (4 !== xyzwGrid[0][0].length)
            return undefined;
        if (!this.validOrderAndPoleCounts(orderU, numPolesU, orderV, numPolesV, numPoles))
            return undefined;
        // validate knot counts
        const numKnotsU = knotArrayU ? knotArrayU.length : numPolesU + orderU - 2;
        const numKnotsV = knotArrayV ? knotArrayV.length : numPolesV + orderV - 2;
        const skipFirstAndLastU = (numPolesU + orderU === numKnotsU); // classic over-clamped input knots
        if (!skipFirstAndLastU && numPolesU + orderU !== numKnotsU + 2) // modern knots
            return undefined;
        const skipFirstAndLastV = (numPolesV + orderV === numKnotsV); // classic
        if (!skipFirstAndLastV && numPolesV + orderV !== numKnotsV + 2) // modern
            return undefined;
        const knotsU = knotArrayU ?
            KnotVector.create(knotArrayU, orderU - 1, skipFirstAndLastU) :
            KnotVector.createUniformClamped(numPolesU, orderU - 1, 0.0, 1.0);
        const knotsV = knotArrayV ?
            KnotVector.create(knotArrayV, orderV - 1, skipFirstAndLastV) :
            KnotVector.createUniformClamped(numPolesV, orderV - 1, 0.0, 1.0);
        const coffs = new Float64Array(4 * numPoles);
        let i = 0;
        switch (weightStyle) {
            case WeightStyle.WeightsSeparateFromCoordinates: {
                for (const row of xyzwGrid) {
                    for (const point of row) {
                        const w = point[3];
                        coffs[i++] = point[0] * w;
                        coffs[i++] = point[1] * w;
                        coffs[i++] = point[2] * w;
                        coffs[i++] = point[3];
                    }
                }
                break;
            }
            case WeightStyle.WeightsAlreadyAppliedToCoordinates: {
                for (const row of xyzwGrid) {
                    for (const point of row) {
                        coffs[i++] = point[0];
                        coffs[i++] = point[1];
                        coffs[i++] = point[2];
                        coffs[i++] = point[3];
                    }
                }
                break;
            }
            case WeightStyle.UnWeighted: {
                for (const row of xyzwGrid) {
                    for (const point of row) {
                        coffs[i++] = point[0];
                        coffs[i++] = point[1];
                        coffs[i++] = point[2];
                        coffs[i++] = 1.0;
                    }
                }
                break;
            }
            default:
                return undefined; // unrecognized WeightStyle
        }
        return new BSplineSurface3dH(numPolesU, numPolesV, knotsU, knotsV, coffs);
    }
    /** Return a deep clone */
    clone() {
        const knotVector1U = this.knots[0].clone();
        const knotVector1V = this.knots[1].clone();
        const surface1 = new BSplineSurface3dH(this.numPolesUV(0), this.numPolesUV(1), knotVector1U, knotVector1V, this.coffs.slice());
        surface1.coffs = this.coffs.slice();
        return surface1;
    }
    /** Return a transformed clone */
    cloneTransformed(transform) {
        const surface1 = this.clone();
        surface1.tryTransformInPlace(transform);
        return surface1;
    }
    /**
      * Return control points json arrays.
      * * Each row of points is an an array.
      * * Within the array for each row, each point is an array [wx,wy,wz,w].
      */
    getPointGridJSON() {
        const result = {
            points: Point3dArray.unpackNumbersToNestedArraysIJK(this.coffs, 4, this.numPolesUV(0)),
            numCartesianDimensions: 3,
            weightStyle: WeightStyle.WeightsAlreadyAppliedToCoordinates,
        };
        return result;
    }
    /** Evaluate at a position given by a knot value. If deweight fails, returns 000. */
    knotToPoint4d(u, v, result) {
        this.evaluateBuffersAtKnot(u, v);
        result = Point4d.createFromPacked(this._poleBuffer, 0, result);
        return result ? result : Point4d.createZero();
    }
    /** Evaluate at a position given by a knot value.  */
    knotToPointAndDerivatives(u, v, result) {
        this.evaluateBuffersAtKnot(u, v, 1);
        return Plane3dByOriginAndVectors.createOriginAndVectorsWeightedArrays(this._poleBuffer, this._poleBuffer1UV[0], this._poleBuffer1UV[1], result);
    }
    /** Evaluate the Point4d (leaving weights in the point) at given fractional coordinates. If deweight fails, returns 000. */
    fractionToPoint4d(fractionU, fractionV, result) {
        result = this.knotToPoint4d(this.knots[0].fractionToKnot(fractionU), this.knots[1].fractionToKnot(fractionV), result);
        return result ? result : Point4d.createZero();
    }
    /**
     * Evaluate the surface and return the Cartesian point (weight = 1).
     * * If the surface XYZW point has weight 0, returns 000.
     * @param fractionU u direction fraction
     * @param fractionV v direction fraction
     * @param result optional result
     */
    fractionToPoint(fractionU, fractionV, result) {
        const point4d = this.knotToPoint4d(this.knots[0].fractionToKnot(fractionU), this.knots[1].fractionToKnot(fractionV));
        return point4d.realPointDefault000(result);
    }
    /**
   * * evaluate the surface and return the cartesian (weight = 1) point.
   * * if the surface XYZW point has weight0, returns point3d at 000.
   * @param knotU u direction knot
   * @param knotV v direction knot
   * @param result optional result
   */
    knotToPoint(knotU, knotV, result) {
        const point4d = this.knotToPoint4d(knotU, knotV);
        return point4d.realPointDefault000(result);
    }
    /**
     * evaluate the surface at u and v fractions.
     * @returns plane with origin at the surface point, direction vectors are derivatives in the u and v directions.
     * @param fractionU u coordinate, as a fraction of the knot range.
     * @param fractionV v coordinate, as a fraction of the knot range.
     * @param result optional pre-allocated object for return values.
     * @returns Returns point and derivative directions.
     */
    fractionToPointAndDerivatives(fractionU, fractionV, result) {
        const knotU = this.knots[0].fractionToKnot(fractionU);
        const knotV = this.knots[1].fractionToKnot(fractionV);
        return this.knotToPointAndDerivatives(knotU, knotV, result);
    }
    /** Implementation of the UVSurface interface; allows `PolyfaceBuilder.addUVGridBody` to facet this B-spline surface. */
    uvFractionToPoint(u, v) {
        return this.fractionToPoint(u, v);
    }
    /** Implementation of the UVSurface interface; allows `PolyfaceBuilder.addUVGridBody` to facet this B-spline surface. */
    uvFractionToPointAndTangents(u, v, result) {
        return this.fractionToPointAndDerivatives(u, v, result);
    }
    /** test for identical counts and near-equal coordinates */
    isAlmostEqual(other) {
        if (other instanceof BSplineSurface3dH) {
            return this.knots[0].isAlmostEqual(other.knots[0])
                && this.knots[1].isAlmostEqual(other.knots[1])
                && Point4dArray.isAlmostEqual(this.coffs, other.coffs);
        }
        return false;
    }
    /** Test if all poles are in a plane */
    isInPlane(plane) {
        return Point4dArray.isCloseToPlane(this.coffs, plane);
    }
    /** Second step of double dispatch:  call `handler.handleBSplineSurface3dH(this)` */
    dispatchToGeometryHandler(handler) {
        return handler.handleBSplineSurface3dH(this);
    }
    /**
     * extend a range to include the (optionally transformed) points of this surface
     * @param rangeToExtend range that is updated to include this surface range
     * @param transform transform to apply to the surface points
     */
    extendRange(rangeToExtend, transform) {
        this.extendRangeXYZH(rangeToExtend, transform);
    }
}
//# sourceMappingURL=BSplineSurface.js.map