/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Tiles
 */
import { assert } from "@itwin/core-bentley";
import { Angle, Ellipsoid, EllipsoidPatch, Point2d, Point3d, Range1d, Range3d, Transform } from "@itwin/core-geometry";
import { RealityMeshParamsBuilder } from "../../render/RealityMeshParams";
import { TerrainMeshProvider, WebMercatorTilingScheme, } from "../internal";
const scratchPoint2d = Point2d.createZero();
const scratchPoint = Point3d.createZero();
const scratchEllipsoid = Ellipsoid.create(Transform.createIdentity());
const scratchZeroRange = Range1d.createXX(0, 0);
/** A terrain mesh provider that produces geometry that represents a smooth ellipsoid without any height perturbations.
 * The area within the project extents are represented as planar tiles and other tiles are facetted approximations
 * of the WGS84 ellipsoid.
 * This is the terrain provider used when the background map is enabled but 3d terrain is disabled.
 * @public
 */
export class EllipsoidTerrainProvider extends TerrainMeshProvider {
    /** Construct a new terrain provider.
     * @note [[TerrainMeshProviderOptions.wantNormals]] is ignored - no normals are produced.
     */
    constructor(opts) {
        super();
        this._tilingScheme = new WebMercatorTilingScheme();
        this._wantSkirts = opts.wantSkirts;
    }
    /** Implements [[TerrainMeshProvider.maxDepth]] to return a fixed maximum depth of 22. */
    get maxDepth() { return 22; }
    /** Implements [[TerrainMeshProvider.getChildHeightRange]] to return an empty range, because the ellipsoid is smooth. */
    getChildHeightRange(_quadId, _rectangle, _parent) {
        return scratchZeroRange;
    }
    /** Implements [[TerrainMeshProvider.tilingScheme]]. */
    get tilingScheme() {
        return this._tilingScheme;
    }
    createSkirtlessPlanarMesh(tile) {
        const projection = tile.getProjection();
        const builder = new RealityMeshParamsBuilder({
            positionRange: projection.localRange,
            initialVertexCapacity: 4,
            initialIndexCapacity: 6,
        });
        const uv = new Point2d();
        const pos = new Point3d();
        for (let v = 0; v < 2; v++) {
            for (let u = 0; u < 2; u++) {
                Point2d.create(u, 1 - v, uv);
                builder.addUnquantizedVertex(projection.getPoint(u, v, 0, pos), uv);
            }
        }
        builder.addQuad(0, 1, 2, 3);
        return builder.finish();
    }
    createSkirtedPlanarMesh(tile) {
        const projection = tile.getProjection();
        const positions = [];
        const uvs = [];
        const skirtHeight = tile.range.xLength() / 20;
        for (let v = 0, i = 0; v < 2; v++) {
            for (let u = 0; u < 2; u++) {
                for (let h = 0; h < 2; h++) {
                    positions.push(projection.getPoint(u, v, h * skirtHeight));
                    uvs[i] = new Point2d(u, 1 - v);
                    i++;
                }
            }
        }
        const builder = new RealityMeshParamsBuilder({
            initialVertexCapacity: 8,
            initialIndexCapacity: 30,
            positionRange: Range3d.createArray(positions),
        });
        for (let i = 0; i < 8; i++)
            builder.addUnquantizedVertex(positions[i], uvs[i]);
        builder.addQuad(0, 2, 4, 6);
        const reorder = [0, 2, 6, 4, 0];
        for (let i = 0; i < 4; i++) {
            const iThis = reorder[i], iNext = reorder[i + 1];
            builder.addQuad(iThis, iNext, iThis + 1, iNext + 1);
        }
        return builder.finish();
    }
    /** @internal override */
    async readMesh(args) {
        const tile = args.tile;
        if (tile.isPlanar)
            return this._wantSkirts ? this.createSkirtedPlanarMesh(tile) : this.createSkirtlessPlanarMesh(tile);
        return this.createGlobeMesh(tile);
    }
    createGlobeMesh(tile) {
        const globeMeshDimension = 10;
        const projection = tile.getProjection();
        const ellipsoidPatch = projection.ellipsoidPatch;
        assert(undefined !== ellipsoidPatch);
        if (!ellipsoidPatch)
            return undefined;
        const bordersSouthPole = tile.quadId.bordersSouthPole(this._tilingScheme);
        const bordersNorthPole = tile.quadId.bordersNorthPole(this._tilingScheme);
        const range = projection.localRange.clone();
        const delta = 1 / (globeMeshDimension - 3);
        const skirtFraction = delta / 2;
        const dimensionM1 = globeMeshDimension - 1;
        const dimensionM2 = globeMeshDimension - 2;
        ellipsoidPatch.ellipsoid.transformRef.clone(scratchEllipsoid.transformRef);
        const skirtPatch = EllipsoidPatch.createCapture(scratchEllipsoid, ellipsoidPatch.longitudeSweep, ellipsoidPatch.latitudeSweep);
        const scaleFactor = Math.max(0.99, 1 - Math.sin(ellipsoidPatch.longitudeSweep.sweepRadians * delta));
        skirtPatch.ellipsoid.transformRef.matrix.scaleColumnsInPlace(scaleFactor, scaleFactor, scaleFactor);
        const pointCount = globeMeshDimension * globeMeshDimension;
        const rowMin = (bordersNorthPole || this._wantSkirts) ? 0 : 1;
        const rowMax = (bordersSouthPole || this._wantSkirts) ? dimensionM1 : dimensionM2;
        const colMin = this._wantSkirts ? 0 : 1;
        const colMax = this._wantSkirts ? dimensionM1 : dimensionM2;
        const indexCount = 6 * (rowMax - rowMin) * (colMax - colMin);
        const builder = new RealityMeshParamsBuilder({
            positionRange: range,
            initialVertexCapacity: pointCount,
            initialIndexCapacity: indexCount,
        });
        for (let iRow = 0, index = 0; iRow < globeMeshDimension; iRow++) {
            for (let iColumn = 0; iColumn < globeMeshDimension; iColumn++, index++) {
                let u = (iColumn ? (Math.min(dimensionM2, iColumn) - 1) : 0) * delta;
                let v = (iRow ? (Math.min(dimensionM2, iRow) - 1) : 0) * delta;
                scratchPoint2d.set(u, 1 - v);
                if (iRow === 0 || iRow === dimensionM1 || iColumn === 0 || iColumn === dimensionM1) {
                    if (bordersSouthPole && iRow === dimensionM1)
                        skirtPatch.ellipsoid.radiansToPoint(0, -Angle.piOver2Radians, scratchPoint);
                    else if (bordersNorthPole && iRow === 0)
                        skirtPatch.ellipsoid.radiansToPoint(0, Angle.piOver2Radians, scratchPoint);
                    else {
                        u += (iColumn === 0) ? -skirtFraction : (iColumn === dimensionM1 ? skirtFraction : 0);
                        v += (iRow === 0) ? -skirtFraction : (iRow === dimensionM1 ? skirtFraction : 0);
                        skirtPatch.uvFractionToPoint(u, v, scratchPoint);
                    }
                }
                else {
                    projection.getPoint(u, v, 0, scratchPoint);
                }
                builder.addUnquantizedVertex(scratchPoint, scratchPoint2d);
            }
        }
        for (let iRow = rowMin; iRow < rowMax; iRow++) {
            for (let iColumn = colMin; iColumn < colMax; iColumn++) {
                const base = iRow * globeMeshDimension + iColumn;
                const top = base + globeMeshDimension;
                builder.addTriangle(base, base + 1, top);
                builder.addTriangle(top, base + 1, top + 1);
            }
        }
        return builder.finish();
    }
    /** Implements [[TerrainMeshProvider.requestMeshData]] to return an empty string because the mesh can be generated
     * purely from information provided by the [[MapTile]].
     */
    async requestMeshData() {
        return "";
    }
}
//# sourceMappingURL=EllipsoidTerrainProvider.js.map