"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Views
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.BackgroundMapGeometry = void 0;
exports.getFrustumPlaneIntersectionDepthRange = getFrustumPlaneIntersectionDepthRange;
exports.calculateEcefToDbTransformAtLocation = calculateEcefToDbTransformAtLocation;
const core_bentley_1 = require("@itwin/core-bentley");
const core_geometry_1 = require("@itwin/core-geometry");
const core_common_1 = require("@itwin/core-common");
const internal_1 = require("./tile/internal");
const scratchRange = core_geometry_1.Range3d.createNull();
const scratchZeroPoint = core_geometry_1.Point3d.createZero();
const scratchPoint = core_geometry_1.Point3d.create();
const scratchVector = core_geometry_1.Vector3d.create();
const scratchCenterPoint = core_geometry_1.Point3d.createZero();
const scratchIntersectRay = core_geometry_1.Ray3d.create(core_geometry_1.Point3d.create(), core_geometry_1.Vector3d.create());
const scratchEyePoint = core_geometry_1.Point3d.createZero();
const scratchViewRotation = core_geometry_1.Matrix3d.createIdentity();
const scratchSilhouetteNormal = core_geometry_1.Vector3d.create();
const scratchCartoRectangle = new core_geometry_1.GrowableXYZArray();
const scratchWorkArray = new core_geometry_1.GrowableXYZArray();
function accumulateDepthRange(point, viewRotation, range) {
    viewRotation.multiplyXYZtoXYZ(point, scratchPoint);
    range.extend(scratchPoint);
}
function accumulateFrustumPlaneDepthRange(frustum, plane, viewRotation, range, eyePoint) {
    let includeHorizon = false;
    for (let i = 0; i < 4; i++) {
        const frustumRay = core_geometry_1.Ray3d.createStartEnd(eyePoint ? eyePoint : frustum.points[i + 4], frustum.points[i]);
        const thisFraction = frustumRay.intersectionWithPlane(plane, scratchPoint);
        if (undefined !== thisFraction && (!eyePoint || thisFraction > 0))
            accumulateDepthRange(scratchPoint, viewRotation, range);
        else
            includeHorizon = true;
    }
    if (includeHorizon) {
        if (eyePoint !== undefined) {
            const eyeHeight = plane.altitude(eyePoint);
            if (eyeHeight < 0)
                accumulateDepthRange(eyePoint, viewRotation, range);
            else {
                const viewZ = viewRotation.getRow(2);
                const horizonDistance = Math.sqrt(eyeHeight * eyeHeight + 2 * eyeHeight * core_geometry_1.Constant.earthRadiusWGS84.equator);
                accumulateDepthRange(eyePoint.plusScaled(viewZ, -horizonDistance, scratchPoint), viewRotation, range);
            }
        }
    }
}
/** @internal */
function getFrustumPlaneIntersectionDepthRange(frustum, plane) {
    const eyePoint = frustum.getEyePoint(scratchEyePoint);
    const viewRotation = frustum.getRotation(scratchViewRotation);
    const intersectRange = core_geometry_1.Range3d.createNull();
    accumulateFrustumPlaneDepthRange(frustum, plane, viewRotation, intersectRange, eyePoint);
    return intersectRange.isNull ? core_geometry_1.Range1d.createNull() : core_geometry_1.Range1d.createXX(intersectRange.low.z, intersectRange.high.z);
}
/** Geometry of background map - either an ellipsoid or a plane as defined by GlobeMode.
 * @internal
 */
class BackgroundMapGeometry {
    constructor(_bimElevationBias, globeMode, _iModel) {
        this._bimElevationBias = _bimElevationBias;
        this._iModel = _iModel;
        this._ecefToDb = _iModel.getMapEcefToDb(_bimElevationBias);
        this.globeMode = globeMode;
        this.cartesianRange = BackgroundMapGeometry.getCartesianRange(_iModel);
        this.cartesianTransitionRange = this.cartesianRange.clone();
        this.cartesianTransitionRange.expandInPlace(BackgroundMapGeometry.getCartesianTransitionDistance(_iModel));
        this.cartesianDiagonal = this.cartesianRange.diagonal().magnitudeXY();
        const earthRadius = core_geometry_1.Constant.earthRadiusWGS84.equator;
        this.globeOrigin = this._ecefToDb.origin.cloneAsPoint3d();
        this.globeMatrix = this._ecefToDb.matrix.clone();
        this.cartesianChordHeight = Math.sqrt(this.cartesianDiagonal * this.cartesianDiagonal + earthRadius * earthRadius) - earthRadius; // Maximum chord height deviation of the cartesian area.
        const halfChordAngle = core_geometry_1.Angle.piOver2Radians / 10;
        this.maxGeometryChordHeight = (1 - Math.cos(halfChordAngle)) * earthRadius;
        this.cartesianPlane = this.getPlane();
        this.geometry = (globeMode === core_common_1.GlobeMode.Ellipsoid) ? this.getEarthEllipsoid() : this.cartesianPlane;
        this._mercatorTilingScheme = new internal_1.WebMercatorTilingScheme();
        this._mercatorFractionToDb = this._mercatorTilingScheme.computeMercatorFractionToDb(this._ecefToDb, _bimElevationBias, _iModel, false);
    }
    static getCartesianRange(iModel, result) {
        const cartesianRange = core_geometry_1.Range3d.createFrom(iModel.projectExtents, result);
        cartesianRange.expandInPlace(BackgroundMapGeometry.maxCartesianDistance);
        return cartesianRange;
    }
    static getCartesianTransitionDistance(iModel) {
        return BackgroundMapGeometry.getCartesianRange(iModel, scratchRange).diagonal().magnitudeXY() * BackgroundMapGeometry._transitionDistanceMultiplier;
    }
    async dbToCartographicFromGcs(db) {
        const scratch = new core_geometry_1.Point3d();
        const promises = db.map(async (p) => {
            return this.cartesianRange.containsPoint(core_geometry_1.Point3d.createFrom(p, scratch)) ? this._iModel.spatialToCartographic(p) : this.dbToCartographic(p);
        });
        return Promise.all(promises);
    }
    async dbToWGS84CartographicFromGcs(db) {
        if (db.length === 0)
            return [];
        const result = Array(db.length);
        const reproject = [];
        const reprojectIdx = [];
        const scratch = new core_geometry_1.Point3d();
        for (let i = 0; i < db.length; i++) {
            core_geometry_1.Point3d.createFrom(db[i], scratch);
            if (this.cartesianRange.containsPoint(scratch)) {
                reprojectIdx.push(i);
                reproject.push(db[i]);
            }
            else {
                result[i] = this.dbToCartographic(db[i]);
            }
        }
        if (reproject.length === 0)
            return result;
        const reprojectPromise = this._iModel.wgs84CartographicFromSpatial(reproject);
        return reprojectPromise.then((reprojected) => {
            if (reprojected.length === reprojectIdx.length) { // reprojected array size must match our index array, otherwise something is OFF
                for (let i = 0; i < reprojected.length; i++) {
                    result[reprojectIdx[i]] = reprojected[i]; // Insert the reprojected values at their original index
                }
            }
            return result;
        });
    }
    dbToCartographic(db, result) {
        if (undefined === result)
            result = core_common_1.Cartographic.createZero();
        if (this.globeMode === core_common_1.GlobeMode.Plane) {
            const mercatorFraction = this._mercatorFractionToDb.multiplyInversePoint3d(db);
            return this._mercatorTilingScheme.fractionToCartographic(mercatorFraction.x, mercatorFraction.y, result, mercatorFraction.z);
        }
        else {
            const ecef = this._ecefToDb.multiplyInversePoint3d(db);
            return core_common_1.Cartographic.fromEcef(ecef, result);
        }
    }
    async cartographicToDbFromGcs(cartographic) {
        let db;
        if (this.globeMode === core_common_1.GlobeMode.Plane) {
            const fraction = core_geometry_1.Point2d.create(0, 0);
            db = cartographic.map((p) => {
                this._mercatorTilingScheme.cartographicToFraction(p.latitude, p.longitude, fraction);
                return this._mercatorFractionToDb.multiplyXYZ(fraction.x, fraction.y, p.height);
            });
        }
        else {
            db = cartographic.map((p) => this._ecefToDb.multiplyPoint3d(p.toEcef()));
        }
        if (this._iModel.noGcsDefined)
            return db;
        const promises = db.map(async (p, i) => {
            return this.cartesianRange.containsPoint(p) ? this._iModel.cartographicToSpatialFromGcs(cartographic[i]) : p;
        });
        return Promise.all(promises);
    }
    async cartographicToDbFromWgs84Gcs(cartographic) {
        let db;
        if (this.globeMode === core_common_1.GlobeMode.Plane) {
            const fraction = core_geometry_1.Point2d.create(0, 0);
            db = cartographic.map((p) => {
                this._mercatorTilingScheme.cartographicToFraction(p.latitude, p.longitude, fraction);
                return this._mercatorFractionToDb.multiplyXYZ(fraction.x, fraction.y, p.height);
            });
        }
        else {
            db = cartographic.map((p) => this._ecefToDb.multiplyPoint3d(p.toEcef()));
        }
        if (this._iModel.noGcsDefined)
            return db;
        const toReprojectCoords = [];
        const toReprojectIdx = [];
        db.forEach(async (p, i) => {
            if (this.cartesianRange.containsPoint(p)) {
                toReprojectCoords.push({ x: cartographic[i].longitudeDegrees, y: cartographic[i].latitudeDegrees, z: cartographic[i].height });
                toReprojectIdx.push(i);
            }
        });
        const spatialPoints = await this._iModel.toSpatialFromGcs(toReprojectCoords, { horizontalCRS: { epsg: 4326 }, verticalCRS: { id: "ELLIPSOID" } });
        return db.map((p, i) => {
            const reprojectedIdx = toReprojectIdx.findIndex((value) => value === i);
            return (reprojectedIdx === -1 ? p : spatialPoints[reprojectedIdx]);
        });
    }
    cartographicToDb(cartographic, result) {
        if (this.globeMode === core_common_1.GlobeMode.Plane) {
            const fraction = core_geometry_1.Point2d.create(0, 0);
            this._mercatorTilingScheme.cartographicToFraction(cartographic.latitude, cartographic.longitude, fraction);
            return this._mercatorFractionToDb.multiplyXYZ(fraction.x, fraction.y, cartographic.height, result);
        }
        else {
            return this._ecefToDb.multiplyPoint3d(cartographic.toEcef());
        }
    }
    getEarthEllipsoid(radiusOffset = 0) {
        const equatorRadius = core_geometry_1.Constant.earthRadiusWGS84.equator + radiusOffset, polarRadius = core_geometry_1.Constant.earthRadiusWGS84.polar + radiusOffset;
        return core_geometry_1.Ellipsoid.createCenterMatrixRadii(this.globeOrigin, this.globeMatrix, equatorRadius, equatorRadius, polarRadius);
    }
    getPlane(offset = 0) {
        return core_geometry_1.Plane3dByOriginAndUnitNormal.create(core_geometry_1.Point3d.create(0, 0, this._bimElevationBias + offset), core_geometry_1.Vector3d.create(0, 0, 1));
    }
    getRayIntersection(ray, positiveOnly) {
        let intersect;
        if (this.globeMode === core_common_1.GlobeMode.Ellipsoid) {
            const ellipsoid = this.geometry;
            BackgroundMapGeometry._scratchRayAngles.length = 0;
            BackgroundMapGeometry._scratchRayFractions.length = 0;
            const count = ellipsoid.intersectRay(ray, BackgroundMapGeometry._scratchRayFractions, undefined, BackgroundMapGeometry._scratchRayAngles);
            let intersectDistance;
            for (let i = 0; i < count; i++) {
                const thisFraction = BackgroundMapGeometry._scratchRayFractions[i];
                if ((!positiveOnly || thisFraction > 0) && (undefined === intersectDistance || thisFraction < intersectDistance)) {
                    intersectDistance = thisFraction;
                    intersect = scratchIntersectRay;
                    ellipsoid.radiansToUnitNormalRay(BackgroundMapGeometry._scratchRayAngles[i].longitudeRadians, BackgroundMapGeometry._scratchRayAngles[i].latitudeRadians, intersect);
                    if (intersect.direction.dotProduct(ray.direction) < 0) {
                        if (this.cartesianRange.containsPoint(intersect.origin)) { // If we're in the cartesian range, correct to planar intersection.
                            const planeFraction = ray.intersectionWithPlane(this.cartesianPlane, scratchIntersectRay.origin);
                            if (undefined !== planeFraction && (!positiveOnly || planeFraction > 0)) {
                                intersect.direction.setFromVector3d(this.cartesianPlane.getNormalRef());
                            }
                        }
                    }
                }
            }
        }
        else {
            const plane = this.geometry;
            const thisFraction = ray.intersectionWithPlane(plane, scratchIntersectRay.origin);
            if (undefined !== thisFraction && (!positiveOnly || thisFraction > 0)) {
                intersect = scratchIntersectRay;
                intersect.direction.setFromVector3d(plane.getNormalRef());
            }
        }
        return intersect;
    }
    getPointHeight(point) {
        if (this.globeMode === core_common_1.GlobeMode.Ellipsoid) {
            const ellipsoid = this.geometry;
            const projected = ellipsoid.projectPointToSurface(point);
            if (undefined === projected)
                return undefined;
            const distance = ellipsoid.radiansToPoint(projected.longitudeRadians, projected.latitudeRadians).distance(point);
            const ellipsePoint = ellipsoid.transformRef.multiplyInversePoint3d(point, BackgroundMapGeometry._scratchPoint);
            return ellipsePoint.magnitude() < 1 ? -distance : distance;
        }
        else {
            const plane = this.geometry;
            return plane.altitude(point);
        }
    }
    /** @internal */
    getFrustumIntersectionDepthRange(frustum, bimRange, heightRange, gridPlane, doGlobalScope) {
        const clipPlanes = frustum.getRangePlanes(false, false, 0);
        const eyePoint = frustum.getEyePoint(scratchEyePoint);
        const viewRotation = frustum.getRotation(scratchViewRotation);
        if (undefined === viewRotation)
            return core_geometry_1.Range1d.createNull(); // Degenerate frustum...
        const viewZ = viewRotation.getRow(2);
        const cartoRange = this.cartesianTransitionRange;
        const intersectRange = core_geometry_1.Range3d.createNull();
        const doAccumulate = ((point) => accumulateDepthRange(point, viewRotation, intersectRange));
        if (gridPlane)
            accumulateFrustumPlaneDepthRange(frustum, gridPlane, viewRotation, intersectRange, eyePoint);
        if (this.geometry instanceof core_geometry_1.Plane3dByOriginAndUnitNormal) {
            // Intersection with a planar background projection...
            const heights = heightRange ? [heightRange.low, heightRange.high] : [0];
            for (const height of heights) {
                accumulateFrustumPlaneDepthRange(frustum, this.getPlane(height), viewRotation, intersectRange, eyePoint);
            }
        }
        else {
            const minOffset = heightRange ? heightRange.low : 0, maxOffset = (heightRange ? heightRange.high : 0) + this.cartesianChordHeight;
            const radiusOffsets = [minOffset, maxOffset];
            // If we are doing global scope then include minimum ellipsoid that represents the chordal approximation of the low level tiles.
            // this substantially expands the frustum so don't do it for non-global views, but this clipping out the low level tiles.
            if (doGlobalScope)
                radiusOffsets.push(minOffset - this.maxGeometryChordHeight);
            const toView = core_geometry_1.Transform.createRefs(core_geometry_1.Point3d.createZero(), viewRotation);
            const eyePoint4d = eyePoint ? core_geometry_1.Point4d.createFromPointAndWeight(eyePoint, 1) : core_geometry_1.Point4d.createFromPointAndWeight(viewZ, 0);
            for (const radiusOffset of radiusOffsets) {
                const ellipsoid = this.getEarthEllipsoid(radiusOffset);
                const isInside = eyePoint && ellipsoid.worldToLocal(eyePoint).magnitude() < 1.0;
                const center = ellipsoid.localToWorld(scratchZeroPoint, scratchCenterPoint);
                const clipPlaneCount = clipPlanes.planes.length;
                // Extrema...
                let angles, extremaPoint;
                if (undefined !== (angles = ellipsoid.surfaceNormalToAngles(viewZ)) &&
                    undefined !== (extremaPoint = ellipsoid.radiansToPoint(angles.longitudeRadians, angles.latitudeRadians)) &&
                    (eyePoint === undefined || viewZ.dotProductStartEnd(extremaPoint, eyePoint) > 0) &&
                    clipPlanes.classifyPointContainment([extremaPoint], false) !== core_geometry_1.ClipPlaneContainment.StronglyOutside)
                    doAccumulate(extremaPoint);
                if (isInside) {
                    if (eyePoint)
                        doAccumulate(eyePoint);
                }
                else {
                    const silhouette = ellipsoid.silhouetteArc(eyePoint4d);
                    if (silhouette !== undefined) {
                        silhouette.perpendicularVector.clone(scratchSilhouetteNormal);
                        // Push the silhouette plane as clip so that we do not include geometry at other side of ellipsoid.
                        // First make sure that it is pointing in the right direction.
                        if (eyePoint) {
                            // Clip toward eye.
                            if (scratchSilhouetteNormal.dotProduct(viewZ) < 0)
                                scratchSilhouetteNormal.negate(scratchSilhouetteNormal);
                        }
                        else {
                            /* If parallel projection - clip toward side of ellipsoid with BIM geometry */
                            if (core_geometry_1.Vector3d.createStartEnd(silhouette.center, bimRange.center).dotProduct(scratchSilhouetteNormal) < 0)
                                scratchSilhouetteNormal.negate(scratchSilhouetteNormal);
                        }
                        clipPlanes.planes.push(core_geometry_1.ClipPlane.createNormalAndDistance(scratchSilhouetteNormal, scratchSilhouetteNormal.dotProduct(silhouette.center)));
                    }
                    else {
                        clipPlanes.planes.push(core_geometry_1.ClipPlane.createNormalAndPoint(viewZ, center));
                    }
                }
                if (!isInside || radiusOffset === radiusOffsets[0]) {
                    // Intersections of ellipsoid with frustum planes...
                    const viewingInside = eyePoint !== undefined && viewZ.dotProduct(core_geometry_1.Vector3d.createStartEnd(center, eyePoint)) < 0;
                    if (eyePoint === undefined || !isInside || viewingInside) {
                        for (const clipPlane of clipPlanes.planes) {
                            const plane = clipPlane.getPlane3d();
                            const arc = ellipsoid.createPlaneSection(plane);
                            if (undefined !== arc) {
                                arc.announceClipIntervals(clipPlanes, (a0, a1, cp) => {
                                    if (Math.abs(a1 - a0) < 1.0E-8) {
                                        doAccumulate(cp.fractionToPoint(a0)); // Tiny sweep - avoid problem with rangeMethod (not worth doing anyway).
                                    }
                                    else {
                                        const segment = cp.clonePartialCurve(a0, a1);
                                        if (segment !== undefined)
                                            segment.extendRange(intersectRange, toView);
                                    }
                                });
                            }
                        }
                    }
                    // Intersections of the cartesian region with frustum planes.
                    scratchCartoRectangle.resize(0);
                    scratchCartoRectangle.push({ x: cartoRange.low.x, y: cartoRange.low.y, z: radiusOffset });
                    scratchCartoRectangle.push({ x: cartoRange.high.x, y: cartoRange.low.y, z: radiusOffset });
                    scratchCartoRectangle.push({ x: cartoRange.high.x, y: cartoRange.high.y, z: radiusOffset });
                    scratchCartoRectangle.push({ x: cartoRange.low.x, y: cartoRange.high.y, z: radiusOffset });
                    scratchCartoRectangle.push({ x: cartoRange.low.x, y: cartoRange.low.y, z: radiusOffset });
                    clipPlanes.clipConvexPolygonInPlace(scratchCartoRectangle, scratchWorkArray);
                    for (let i = 0; i < scratchCartoRectangle.length; i++)
                        doAccumulate(scratchCartoRectangle.getPoint3dAtUncheckedPointIndex(i));
                    while (clipPlanes.planes.length > clipPlaneCount) // Remove pushed silhouette plane.
                        clipPlanes.planes.pop();
                }
            }
        }
        if (intersectRange.zLength() < 5) {
            // For the case where the fitted depth is small (less than 5 meters) we must be viewing planar projection or the
            // planar portion of the iModel in plan view. In this case use a constant (arbitrarily 100 meters) depth so that the frustum
            // Z is doesn't change and cause nearly planar geometry to jitter in Z buffer.
            const zCenter = (intersectRange.low.z + intersectRange.high.z) / 2;
            const zExpand = 50;
            return core_geometry_1.Range1d.createXX(zCenter - zExpand, zCenter + zExpand);
        }
        else {
            const diagonal = intersectRange.diagonal(scratchVector).magnitudeXY();
            const expansion = diagonal * .01;
            return core_geometry_1.Range1d.createXX(intersectRange.low.z - expansion, intersectRange.high.z + expansion);
        }
    }
    addFrustumDecorations(builder, frustum) {
        if (this.geometry instanceof core_geometry_1.Ellipsoid) {
            const ellipsoid = this.geometry;
            const clipPlanes = frustum.getRangePlanes(false, false, 0);
            const viewRotation = frustum.getRotation();
            const eyePoint = frustum.getEyePoint(scratchEyePoint);
            const viewZ = viewRotation.getRow(2);
            const eyePoint4d = eyePoint ? core_geometry_1.Point4d.createFromPointAndWeight(eyePoint, 1) : core_geometry_1.Point4d.createFromPointAndWeight(viewZ, 0);
            const isInside = eyePoint && ellipsoid.worldToLocal(eyePoint).magnitude() < 1.0;
            const center = ellipsoid.localToWorld(scratchZeroPoint, scratchCenterPoint);
            const cartoRange = this.cartesianTransitionRange;
            if (!isInside) {
                const silhouette = ellipsoid.silhouetteArc(eyePoint4d);
                if (silhouette !== undefined) {
                    silhouette.perpendicularVector.clone(scratchSilhouetteNormal);
                    if (scratchSilhouetteNormal.dotProduct(viewZ) < 0)
                        scratchSilhouetteNormal.negate(scratchSilhouetteNormal);
                    clipPlanes.planes.push(core_geometry_1.ClipPlane.createNormalAndDistance(scratchSilhouetteNormal, scratchSilhouetteNormal.dotProduct(silhouette.center)));
                }
                else {
                    clipPlanes.planes.push(core_geometry_1.ClipPlane.createNormalAndPoint(viewZ, center));
                }
                const ellipsoidColor = core_common_1.ColorDef.create(core_common_1.ColorByName.yellow);
                builder.setSymbology(ellipsoidColor, ellipsoidColor, 1, core_common_1.LinePixels.Code2);
                for (const clipPlane of clipPlanes.planes) {
                    const plane = clipPlane.getPlane3d();
                    const arc = ellipsoid.createPlaneSection(plane);
                    if (undefined !== arc) {
                        arc.announceClipIntervals(clipPlanes, (a0, a1, cp) => {
                            const segment = cp.clonePartialCurve(a0, a1);
                            if (segment !== undefined)
                                builder.addArc(segment, false, false);
                        });
                    }
                    // Intersections of the cartesian region with frustum planes.
                    scratchCartoRectangle.resize(0);
                    scratchCartoRectangle.push({ x: cartoRange.low.x, y: cartoRange.low.y, z: 0 });
                    scratchCartoRectangle.push({ x: cartoRange.high.x, y: cartoRange.low.y, z: 0 });
                    scratchCartoRectangle.push({ x: cartoRange.high.x, y: cartoRange.high.y, z: 0 });
                    scratchCartoRectangle.push({ x: cartoRange.low.x, y: cartoRange.high.y, z: 0 });
                    scratchCartoRectangle.push({ x: cartoRange.low.x, y: cartoRange.low.y, z: 0 });
                    clipPlanes.clipConvexPolygonInPlace(scratchCartoRectangle, scratchWorkArray);
                    if (scratchCartoRectangle.length > 0) {
                        builder.addLineString(scratchCartoRectangle.getPoint3dArray());
                    }
                }
            }
        }
    }
}
exports.BackgroundMapGeometry = BackgroundMapGeometry;
BackgroundMapGeometry.maxCartesianDistance = 1E4; // If globe is 3D we still consider the map geometry flat within this distance of the project extents.
BackgroundMapGeometry._transitionDistanceMultiplier = .25; // In the transition range which extends beyond the cartesian range we interpolate between cartesian and ellipsoid.
BackgroundMapGeometry._scratchRayFractions = new Array();
BackgroundMapGeometry._scratchRayAngles = new Array();
BackgroundMapGeometry._scratchPoint = core_geometry_1.Point3d.createZero();
/** Calculate the ECEF to database (IModel) coordinate transform at a provided location, using the GCS of the iModel.
 *  The transform will exactly represent the GCS at the provided location.
 * @public
 */
async function calculateEcefToDbTransformAtLocation(originIn, iModel) {
    const geoConverter = iModel.noGcsDefined ? undefined : iModel.geoServices.getConverter("WGS84");
    if (geoConverter === undefined)
        return undefined;
    const origin = core_geometry_1.Point3d.create(originIn.x, originIn.y, 0); // Always Test at zero.
    const eastPoint = origin.plusXYZ(1, 0, 0);
    const northPoint = origin.plusXYZ(0, 1, 0);
    const response = await geoConverter.getGeoCoordinatesFromIModelCoordinates([origin, northPoint, eastPoint]);
    if (response.geoCoords[0].s !== core_common_1.GeoCoordStatus.Success || response.geoCoords[1].s !== core_common_1.GeoCoordStatus.Success || response.geoCoords[2].s !== core_common_1.GeoCoordStatus.Success)
        return undefined;
    const geoOrigin = core_geometry_1.Point3d.fromJSON(response.geoCoords[0].p);
    const geoNorth = core_geometry_1.Point3d.fromJSON(response.geoCoords[1].p);
    const geoEast = core_geometry_1.Point3d.fromJSON(response.geoCoords[2].p);
    const ecefOrigin = core_common_1.Cartographic.fromDegrees({ longitude: geoOrigin.x, latitude: geoOrigin.y, height: geoOrigin.z }).toEcef();
    const ecefNorth = core_common_1.Cartographic.fromDegrees({ longitude: geoNorth.x, latitude: geoNorth.y, height: geoNorth.z }).toEcef();
    const ecefEast = core_common_1.Cartographic.fromDegrees({ longitude: geoEast.x, latitude: geoEast.y, height: geoEast.z }).toEcef();
    const xVector = core_geometry_1.Vector3d.createStartEnd(ecefOrigin, ecefEast);
    const yVector = core_geometry_1.Vector3d.createStartEnd(ecefOrigin, ecefNorth);
    const zVector = xVector.unitCrossProduct(yVector);
    if (undefined === zVector) {
        (0, core_bentley_1.assert)(false); // Should never occur.
        return undefined;
    }
    const matrix = core_geometry_1.Matrix3d.createColumns(xVector, yVector, zVector);
    if (matrix === undefined)
        return undefined;
    const inverse = matrix.inverse();
    if (inverse === undefined) {
        (0, core_bentley_1.assert)(false); // Should never occur.
        return undefined;
    }
    return core_geometry_1.Transform.createMatrixPickupPutdown(matrix, origin, ecefOrigin).inverse();
}
//# sourceMappingURL=BackgroundMapGeometry.js.map