"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.IModelJson = void 0;
/** @packageDocumentation
 * @module Serialization
 */
const core_bentley_1 = require("@itwin/core-bentley");
const AkimaCurve3d_1 = require("../bspline/AkimaCurve3d");
const BSplineCurve_1 = require("../bspline/BSplineCurve");
const BSplineCurve3dH_1 = require("../bspline/BSplineCurve3dH");
const BSplineCurveOps_1 = require("../bspline/BSplineCurveOps");
const BSplineSurface_1 = require("../bspline/BSplineSurface");
const InterpolationCurve3d_1 = require("../bspline/InterpolationCurve3d");
const KnotVector_1 = require("../bspline/KnotVector");
const Arc3d_1 = require("../curve/Arc3d");
const CoordinateXYZ_1 = require("../curve/CoordinateXYZ");
const CurveCollection_1 = require("../curve/CurveCollection");
const GeometryQuery_1 = require("../curve/GeometryQuery");
const LineSegment3d_1 = require("../curve/LineSegment3d");
const LineString3d_1 = require("../curve/LineString3d");
const Loop_1 = require("../curve/Loop");
const ParityRegion_1 = require("../curve/ParityRegion");
const Path_1 = require("../curve/Path");
const PointString3d_1 = require("../curve/PointString3d");
const DirectSpiral3d_1 = require("../curve/spiral/DirectSpiral3d");
const IntegratedSpiral3d_1 = require("../curve/spiral/IntegratedSpiral3d");
const UnionRegion_1 = require("../curve/UnionRegion");
const Geometry_1 = require("../Geometry");
const Angle_1 = require("../geometry3d/Angle");
const AngleSweep_1 = require("../geometry3d/AngleSweep");
const GeometryHandler_1 = require("../geometry3d/GeometryHandler");
const Matrix3d_1 = require("../geometry3d/Matrix3d");
const Point2dVector2d_1 = require("../geometry3d/Point2dVector2d");
const Point3dVector3d_1 = require("../geometry3d/Point3dVector3d");
const Ray3d_1 = require("../geometry3d/Ray3d");
const Segment1d_1 = require("../geometry3d/Segment1d");
const Transform_1 = require("../geometry3d/Transform");
const YawPitchRollAngles_1 = require("../geometry3d/YawPitchRollAngles");
const AuxData_1 = require("../polyface/AuxData");
const Polyface_1 = require("../polyface/Polyface");
const TaggedNumericData_1 = require("../polyface/TaggedNumericData");
const Box_1 = require("../solid/Box");
const Cone_1 = require("../solid/Cone");
const LinearSweep_1 = require("../solid/LinearSweep");
const RotationalSweep_1 = require("../solid/RotationalSweep");
const RuledSweep_1 = require("../solid/RuledSweep");
const Sphere_1 = require("../solid/Sphere");
const TorusPipe_1 = require("../solid/TorusPipe");
const SerializationHelpers_1 = require("./SerializationHelpers");
/**
 * `IModelJson` namespace has classes for serializing and deserialization json objects
 * @public
 */
var IModelJson;
(function (IModelJson) {
    /** parser services for "iModelJson" schema
     * * 1: create a reader with `new ImodelJsonReader`
     * * 2: parse json fragment to strongly typed geometry: `const g = reader.parse (fragment)`
     * @public
     */
    class Reader {
        constructor() {
        }
        static parseVector3dProperty(json, propertyName, defaultValue) {
            if (json.hasOwnProperty(propertyName)) {
                const value = json[propertyName];
                if (Geometry_1.Geometry.isNumberArray(value, 3))
                    return Point3dVector3d_1.Vector3d.create(value[0], value[1], value[2]);
                if (Geometry_1.Geometry.isNumberArray(value, 2))
                    return Point3dVector3d_1.Vector3d.create(value[0], value[1]);
                if (Point3dVector3d_1.XYZ.isXAndY(value))
                    return Point3dVector3d_1.Vector3d.fromJSON(value);
            }
            return defaultValue;
        }
        static parsePoint3dProperty(json, propertyName, defaultValue) {
            if (json.hasOwnProperty(propertyName)) {
                const value = json[propertyName];
                if (Geometry_1.Geometry.isNumberArray(value, 3))
                    return Point3dVector3d_1.Point3d.create(value[0], value[1], value[2]);
                if (Geometry_1.Geometry.isNumberArray(value, 2))
                    return Point3dVector3d_1.Point3d.create(value[0], value[1]);
                if (Point3dVector3d_1.XYZ.isXAndY(value))
                    return Point3dVector3d_1.Point3d.fromJSON(value);
            }
            return defaultValue;
        }
        static parseSegment1dProperty(json, propertyName, defaultValue) {
            if (json.hasOwnProperty(propertyName)) {
                const value = json[propertyName];
                if (Geometry_1.Geometry.isNumberArray(value, 2))
                    return Segment1d_1.Segment1d.create(value[0], value[1]);
            }
            return defaultValue;
        }
        static parseNumberProperty(json, propertyName, defaultValue) {
            if (json.hasOwnProperty(propertyName)) {
                const value = json[propertyName];
                if (Number.isFinite(value))
                    return value;
            }
            return defaultValue;
        }
        /**
         * @internal
         */
        static parseTaggedNumericProps(json) {
            const tagA = this.parseNumberProperty(json, "tagA");
            const tagB = this.parseNumberProperty(json, "tagB", 0);
            if (tagA !== undefined) {
                const result = new TaggedNumericData_1.TaggedNumericData(tagA, tagB);
                if (json.hasOwnProperty("intData"))
                    result.intData = this.parseNumberArrayProperty(json, "intData", 0, undefined);
                if (json.hasOwnProperty("doubleData"))
                    result.doubleData = this.parseNumberArrayProperty(json, "doubleData", 0, undefined);
                return result;
            }
            return undefined;
        }
        static parseNumberArrayProperty(json, propertyName, minValues, maxValues, defaultValue) {
            if (json.hasOwnProperty(propertyName)) {
                const value = json[propertyName];
                if (Array.isArray(value)
                    && value.length >= minValues && (undefined === maxValues || value.length <= maxValues)) {
                    const result = [];
                    for (const a of value) {
                        result.push(a);
                    }
                    return result;
                }
            }
            return defaultValue;
        }
        static parseAngleProperty(json, propertyName, defaultValue) {
            if (json.hasOwnProperty(propertyName)) {
                const value = json[propertyName];
                return Angle_1.Angle.fromJSON(value);
            }
            return defaultValue;
        }
        /**
         * @param defaultFunction function to call if needed to produce a default value
         */
        static parseAngleSweepProps(json, propertyName, defaultFunction) {
            if (json.hasOwnProperty(propertyName)) {
                const value = json[propertyName];
                return AngleSweep_1.AngleSweep.fromJSON(value);
            }
            if (defaultFunction === undefined)
                return undefined;
            return defaultFunction();
        }
        static parseBooleanProperty(json, propertyName, defaultValue) {
            if (json.hasOwnProperty(propertyName)) {
                const value = json[propertyName];
                if (value === true)
                    return true;
                if (value === false)
                    return false;
            }
            return defaultValue;
        }
        static loadContourArray(json, propertyName) {
            if (json.hasOwnProperty(propertyName)) {
                const value = json[propertyName];
                if (Array.isArray(value)) {
                    const result = [];
                    for (const contourData of value) {
                        const contour = Reader.parse(contourData);
                        if (contour instanceof CurveCollection_1.CurveCollection) {
                            result.push(contour);
                        }
                    }
                    if (result.length > 0)
                        return result;
                }
            }
            return undefined;
        }
        static parseYawPitchRollAnglesToMatrix3d(json) {
            const ypr = YawPitchRollAngles_1.YawPitchRollAngles.fromJSON(json);
            return ypr.toMatrix3d();
        }
        static parseStringProperty(json, propertyName, defaultValue) {
            if (json.hasOwnProperty(propertyName)) {
                const value = json[propertyName];
                // if (value instanceof string)
                return value;
            }
            return defaultValue;
        }
        static parseAxesFromVectors(json, axisOrder, createDefaultIdentity) {
            if (Array.isArray(json)) {
                let matrix;
                if (json.length === 2) { // square and normalize
                    const xVector = Point3dVector3d_1.Vector3d.fromJSON(json[0]);
                    const yVector = Point3dVector3d_1.Vector3d.fromJSON(json[1]);
                    matrix = Matrix3d_1.Matrix3d.createRigidFromColumns(xVector, yVector, axisOrder);
                }
                else if (json.length === 3) { // preserve axes
                    const xVector = Point3dVector3d_1.Vector3d.fromJSON(json[0]);
                    const yVector = Point3dVector3d_1.Vector3d.fromJSON(json[1]);
                    const zVector = Point3dVector3d_1.Vector3d.fromJSON(json[2]);
                    matrix = Matrix3d_1.Matrix3d.createColumnsInAxisOrder(axisOrder, xVector, yVector, zVector);
                }
                if (matrix)
                    return matrix;
            }
            if (createDefaultIdentity)
                return Matrix3d_1.Matrix3d.createIdentity();
            return undefined;
        }
        /**
         * Look for orientation data and convert to Matrix3d.
         * * Search order and interpretation:
         * * * xyzVectors - general matrix, axes preserved
         * * * yawPitchRollAngles - right-handed rotation via axial rotations
         * * * xyVectors - right-handed rotation, xy-plane specified, axes squared and normalized
         * * * zxVectors - right-handed rotation, zx-plane specified, axes squared and normalized
         * @param json [in] json source data
         * @param createDefaultIdentity [in] If true and no orientation is present, return an identity matrix.  If false and no orientation is present, return undefined.
         */
        static parseOrientation(json, createDefaultIdentity) {
            if (json.xyzVectors)
                return Reader.parseAxesFromVectors(json.xyzVectors, Geometry_1.AxisOrder.XYZ, createDefaultIdentity);
            if (json.yawPitchRollAngles)
                return Reader.parseYawPitchRollAnglesToMatrix3d(json.yawPitchRollAngles);
            if (json.xyVectors)
                return Reader.parseAxesFromVectors(json.xyVectors, Geometry_1.AxisOrder.XYZ, createDefaultIdentity);
            if (json.zxVectors)
                return Reader.parseAxesFromVectors(json.zxVectors, Geometry_1.AxisOrder.ZXY, createDefaultIdentity);
            if (createDefaultIdentity)
                return Matrix3d_1.Matrix3d.createIdentity();
            return undefined;
        }
        static parseArcByVectorProps(data) {
            if (data
                && data.center !== undefined
                && data.vectorX !== undefined
                && data.vectorY !== undefined
                && data.sweepStartEnd !== undefined) {
                return Arc3d_1.Arc3d.create(Point3dVector3d_1.Point3d.fromJSON(data.center), Point3dVector3d_1.Vector3d.fromJSON(data.vectorX), Point3dVector3d_1.Vector3d.fromJSON(data.vectorY), AngleSweep_1.AngleSweep.fromJSON(data.sweepStartEnd));
            }
            return undefined;
        }
        // remark: Returns LineString3d as last default when give points are colinear.
        static parseArcBy3Points(data) {
            if (Array.isArray(data) && data.length > 2) {
                const pointA = Point3dVector3d_1.Point3d.fromJSON(data[0]);
                const pointB = Point3dVector3d_1.Point3d.fromJSON(data[1]);
                const pointC = Point3dVector3d_1.Point3d.fromJSON(data[2]);
                return Arc3d_1.Arc3d.createCircularStartMiddleEnd(pointA, pointB, pointC);
            }
            return undefined;
        }
        static parseArcObject(data) {
            let arc = Reader.parseArcByVectorProps(data);
            if (arc)
                return arc;
            arc = Reader.parseArcBy3Points(data);
            return arc; // possibly undefined.
        }
        /** Parse point content (right side) `[1,2,3]` to a CoordinateXYZ object. */
        static parseCoordinate(data) {
            const point = Point3dVector3d_1.Point3d.fromJSON(data);
            if (point)
                return CoordinateXYZ_1.CoordinateXYZ.create(point);
            return undefined;
        }
        /** Parse `transitionSpiral` content (right side) to TransitionSpiral3d. */
        static parseTransitionSpiral(data) {
            const axes = Reader.parseOrientation(data, true);
            const origin = Reader.parsePoint3dProperty(data, "origin");
            // the create method will juggle any 4 out of these 5 inputs to define the other ..
            const startBearing = Reader.parseAngleProperty(data, "startBearing");
            const endBearing = Reader.parseAngleProperty(data, "endBearing");
            const startRadius = Reader.parseNumberProperty(data, "startRadius");
            const endRadius = Reader.parseNumberProperty(data, "endRadius");
            let length = Reader.parseNumberProperty(data, "length", undefined);
            if (length === undefined)
                length = Reader.parseNumberProperty(data, "curveLength", undefined);
            let interval = Reader.parseSegment1dProperty(data, "activeFractionInterval", undefined);
            if (!interval)
                interval = Reader.parseSegment1dProperty(data, "fractionInterval", undefined);
            if (!interval)
                interval = Reader.parseSegment1dProperty(data, "activeInterval", undefined);
            const spiralType = Reader.parseStringProperty(data, "type", "clothoid");
            // REMARK:  Our job is to parse and pass data along -- inscrutable validation happens in the implementation classes . . .
            if (origin) {
                let candidate;
                candidate = IntegratedSpiral3d_1.IntegratedSpiral3d.createFrom4OutOf5(spiralType, startRadius, endRadius, startBearing, endBearing, length, interval, Transform_1.Transform.createOriginAndMatrix(origin, axes));
                if (candidate)
                    return candidate;
                candidate = DirectSpiral3d_1.DirectSpiral3d.createFromLengthAndRadius(spiralType, startRadius, endRadius, startBearing, endBearing, length, interval, Transform_1.Transform.createOriginAndMatrix(origin, axes));
                if (candidate)
                    return candidate;
            }
            return undefined;
        }
        /** Parse `bcurve` content to BSplineCurve3d or BSplineCurve3dH object. */
        static parseBcurve(data) {
            let newCurve;
            let dim;
            if (data !== undefined
                && data.hasOwnProperty("knots") && Array.isArray(data.knots)
                && data.hasOwnProperty("order") && Number.isFinite(data.order)
                && data.hasOwnProperty("points") && Array.isArray(data.points) && Array.isArray(data.points[0])
                && (dim = data.points[0].length) >= 3 && dim <= 4) {
                const myData = SerializationHelpers_1.SerializationHelpers.createBSplineCurveData(data.points, dim, data.knots, data.points.length, data.order);
                if (data.hasOwnProperty("closed") && true === data.closed)
                    myData.params.closed = true;
                if (SerializationHelpers_1.SerializationHelpers.Import.prepareBSplineCurveData(myData)) {
                    if (dim === 3)
                        newCurve = BSplineCurve_1.BSplineCurve3d.create(myData.poles, myData.params.knots, myData.params.order);
                    else
                        newCurve = BSplineCurve3dH_1.BSplineCurve3dH.create(myData.poles, myData.params.knots, myData.params.order);
                    if (undefined !== newCurve) {
                        if (undefined !== myData.params.wrapMode)
                            newCurve.setWrappable(myData.params.wrapMode);
                    }
                }
            }
            return newCurve;
        }
        /** Parse `interpolationCurve` content to an InterpolationCurve3d object. */
        static parseInterpolationCurve(data) {
            if (data === undefined)
                return undefined;
            return InterpolationCurve3d_1.InterpolationCurve3d.create(data);
        }
        /** Parse `akimaCurve` content to an Akima curve object. */
        static parseAkimaCurve3d(data) {
            if (data === undefined)
                return undefined;
            return AkimaCurve3d_1.AkimaCurve3d.create(data);
        }
        /** Parse array of json objects to array of instances. */
        static parseArray(data) {
            if (Array.isArray(data)) {
                const myArray = [];
                let c;
                for (c of data) {
                    const g = Reader.parse(c);
                    if (g !== undefined)
                        myArray.push(g);
                }
                return myArray;
            }
            return undefined;
        }
        /** parse polyface aux data content to PolyfaceAuxData instance */
        static parsePolyfaceAuxData(data = undefined, numPerFace = 0) {
            if (!Array.isArray(data.channels) || !Array.isArray(data.indices))
                return undefined;
            const outChannels = [];
            for (const inChannel of data.channels) {
                if (Array.isArray(inChannel.data) && inChannel.hasOwnProperty("dataType")) {
                    const outChannelData = [];
                    for (const inChannelData of inChannel.data) {
                        if (inChannelData.hasOwnProperty("input") && Array.isArray(inChannelData.values))
                            outChannelData.push(new AuxData_1.AuxChannelData(inChannelData.input, inChannelData.values));
                    }
                    outChannels.push(new AuxData_1.AuxChannel(outChannelData, inChannel.dataType, inChannel.name, inChannel.inputName));
                }
            }
            const auxData = new AuxData_1.PolyfaceAuxData(outChannels, []);
            SerializationHelpers_1.SerializationHelpers.announceZeroBasedIndicesFromSignedOneBasedIndices(data.indices, numPerFace, (x) => { auxData.indices.push(x); });
            return auxData;
        }
        /** parse indexed mesh content to an IndexedPolyface instance */
        static parseIndexedMesh(data) {
            // {Coord:[[x,y,z],. . . ],   -- simple xyz for each point
            // CoordIndex[1,2,3,0]    -- zero-terminated, one based !!!
            if (data.hasOwnProperty("point") && Array.isArray(data.point)
                && data.hasOwnProperty("pointIndex") && Array.isArray(data.pointIndex)) {
                const polyface = Polyface_1.IndexedPolyface.create();
                const numPerFace = data.hasOwnProperty("numPerFace") ? data.numPerFace : 0;
                // default value is true!!
                polyface.twoSided = this.parseBooleanProperty(data, "twoSided", true) ?? true;
                if (data.hasOwnProperty("expectedClosure")) {
                    const q = data.expectedClosure;
                    if (Number.isFinite(q)) {
                        polyface.expectedClosure = q;
                    }
                }
                if (data.hasOwnProperty("normal") && Array.isArray(data.normal) && data.hasOwnProperty("normalIndex")) {
                    // For normals, addNormal() is overeager to detect the (common) case of duplicate normals in sequence.
                    // Use addNormalXYZ which always creates a new one. Likewise for params.
                    for (const uvw of data.normal) {
                        if (Geometry_1.Geometry.isNumberArray(uvw, 3))
                            polyface.addNormalXYZ(uvw[0], uvw[1], uvw[2]);
                    }
                    SerializationHelpers_1.SerializationHelpers.announceZeroBasedIndicesFromSignedOneBasedIndices(data.normalIndex, numPerFace, (x) => { polyface.addNormalIndex(x); });
                }
                if (data.hasOwnProperty("param") && Array.isArray(data.param) && data.hasOwnProperty("paramIndex")) {
                    for (const uv of data.param) {
                        if (Geometry_1.Geometry.isNumberArray(uv, 2))
                            polyface.addParamUV(uv[0], uv[1]);
                    }
                    SerializationHelpers_1.SerializationHelpers.announceZeroBasedIndicesFromSignedOneBasedIndices(data.paramIndex, numPerFace, (x) => { polyface.addParamIndex(x); });
                }
                if (data.hasOwnProperty("color") && Array.isArray(data.color) && data.hasOwnProperty("colorIndex")) {
                    for (const c of data.color)
                        polyface.addColor(c);
                    SerializationHelpers_1.SerializationHelpers.announceZeroBasedIndicesFromSignedOneBasedIndices(data.colorIndex, numPerFace, (x) => { polyface.addColorIndex(x); });
                }
                for (const p of data.point)
                    polyface.addPointXYZ(p[0], p[1], p[2]);
                SerializationHelpers_1.SerializationHelpers.announceZeroBasedIndicesFromSignedOneBasedIndices(data.pointIndex, numPerFace, (i, v) => { polyface.addPointIndex(i, v); }, () => { polyface.terminateFacet(false); });
                if (data.hasOwnProperty("edgeMateIndex") && Array.isArray(data.edgeMateIndex)) {
                    const edgeMateIndex = [];
                    if (SerializationHelpers_1.SerializationHelpers.announceCompressedZeroBasedReflexiveIndices(data.edgeMateIndex, numPerFace, SerializationHelpers_1.SerializationHelpers.EdgeMateIndex.BlockSeparator, SerializationHelpers_1.SerializationHelpers.EdgeMateIndex.NoEdgeMate, (i) => edgeMateIndex.push(i))) {
                        polyface.data.edgeMateIndex = edgeMateIndex;
                    }
                }
                if (!polyface.validateAllIndices())
                    return undefined;
                if (data.hasOwnProperty("auxData"))
                    polyface.data.auxData = Reader.parsePolyfaceAuxData(data.auxData, numPerFace);
                if (data.hasOwnProperty("tags"))
                    polyface.data.taggedNumericData = Reader.parseTaggedNumericProps(data.tags);
                // NOTE: faceData is ignored
                return polyface;
            }
            return undefined;
        }
        /** parse contents of a curve collection to a CurveCollection instance */
        static parseCurveCollectionMembers(result, data) {
            if (data && Array.isArray(data)) {
                for (const c of data) {
                    const g = Reader.parse(c);
                    if (g instanceof GeometryQuery_1.GeometryQuery && ("curveCollection" === g.geometryCategory || "curvePrimitive" === g.geometryCategory))
                        result.tryAddChild(g);
                }
                return result;
            }
            return undefined;
        }
        /** Parse content of `bsurf` to BSplineSurface3d or BSplineSurface3dH */
        static parseBsurf(data) {
            let newSurface;
            let dim;
            if (data !== undefined
                && data.hasOwnProperty("uKnots") && Array.isArray(data.uKnots)
                && data.hasOwnProperty("vKnots") && Array.isArray(data.vKnots)
                && data.hasOwnProperty("orderU") && Number.isFinite(data.orderU)
                && data.hasOwnProperty("orderV") && Number.isFinite(data.orderV)
                && data.hasOwnProperty("points") && Array.isArray(data.points) && Array.isArray(data.points[0]) && Array.isArray(data.points[0][0])
                && (dim = data.points[0][0].length) >= 3 && dim <= 4) {
                const myData = SerializationHelpers_1.SerializationHelpers.createBSplineSurfaceData(data.points, dim, data.uKnots, data.points[0].length, data.orderU, data.vKnots, data.points.length, data.orderV);
                if (data.hasOwnProperty("closedU") && true === data.closedU)
                    myData.uParams.closed = true;
                if (data.hasOwnProperty("closedV") && true === data.closedV)
                    myData.vParams.closed = true;
                if (SerializationHelpers_1.SerializationHelpers.Import.prepareBSplineSurfaceData(myData, { jsonPoles: true })) {
                    if (dim === 3)
                        newSurface = BSplineSurface_1.BSplineSurface3d.createGrid(myData.poles, myData.uParams.order, myData.uParams.knots, myData.vParams.order, myData.vParams.knots);
                    else
                        newSurface = BSplineSurface_1.BSplineSurface3dH.createGrid(myData.poles, BSplineSurface_1.WeightStyle.WeightsAlreadyAppliedToCoordinates, myData.uParams.order, myData.uParams.knots, myData.vParams.order, myData.vParams.knots);
                    if (undefined !== newSurface) {
                        if (undefined !== myData.uParams.wrapMode)
                            newSurface.setWrappable(BSplineSurface_1.UVSelect.uDirection, myData.uParams.wrapMode);
                        if (undefined !== myData.vParams.wrapMode)
                            newSurface.setWrappable(BSplineSurface_1.UVSelect.vDirection, myData.vParams.wrapMode);
                    }
                }
            }
            return newSurface;
        }
        /** Parse `cone` contents to `Cone` instance  */
        static parseConeProps(json) {
            const axes = Reader.parseOrientation(json, false);
            const start = Reader.parsePoint3dProperty(json, "start");
            const end = Reader.parsePoint3dProperty(json, "end");
            const radius = Reader.parseNumberProperty(json, "radius");
            const startRadius = Reader.parseNumberProperty(json, "startRadius", radius);
            const endRadius = Reader.parseNumberProperty(json, "endRadius", startRadius);
            const capped = Reader.parseBooleanProperty(json, "capped");
            if (start && end && startRadius !== undefined && endRadius !== undefined) {
                if (axes === undefined) {
                    const axisVector = Point3dVector3d_1.Vector3d.createStartEnd(start, end);
                    const frame = Matrix3d_1.Matrix3d.createRigidHeadsUp(axisVector, Geometry_1.AxisOrder.ZXY);
                    const vectorX = frame.columnX();
                    const vectorY = frame.columnY();
                    return Cone_1.Cone.createBaseAndTarget(start, end, vectorX, vectorY, startRadius, endRadius, capped);
                }
                return Cone_1.Cone.createBaseAndTarget(start, end, axes.columnX(), axes.columnY(), startRadius, endRadius, capped);
            }
            return undefined;
        }
        /** Parse `cylinder` content to `Cone` instance */
        static parseCylinderProps(json) {
            const start = Reader.parsePoint3dProperty(json, "start");
            const end = Reader.parsePoint3dProperty(json, "end");
            const radius = Reader.parseNumberProperty(json, "radius");
            const capped = Reader.parseBooleanProperty(json, "capped", false);
            if (start
                && end
                && radius !== undefined) {
                return Cone_1.Cone.createAxisPoints(start, end, radius, radius, capped);
            }
            return undefined;
        }
        /** Parse line segment (array of 2 points) properties to `LineSegment3d` instance */
        static parseLineSegmentProps(value) {
            if (Array.isArray(value) && value.length > 1)
                return LineSegment3d_1.LineSegment3d.create(Point3dVector3d_1.Point3d.fromJSON(value[0]), Point3dVector3d_1.Point3d.fromJSON(value[1]));
            else
                return undefined;
        }
        /** Parse linear sweep content to `LinearSweep` instance. */
        static parseLinearSweep(json) {
            const contour = Reader.parse(json.contour);
            const capped = Reader.parseBooleanProperty(json, "capped");
            const extrusionVector = Reader.parseVector3dProperty(json, "vector");
            if (contour instanceof GeometryQuery_1.GeometryQuery
                && "curveCollection" === contour.geometryCategory
                && capped !== undefined
                && extrusionVector) {
                return LinearSweep_1.LinearSweep.create(contour, extrusionVector, capped);
            }
            return undefined;
        }
        /** Parse rotational sweep contents to `RotationalSweep` instance */
        static parseRotationalSweep(json) {
            if (json === undefined)
                return undefined;
            const contour = Reader.parse(json.contour);
            const capped = Reader.parseBooleanProperty(json, "capped");
            const axisVector = Reader.parseVector3dProperty(json, "axis");
            const center = Reader.parsePoint3dProperty(json, "center");
            const sweepDegrees = Reader.parseNumberProperty(json, "sweepAngle");
            if (contour instanceof GeometryQuery_1.GeometryQuery
                && "curveCollection" === contour.geometryCategory
                && sweepDegrees !== undefined
                && capped !== undefined
                && axisVector
                && center) {
                return RotationalSweep_1.RotationalSweep.create(contour, Ray3d_1.Ray3d.createCapture(center, axisVector), Angle_1.Angle.createDegrees(sweepDegrees), capped);
            }
            return undefined;
        }
        /** Parse box contents to `Box` instance */
        static parseBox(json) {
            const capped = Reader.parseBooleanProperty(json, "capped", false);
            // A mismatch between native and TypeScript code: TypeScript used "origin" where native used "baseOrigin".
            // Native now outputs and accepts either, preferring "origin"; TypeScript continues to expose only "origin".
            const origin = Reader.parsePoint3dProperty(json, "origin") ?? Reader.parsePoint3dProperty(json, "baseOrigin");
            const baseX = Reader.parseNumberProperty(json, "baseX");
            const baseY = Reader.parseNumberProperty(json, "baseY", baseX);
            let topOrigin = Reader.parsePoint3dProperty(json, "topOrigin");
            const topX = Reader.parseNumberProperty(json, "topX", baseX);
            const topY = Reader.parseNumberProperty(json, "topY", baseY);
            const height = Reader.parseNumberProperty(json, "height", baseX);
            const axes = Reader.parseOrientation(json, true);
            if (origin && !topOrigin && height)
                topOrigin = Matrix3d_1.Matrix3d.xyzPlusMatrixTimesXYZ(origin, axes, Point3dVector3d_1.Vector3d.create(0, 0, height));
            if (capped !== undefined
                && baseX !== undefined
                && baseY !== undefined
                && topY !== undefined
                && topX !== undefined
                && axes
                && origin
                && topOrigin) {
                return Box_1.Box.createDgnBoxWithAxes(origin, axes, topOrigin, baseX, baseY, topX, topY, capped);
            }
            return undefined;
        }
        /** Parse `SphereProps` to `Sphere` instance. */
        static parseSphere(json) {
            const center = Reader.parsePoint3dProperty(json, "center");
            const radius = Reader.parseNumberProperty(json, "radius");
            const radiusX = Reader.parseNumberProperty(json, "radiusX", radius);
            const radiusY = Reader.parseNumberProperty(json, "radiusY", radiusX);
            const radiusZ = Reader.parseNumberProperty(json, "radiusZ", radiusY);
            const latitudeStartEnd = Reader.parseAngleSweepProps(json, "latitudeStartEnd");
            const axes = Reader.parseOrientation(json, false);
            const capped = Reader.parseBooleanProperty(json, "capped");
            if (center && radiusX && radiusY && radiusZ)
                return Sphere_1.Sphere.createFromAxesAndScales(center, axes, radiusX, radiusY, radiusZ, latitudeStartEnd, capped);
            return undefined;
        }
        /** Parse RuledSweepProps to RuledSweep instance. */
        static parseRuledSweep(json) {
            const capped = Reader.parseBooleanProperty(json, "capped", false);
            const contours = this.loadContourArray(json, "contour");
            if (contours !== undefined
                && capped !== undefined) {
                return RuledSweep_1.RuledSweep.create(contours, capped);
            }
            return undefined;
        }
        /** Parse TorusPipe props to TorusPipe instance. */
        static parseTorusPipe(json) {
            const axes = Reader.parseOrientation(json, true); // force frame to be pure rotation (no scale or mirror)!
            const center = Reader.parsePoint3dProperty(json, "center");
            const radiusA = Reader.parseNumberProperty(json, "majorRadius");
            const radiusB = Reader.parseNumberProperty(json, "minorRadius");
            const sweepAngle = Reader.parseAngleProperty(json, "sweepAngle", undefined);
            const capped = Reader.parseBooleanProperty(json, "capped", false);
            if (center
                && radiusA !== undefined
                && radiusB !== undefined) {
                return TorusPipe_1.TorusPipe.createDgnTorusPipe(center, axes.columnX(), axes.columnY(), radiusA, radiusB, sweepAngle ? sweepAngle : Angle_1.Angle.createDegrees(360), capped);
            }
            return undefined;
        }
        /** Parse an array object to array of Point3d instances. */
        static parsePointArray(json) {
            const points = [];
            if (json && Array.isArray(json)) {
                for (const member of json) {
                    if (Point3dVector3d_1.XYZ.isXAndY(member)) {
                        points.push(Point3dVector3d_1.Point3d.fromJSON(member));
                    }
                    else if (Geometry_1.Geometry.isNumberArray(member, 2)) {
                        points.push(Point3dVector3d_1.Point3d.fromJSON(member));
                    }
                }
            }
            return points;
        }
        /** Deserialize `json` to `GeometryQuery` instances. */
        static parse(json) {
            if (json !== undefined && json) {
                if (json.lineSegment !== undefined) {
                    return Reader.parseLineSegmentProps(json.lineSegment);
                }
                else if (json.lineString !== undefined) {
                    return LineString3d_1.LineString3d.create(Reader.parsePointArray(json.lineString));
                }
                else if (json.arc !== undefined) {
                    return Reader.parseArcObject(json.arc);
                }
                else if (json.hasOwnProperty("point")) {
                    return Reader.parseCoordinate(json.point);
                }
                else if (json.hasOwnProperty("bcurve")) {
                    return Reader.parseBcurve(json.bcurve);
                }
                else if (json.hasOwnProperty("interpolationCurve")) {
                    return Reader.parseInterpolationCurve(json.interpolationCurve);
                }
                else if (json.hasOwnProperty("akimaCurve")) {
                    return Reader.parseAkimaCurve3d(json.akimaCurve);
                }
                else if (json.hasOwnProperty("path")) {
                    return Reader.parseCurveCollectionMembers(new Path_1.Path(), json.path);
                }
                else if (json.hasOwnProperty("loop")) {
                    return Reader.parseCurveCollectionMembers(new Loop_1.Loop(), json.loop);
                }
                else if (json.hasOwnProperty("parityRegion")) {
                    return Reader.parseCurveCollectionMembers(new ParityRegion_1.ParityRegion(), json.parityRegion);
                }
                else if (json.hasOwnProperty("unionRegion")) {
                    return Reader.parseCurveCollectionMembers(new UnionRegion_1.UnionRegion(), json.unionRegion);
                }
                else if (json.hasOwnProperty("bagOfCurves")) {
                    return Reader.parseCurveCollectionMembers(new CurveCollection_1.BagOfCurves(), json.bagOfCurves);
                }
                else if (json.hasOwnProperty("indexedMesh")) {
                    return Reader.parseIndexedMesh(json.indexedMesh);
                }
                else if (json.hasOwnProperty("bsurf")) {
                    return Reader.parseBsurf(json.bsurf);
                }
                else if (json.hasOwnProperty("cone")) {
                    return Reader.parseConeProps(json.cone);
                }
                else if (json.hasOwnProperty("cylinder")) {
                    return Reader.parseCylinderProps(json.cylinder);
                }
                else if (json.hasOwnProperty("sphere")) {
                    return Reader.parseSphere(json.sphere);
                }
                else if (json.hasOwnProperty("linearSweep")) {
                    return Reader.parseLinearSweep(json.linearSweep);
                }
                else if (json.hasOwnProperty("box")) {
                    return Reader.parseBox(json.box);
                }
                else if (json.hasOwnProperty("rotationalSweep")) {
                    return Reader.parseRotationalSweep(json.rotationalSweep);
                }
                else if (json.hasOwnProperty("ruledSweep")) {
                    return Reader.parseRuledSweep(json.ruledSweep);
                }
                else if (json.hasOwnProperty("torusPipe")) {
                    return Reader.parseTorusPipe(json.torusPipe);
                }
                else if (json.hasOwnProperty("pointString")) {
                    return PointString3d_1.PointString3d.create(Reader.parsePointArray(json.pointString));
                }
                else if (json.hasOwnProperty("transitionSpiral")) {
                    return Reader.parseTransitionSpiral(json.transitionSpiral);
                }
                else if (Array.isArray(json))
                    return Reader.parseArray(json);
            }
            return undefined;
        }
    }
    IModelJson.Reader = Reader;
    /**
     * Class to deserialize json objects into GeometryQuery objects
     * @public
     */
    class Writer extends GeometryHandler_1.GeometryHandler {
        handleTaggedNumericData(data) {
            const result = { tagA: data.tagA, tagB: data.tagB };
            if (data.intData !== undefined && data.intData.length > 0)
                result.intData = data.intData.slice();
            if (data.doubleData !== undefined && data.doubleData.length > 0)
                result.doubleData = data.doubleData.slice();
            return result;
        }
        /** Convert strongly typed instance to tagged json */
        handleLineSegment3d(data) {
            return { lineSegment: [data.point0Ref.toJSON(), data.point1Ref.toJSON()] };
        }
        /** Convert strongly typed instance to tagged json */
        handleCoordinateXYZ(data) {
            return { point: data.point.toJSON() };
        }
        /** Convert strongly typed instance to tagged json */
        handleArc3d(data) {
            return {
                arc: {
                    center: data.center.toJSON(),
                    vectorX: data.vector0.toJSON(),
                    vectorY: data.vector90.toJSON(),
                    sweepStartEnd: [data.sweep.startDegrees, data.sweep.endDegrees],
                },
            };
        }
        /**
         * Insert orientation description to a data object.
         * @param matrix matrix with orientation
         * @param omitIfIdentity omit the axis data if the matrix is an identity.
         * @param data AxesProps object to be annotated.
         */
        static insertOrientationFromMatrix(data, matrix, omitIfIdentity) {
            if (omitIfIdentity) {
                if (matrix === undefined)
                    return;
                if (matrix.isIdentity)
                    return;
            }
            if (matrix)
                data.xyVectors = [matrix.columnX().toJSON(), matrix.columnY().toJSON()];
            else
                data.xyVectors = [[1, 0, 0], [0, 1, 0]];
        }
        static isIdentityXY(xVector, yVector) {
            return xVector.isAlmostEqualXYZ(1, 0, 0) && yVector.isAlmostEqualXYZ(0, 1, 0);
        }
        /**
         * Insert orientation description to a data object.
         * @param matrix matrix with orientation
         * @param omitIfIdentity omit the axis data if the matrix is an identity.
         * @param data AxesProps object to be annotated.
         */
        static insertOrientationFromXYVectors(data, vectorX, vectorY, omitIfIdentity) {
            if (omitIfIdentity && Writer.isIdentityXY(vectorX, vectorY))
                return;
            data.xyVectors = [vectorX.toJSON(), vectorY.toJSON()];
        }
        /**
         * Insert orientation description to a data object, with orientation defined by u and v direction
         * vectors.
         * @param vectorX u direction
         * @param vectorV v direction
         * @param omitIfIdentity omit the axis data if the vectorU and vectorV are global x and y vectors.
         * @param data AxesProps object to be annotated.
         */
        static insertXYOrientation(data, vectorU, vectorV, omitIfIdentity) {
            if (omitIfIdentity) {
                if (vectorU.isAlmostEqualXYZ(1, 0, 0) && vectorV.isAlmostEqualXYZ(0, 1, 0))
                    return;
            }
            data.xyVectors = [vectorU.toJSON(), vectorV.toJSON()];
        }
        /** Parse properties of a TransitionSpiral. */
        handleTransitionSpiral(data) {
            // TODO: HANDLE NONRIGID TRANSFORM !!
            // the spiral may have indication of how it was defined.  If so, use defined/undefined state of the original data
            // as indication of what current data to use.  (Current data may have changed due to transforms.)
            if (data instanceof DirectSpiral3d_1.DirectSpiral3d) {
                const value = {
                    origin: data.localToWorld.origin.toJSON(),
                    type: data.spiralType,
                };
                Writer.insertOrientationFromMatrix(value, data.localToWorld.matrix, true);
                if (!data.activeFractionInterval.isExact01)
                    value.activeFractionInterval = [data.activeFractionInterval.x0, data.activeFractionInterval.x1];
                // Object.defineProperty(value, "fractionInterval", { value: [data.activeFractionInterval.x0, data.activeFractionInterval.x1] });
                value.startRadius = 0;
                value.endRadius = data.nominalR1;
                value.length = data.nominalL1;
                return { transitionSpiral: value };
            }
            else if (data instanceof IntegratedSpiral3d_1.IntegratedSpiral3d) {
                // TODO: HANDLE NONRIGID TRANSFORM !!
                // the spiral may have indication of how it was defined.  If so, use defined/undefined state of the original data
                // as indication of what current data to use.  (Current data may have changed due to transforms.)
                const originalProperties = data.designProperties;
                const value = {
                    origin: data.localToWorld.origin.toJSON(),
                    type: data.spiralType,
                };
                Writer.insertOrientationFromMatrix(value, data.localToWorld.matrix, true);
                if (!data.activeFractionInterval.isExact01)
                    value.activeFractionInterval = [data.activeFractionInterval.x0, data.activeFractionInterval.x1];
                // Object.defineProperty(value, "fractionInterval", { value: [data.activeFractionInterval.x0, data.activeFractionInterval.x1] });
                // if possible, do selective output of defining data (omit exactly one out of the 5, matching original definition)
                // EXCEPT do not omit final radius .. readers want it?
                if (originalProperties !== undefined && originalProperties.numDefinedProperties() === 4) {
                    if (originalProperties.radius0 !== undefined)
                        value.startRadius = data.radius01.x0;
                    if (originalProperties.radius1 !== undefined)
                        value.endRadius = data.radius01.x1;
                    if (originalProperties.bearing0 !== undefined)
                        value.startBearing = data.bearing01.startAngle.toJSON();
                    if (originalProperties.bearing1 !== undefined)
                        value.endBearing = data.bearing01.endAngle.toJSON();
                    if (originalProperties.curveLength !== undefined)
                        value.length = data.curveLength();
                    if (value.endRadius === undefined)
                        value.endRadius = data.radius01.x1;
                }
                else {
                    // uh oh ... no original data, but the spiral itself knows all 5 values.  We don't know which to consider primary.
                    // DECISION -- put everything out, let readers make sense if they can. (It should be consistent ?)
                    value.startRadius = data.radius01.x0;
                    value.endRadius = data.radius01.x1;
                    value.startBearing = data.bearing01.startAngle.toJSON();
                    value.endBearing = data.bearing01.endAngle.toJSON();
                    value.length = data.curveLength();
                }
                return { transitionSpiral: value };
            }
            return undefined;
        }
        /** Convert strongly typed instance to tagged json */
        handleCone(data) {
            const radiusA = data.getRadiusA();
            const radiusB = data.getRadiusB();
            const centerA = data.getCenterA();
            const centerB = data.getCenterB();
            const vectorX = data.getVectorX();
            const vectorY = data.getVectorY();
            const xMag = vectorX.magnitude();
            const yMag = vectorY.magnitude();
            const xySameLength = Geometry_1.Geometry.isSameCoordinate(xMag, yMag);
            const axisVector = Point3dVector3d_1.Vector3d.createStartEnd(centerA, centerB);
            // special case of cylinder
            if (Geometry_1.Geometry.isSameCoordinate(radiusA, radiusB)
                && vectorX.isPerpendicularTo(axisVector)
                && vectorY.isPerpendicularTo(axisVector)
                && xySameLength
                && Geometry_1.Geometry.isSameCoordinate(xMag, 1.0)) {
                return {
                    cylinder: {
                        capped: data.capped,
                        start: centerA.toJSON(),
                        end: centerB.toJSON(),
                        radius: radiusA,
                    },
                };
            }
            const coneProps = {
                capped: data.capped,
                start: centerA.toJSON(),
                end: centerB.toJSON(),
            };
            if (Geometry_1.Geometry.isSameCoordinate(radiusA, radiusB)) {
                coneProps.radius = radiusA;
            }
            else {
                coneProps.startRadius = radiusA;
                coneProps.endRadius = radiusB;
            }
            // always specify an orthogonal frame for backwards compatibility
            Writer.insertOrientationFromXYVectors(coneProps, vectorX, vectorY, false);
            // specify a general matrix if elliptical sections
            const ellipticalSections = !xySameLength || !vectorX.isPerpendicularTo(vectorY, true);
            if (ellipticalSections)
                coneProps.xyzVectors = [vectorX.toJSON(), vectorY.toJSON(), axisVector.toJSON()];
            return { cone: coneProps };
        }
        /** Convert strongly typed instance to tagged json */
        handleSphere(data) {
            const xData = data.cloneVectorX().normalizeWithLength();
            const yData = data.cloneVectorY().normalizeWithLength();
            const zData = data.cloneVectorZ().normalizeWithLength();
            if (!xData.v || !yData.v || !zData.v)
                return undefined;
            const rigid = Matrix3d_1.Matrix3d.createIdentity();
            const skew = Matrix3d_1.Matrix3d.createIdentity();
            if (!data.cloneLocalToWorld().matrix.factorRigidSkew(rigid, skew))
                return undefined;
            const value = { center: data.cloneCenter().toJSON() };
            // always specify an orthogonal frame if !identity for backwards compatibility
            if (!rigid.isIdentity)
                value.zxVectors = [zData.v.toJSON(), xData.v.toJSON()];
            // specify a general matrix if skew/mirror local frame
            if (!skew.isDiagonal || skew.determinant() < 0.0)
                value.xyzVectors = [xData.v.toJSON(), yData.v.toJSON(), zData.v.toJSON()];
            const latitudeSweep = data.cloneLatitudeSweep();
            const fullSweep = latitudeSweep.isFullLatitudeSweep;
            if (data.capped && !fullSweep)
                value.capped = data.capped;
            if (!fullSweep)
                value.latitudeStartEnd = latitudeSweep.toJSON();
            const rX = xData.mag;
            const rY = yData.mag;
            const rZ = zData.mag;
            if (Geometry_1.Geometry.isSameCoordinate(rX, rY)) {
                if (Geometry_1.Geometry.isSameCoordinate(rX, rZ))
                    value.radius = rX;
                else { // radiusY will pick up radiusX
                    value.radiusX = rX;
                    value.radiusZ = rZ;
                }
            }
            else {
                value.radiusX = rX;
                value.radiusY = rY;
                value.radiusZ = rZ;
            }
            return { sphere: value };
        }
        /** Convert strongly typed instance to tagged json */
        handleTorusPipe(data) {
            const vectorX = data.cloneVectorX();
            const vectorY = data.cloneVectorY();
            const radiusA = data.getMajorRadius();
            const radiusB = data.getMinorRadius();
            const sweep = data.getSweepAngle();
            if (data.getIsReversed()) {
                // the TorusPipe was created with negative sweep that was forced positive; restore original values
                vectorY.scaleInPlace(-1.0);
                sweep.setRadians(-sweep.radians);
            }
            const value = {
                center: data.cloneCenter().toJSON(),
                majorRadius: radiusA,
                minorRadius: radiusB,
                xyVectors: [vectorX.toJSON(), vectorY.toJSON()],
            };
            if (!sweep.isFullCircle) {
                value.sweepAngle = sweep.degrees;
                value.capped = data.capped;
            }
            return { torusPipe: value };
        }
        /** Convert strongly typed instance to tagged json */
        handleLineString3d(data) {
            const pointsA = data.points;
            const pointsB = [];
            if (pointsA)
                for (const p of pointsA)
                    pointsB.push(p.toJSON());
            return { lineString: pointsB };
        }
        /** Convert strongly typed instance to tagged json */
        handlePointString3d(data) {
            const pointsA = data.points;
            const pointsB = [];
            if (pointsA)
                for (const p of pointsA)
                    pointsB.push(p.toJSON());
            return { pointString: pointsB };
        }
        /** Convert strongly typed instance to tagged json */
        handlePath(data) {
            return { path: this.collectChildren(data) };
        }
        /** Convert strongly typed instance to tagged json */
        handleLoop(data) {
            return { loop: this.collectChildren(data) };
        }
        /** Convert strongly typed instance to tagged json */
        handleParityRegion(data) {
            return { parityRegion: this.collectChildren(data) };
        }
        /** Convert strongly typed instance to tagged json */
        handleUnionRegion(data) {
            return { unionRegion: this.collectChildren(data) };
        }
        /** Convert strongly typed instance to tagged json */
        handleBagOfCurves(data) {
            return { bagOfCurves: this.collectChildren(data) };
        }
        collectChildren(data) {
            const children = [];
            if (data.children && Array.isArray(data.children)) {
                for (const child of data.children) {
                    const cdata = child.dispatchToGeometryHandler(this);
                    if (cdata)
                        children.push(cdata);
                }
            }
            return children;
        }
        /** Convert strongly typed instance to tagged json */
        handleLinearSweep(data) {
            const extrusionVector = data.cloneSweepVector();
            const curves = data.getCurvesRef();
            const capped = data.capped;
            if (extrusionVector
                && curves
                && capped !== undefined) {
                return {
                    linearSweep: {
                        contour: curves.dispatchToGeometryHandler(this),
                        capped,
                        vector: extrusionVector.toJSON(),
                    },
                };
            }
            return undefined;
        }
        /** Convert strongly typed instance to tagged json */
        handleRuledSweep(data) {
            const contours = data.cloneContours();
            const capped = data.capped;
            if (contours
                && contours.length > 1
                && capped !== undefined) {
                const jsonContours = [];
                for (const c of contours) {
                    jsonContours.push(this.emit(c));
                }
                return {
                    ruledSweep: {
                        contour: jsonContours,
                        capped,
                    },
                };
            }
            return undefined;
        }
        /** Convert strongly typed instance to tagged json */
        handleRotationalSweep(data) {
            const axisRay = data.cloneAxisRay();
            const curves = data.getCurves();
            const capped = data.capped;
            const sweepAngle = data.getSweep();
            return {
                rotationalSweep: {
                    axis: axisRay.direction.toJSON(),
                    contour: curves.dispatchToGeometryHandler(this),
                    capped,
                    center: axisRay.origin.toJSON(),
                    sweepAngle: sweepAngle.degrees,
                },
            };
        }
        /** Convert strongly typed instance to tagged json */
        handleBox(box) {
            const out = {
                box: {
                    origin: box.getBaseOrigin().toJSON(),
                    baseOrigin: box.getBaseOrigin().toJSON(),
                    baseX: box.getBaseX(),
                    baseY: box.getBaseY(),
                    capped: box.capped,
                    topOrigin: box.getTopOrigin().toJSON(),
                },
            };
            const outBox = out.box;
            Writer.insertXYOrientation(outBox, box.getVectorX(), box.getVectorY(), true);
            if (!Geometry_1.Geometry.isSameCoordinate(box.getTopX(), box.getBaseX()))
                outBox.topX = box.getTopX();
            if (!Geometry_1.Geometry.isSameCoordinate(box.getTopY(), box.getBaseY()))
                outBox.topY = box.getTopY();
            return out;
        }
        handlePolyfaceAuxData(auxData, pf) {
            (0, core_bentley_1.assert)(auxData === pf.data.auxData);
            const contents = { indices: [], channels: [] };
            const visitor = pf.createVisitor(0);
            while (visitor.moveToNextFacet()) {
                for (let i = 0; i < visitor.indexCount; i++)
                    contents.indices.push(visitor.auxData.indices[i] + 1);
                contents.indices.push(0); // facet terminator.
            }
            for (const inChannel of auxData.channels) {
                const outChannel = { data: [], dataType: inChannel.dataType, name: inChannel.name, inputName: inChannel.inputName };
                for (const inData of inChannel.data) {
                    const outData = { input: inData.input, values: inData.values.slice(0) };
                    outChannel.data.push(outData);
                }
                contents.channels.push(outChannel);
            }
            return contents;
        }
        /** Convert strongly typed instance to tagged json */
        handleIndexedPolyface(pf) {
            const points = [];
            const pointIndex = [];
            const normals = [];
            const params = [];
            const colors = [];
            {
                const p = Point3dVector3d_1.Point3d.create();
                for (let i = 0; pf.data.point.getPoint3dAtCheckedPointIndex(i, p); i++)
                    points.push(p.toJSON());
            }
            if (pf.data.normal) {
                const numNormal = pf.data.normal.length;
                const normal = Point3dVector3d_1.Vector3d.create();
                for (let i = 0; i < numNormal; i++) {
                    pf.data.normal.getVector3dAtCheckedVectorIndex(i, normal);
                    normals.push(normal.toJSON());
                }
            }
            if (pf.data.param) {
                const uv = Point2dVector2d_1.Point2d.create();
                for (let i = 0; pf.data.param.getPoint2dAtCheckedPointIndex(i, uv); i++)
                    params.push(uv.toJSON());
            }
            if (pf.data.color) {
                for (const value of pf.data.color)
                    colors.push(value);
            }
            const visitor = pf.createVisitor(0);
            let indexCounter = 0;
            const normalIndex = [];
            const paramIndex = [];
            const colorIndex = [];
            let n;
            while (visitor.moveToNextFacet()) {
                n = visitor.indexCount;
                // All meshes have point and point index ...
                for (let i = 0; i < n; i++) {
                    // Change sign of value to be pushed based on whether or not the edge was originally visible or not
                    const toPush = pf.data.edgeVisible[indexCounter + i] ? visitor.pointIndex[i] + 1 : -(visitor.clientPointIndex(i) + 1);
                    pointIndex.push(toPush);
                }
                pointIndex.push(0); // facet terminator.
                indexCounter += visitor.indexCount;
                if (visitor.normalIndex) {
                    for (let i = 0; i < n; i++)
                        normalIndex.push(1 + visitor.clientNormalIndex(i));
                    normalIndex.push(0);
                }
                if (visitor.paramIndex) {
                    for (let i = 0; i < n; i++)
                        paramIndex.push(1 + visitor.clientParamIndex(i));
                    paramIndex.push(0);
                }
                if (visitor.colorIndex) {
                    for (let i = 0; i < n; i++)
                        colorIndex.push(1 + visitor.clientColorIndex(i));
                    colorIndex.push(0);
                }
            }
            let taggedNumericData;
            if (pf.data.taggedNumericData) {
                taggedNumericData = this.handleTaggedNumericData(pf.data.taggedNumericData);
            }
            let edgeMateIndex;
            if (pf.data.edgeMateIndex) {
                edgeMateIndex = [];
                if (!SerializationHelpers_1.SerializationHelpers.announceUncompressedZeroBasedReflexiveIndices(pf.data.edgeMateIndex, pf.facetStart, SerializationHelpers_1.SerializationHelpers.EdgeMateIndex.BlockSeparator, SerializationHelpers_1.SerializationHelpers.EdgeMateIndex.NoEdgeMate, (i) => edgeMateIndex.push(i))) {
                    edgeMateIndex = undefined;
                }
            }
            const contents = {};
            if (pf.expectedClosure !== 0)
                contents.expectedClosure = pf.expectedClosure;
            contents.twoSided = pf.twoSided;
            if (pf.data.auxData)
                contents.auxData = this.handlePolyfaceAuxData(pf.data.auxData, pf);
            if (pf.data.color)
                contents.color = colors;
            if (pf.data.colorIndex)
                contents.colorIndex = colorIndex;
            if (pf.data.normal)
                contents.normal = normals;
            if (pf.data.normalIndex)
                contents.normalIndex = normalIndex;
            if (pf.data.param)
                contents.param = params;
            if (pf.data.paramIndex)
                contents.paramIndex = paramIndex;
            contents.point = points;
            contents.pointIndex = pointIndex;
            if (taggedNumericData)
                contents.tags = taggedNumericData;
            if (pf.data.edgeMateIndex)
                contents.edgeMateIndex = edgeMateIndex;
            // NOTE: pf.data.face is not persistent
            return { indexedMesh: contents };
        }
        handleBSplineCurve(curve) {
            const data = SerializationHelpers_1.SerializationHelpers.createBSplineCurveData(curve.polesRef, curve.poleDimension, curve.knotsRef, curve.numPoles, curve.order);
            const wrapMode = curve.getWrappable();
            if (KnotVector_1.BSplineWrapMode.None !== wrapMode)
                data.params.wrapMode = wrapMode;
            if (!SerializationHelpers_1.SerializationHelpers.Export.prepareBSplineCurveData(data, { jsonPoles: true, jsonKnots: true }))
                return undefined;
            return {
                bcurve: {
                    points: data.poles,
                    knots: data.params.knots,
                    order: data.params.order,
                    closed: data.params.closed ? true : undefined,
                },
            };
        }
        /** Convert strongly typed instance to tagged json */
        handleBSplineCurve3d(curve) {
            return this.handleBSplineCurve(curve);
        }
        /** Convert strongly typed instance to tagged json */
        handleInterpolationCurve3d(curve) {
            const props = curve.cloneProps();
            BSplineCurveOps_1.BSplineCurveOps.C2CubicFit.convertToJsonKnots(props);
            return { interpolationCurve: props };
        }
        /** Convert strongly typed instance to tagged json */
        handleAkimaCurve3d(curve) {
            return { akimaCurve: curve.cloneProps() };
        }
        /** Convert strongly typed instance to tagged json */
        handleBezierCurve3d(curve) {
            const knots = [];
            const order = curve.order;
            for (let i = 0; i < order; i++)
                knots.push(0.0);
            for (let i = 0; i < order; i++)
                knots.push(1.0);
            return {
                bcurve: {
                    points: curve.copyPolesAsJsonArray(),
                    knots,
                    order: curve.order,
                },
            };
        }
        /** Convert strongly typed instance to tagged json */
        handleBSplineCurve3dH(curve) {
            return this.handleBSplineCurve(curve);
        }
        /** Convert strongly typed instance to tagged json */
        handleBSplineSurface3d(surface) {
            return this.handleBSplineSurface(surface);
        }
        /** Convert strongly typed instance to tagged json */
        handleBezierCurve3dH(curve) {
            const knots = [];
            const order = curve.order;
            for (let i = 0; i < order; i++)
                knots.push(0.0);
            for (let i = 0; i < order; i++)
                knots.push(1.0);
            return {
                bcurve: {
                    points: curve.copyPolesAsJsonArray(),
                    knots,
                    order: curve.order,
                },
            };
        }
        /** Convert strongly typed instance to tagged json */
        handleBSplineSurface(surface) {
            const data = SerializationHelpers_1.SerializationHelpers.createBSplineSurfaceData(surface.coffs, surface.poleDimension, surface.knots[BSplineSurface_1.UVSelect.uDirection].knots, surface.numPolesUV(BSplineSurface_1.UVSelect.uDirection), surface.orderUV(BSplineSurface_1.UVSelect.uDirection), surface.knots[BSplineSurface_1.UVSelect.vDirection].knots, surface.numPolesUV(BSplineSurface_1.UVSelect.vDirection), surface.orderUV(BSplineSurface_1.UVSelect.vDirection));
            const wrapModeU = surface.getWrappable(BSplineSurface_1.UVSelect.uDirection);
            const wrapModeV = surface.getWrappable(BSplineSurface_1.UVSelect.vDirection);
            if (KnotVector_1.BSplineWrapMode.None !== wrapModeU)
                data.uParams.wrapMode = wrapModeU;
            if (KnotVector_1.BSplineWrapMode.None !== wrapModeV)
                data.vParams.wrapMode = wrapModeV;
            if (!SerializationHelpers_1.SerializationHelpers.Export.prepareBSplineSurfaceData(data, { jsonPoles: true, jsonKnots: true }))
                return undefined;
            return {
                bsurf: {
                    points: data.poles,
                    uKnots: data.uParams.knots,
                    vKnots: data.vParams.knots,
                    orderU: data.uParams.order,
                    orderV: data.vParams.order,
                    closedU: data.uParams.closed ? true : undefined,
                    closedV: data.vParams.closed ? true : undefined,
                },
            };
        }
        /** Convert strongly typed instance to tagged json */
        handleBSplineSurface3dH(surface) {
            return this.handleBSplineSurface(surface);
        }
        /** Convert an array of strongly typed instances to an array of tagged json */
        emitArray(data) {
            const members = [];
            for (const c of data) {
                const toPush = this.emit(c);
                members.push(toPush);
            }
            return members;
        }
        /** Convert GeometryQuery data (array or single instance) to instance to tagged json */
        emit(data) {
            if (Array.isArray(data))
                return this.emitArray(data);
            if (data instanceof GeometryQuery_1.GeometryQuery) {
                return data.dispatchToGeometryHandler(this);
            }
            else if (data instanceof TaggedNumericData_1.TaggedNumericData) {
                return this.handleTaggedNumericData(data);
            }
            return undefined;
        }
        /** One-step static method to create a writer and emit a json object */
        static toIModelJson(data) {
            const writer = new Writer();
            return writer.emit(data);
        }
    }
    IModelJson.Writer = Writer;
})(IModelJson || (exports.IModelJson = IModelJson = {}));
//# sourceMappingURL=IModelJsonSchema.js.map