"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.GlobeAnimator = void 0;
const core_geometry_1 = require("@itwin/core-geometry");
const core_common_1 = require("@itwin/core-common");
const ViewGlobalLocation_1 = require("./ViewGlobalLocation");
/** Animates the transition of a [[Viewport]] to view a location on the Earth. The animation traces a flight path from the viewport's current [Frustum]($common) to the destination.
 * The duration of the animation varies based on the distance traversed.
 * @see [[Viewport.animateFlyoverToGlobalLocation]].
 * @public
 * @extensions
 */
class GlobeAnimator {
    _moveFlightToFraction(fraction) {
        const vp = this._viewport;
        const view = vp.view;
        if (!(view.is3d()) || !vp.iModel.isGeoLocated) // This animation only works for 3d views and geolocated models
            return true;
        // If we're done, set the final state directly
        if (fraction >= 1.0) {
            if (vp.view.is3d()) // Need to reset focus as well -- setupViewFromFustum does not set this and it will remain at flight distance.
                vp.view.camera.setFocusDistance(this._afterFocusDistance);
            vp.setupViewFromFrustum(this._afterLanding);
            vp.synchWithView();
            return true;
        }
        // Possibly smooth the takeoff
        if (fraction < this._fixTakeoffFraction && this._fixTakeoffInterpolator !== undefined) {
            this._moveFixToFraction((1.0 / this._fixTakeoffFraction) * fraction, this._fixTakeoffInterpolator);
            return false;
        }
        // Possibly smooth the landing
        if (fraction >= this._fixLandingFraction && fraction < 1.0) {
            if (this._fixLandingInterpolator === undefined) {
                const beforeLanding = vp.getWorldFrustum();
                this._fixLandingInterpolator = core_geometry_1.SmoothTransformBetweenFrusta.create(beforeLanding.points, this._afterLanding.points);
            }
            this._moveFixToFraction((1.0 / (1.0 - this._fixLandingFraction)) * (fraction - this._fixLandingFraction), this._fixLandingInterpolator);
            return false;
        }
        // Set the camera based on a fraction along the flight arc
        const height = core_common_1.Interpolation.Bezier([this._startHeight, this._midHeight, this._endHeight], fraction);
        let targetPoint;
        if (view.globeMode === core_common_1.GlobeMode.Plane)
            targetPoint = this._columbusLine[0].interpolate(fraction, this._columbusLine[1]);
        else
            targetPoint = this._ellipsoidArc.fractionToPoint(fraction);
        view.lookAtGlobalLocation(height, ViewGlobalLocation_1.ViewGlobalLocationConstants.birdPitchAngleRadians, undefined, targetPoint);
        vp.setupFromView();
        return false;
    }
    /** Apply a SmoothTransformBetweenFrusta interpolator to the view based on a fraction. */
    _moveFixToFraction(fract, interpolator) {
        let done = false;
        if (fract >= 1.0) {
            fract = 1.0;
            done = true;
        }
        interpolator.fractionToWorldCorners(Math.max(fract, 0), this._scratchFrustum.points);
        this._viewport.setupViewFromFrustum(this._scratchFrustum);
        return done;
    }
    /** Create an animator to transition to the specified destination.
     * @param viewport The viewport to animate.
     * @param destination The destination to travel to.
     * @returns An animator, or undefined if the viewport's iModel is not geolocated or its view is not 3d.
     */
    static async create(viewport, destination) {
        const view = viewport.view;
        if (!(view.is3d()) || !viewport.iModel.isGeoLocated) // This animation only works for 3d views and geolocated models
            return undefined;
        const endHeight = destination.area !== undefined ? await (0, ViewGlobalLocation_1.areaToEyeHeightFromGcs)(view, destination.area, destination.center.height) : ViewGlobalLocation_1.ViewGlobalLocationConstants.birdHeightAboveEarthInMeters;
        const beforeFrustum = viewport.getWorldFrustum();
        await view.lookAtGlobalLocationFromGcs(endHeight, ViewGlobalLocation_1.ViewGlobalLocationConstants.birdPitchAngleRadians, destination);
        viewport.setupFromView();
        const afterLanding = viewport.getWorldFrustum();
        const afterFocus = view.camera.focusDist;
        viewport.setupViewFromFrustum(beforeFrustum); // revert old frustum
        return new GlobeAnimator(viewport, destination, afterLanding, afterFocus);
    }
    constructor(viewport, destination, afterLanding, afterFocus) {
        this._flightTweens = new core_common_1.Tweens();
        this._columbusLine = [];
        this._flightLength = 0;
        this._fixLandingFraction = 0.9;
        this._scratchFrustum = new core_common_1.Frustum();
        this._viewport = viewport;
        this._endLocation = destination;
        this._afterLanding = afterLanding;
        this._afterFocusDistance = afterFocus;
        const view = viewport.view;
        if (!(view.is3d()) || !viewport.iModel.isGeoLocated) // This animation only works for 3d views and geolocated models
            return;
        // Calculate start height as the height of the current eye above the earth.
        // Calculate end height from the destination area (if specified); otherwise, use a constant value.
        const backgroundMapGeometry = view.displayStyle.getBackgroundMapGeometry();
        if (undefined === backgroundMapGeometry)
            return;
        this._startHeight = (0, ViewGlobalLocation_1.eyeToCartographicOnGlobe)(this._viewport, true).height;
        this._endHeight = destination.area !== undefined ? (0, ViewGlobalLocation_1.areaToEyeHeight)(view, destination.area, destination.center.height) : ViewGlobalLocation_1.ViewGlobalLocationConstants.birdHeightAboveEarthInMeters;
        // Starting cartographic position is the eye projected onto the globe.
        let startCartographic = (0, ViewGlobalLocation_1.eyeToCartographicOnGlobe)(viewport);
        if (startCartographic === undefined) {
            startCartographic = core_common_1.Cartographic.createZero();
        }
        this._startCartographic = startCartographic;
        let maxFlightDuration;
        if (view.globeMode === core_common_1.GlobeMode.Plane) {
            // Calculate a line segment going from the starting cartographic coordinate to the ending cartographic coordinate
            this._columbusLine.push(view.cartographicToRoot(startCartographic));
            this._columbusLine.push(view.cartographicToRoot(this._endLocation.center));
            this._flightLength = this._columbusLine[0].distance(this._columbusLine[1]);
            // Set a shorter flight duration in Plane mode
            maxFlightDuration = 7000.0;
        }
        else {
            // Calculate a flight arc from the ellipsoid of the Earth and the starting and ending cartographic coordinates.
            const earthEllipsoid = backgroundMapGeometry.getEarthEllipsoid();
            this._ellipsoidArc = earthEllipsoid.radiansPairToGreatArc(this._startCartographic.longitude, this._startCartographic.latitude, this._endLocation.center.longitude, this._endLocation.center.latitude);
            if (this._ellipsoidArc !== undefined)
                this._flightLength = this._ellipsoidArc.curveLength();
            // Set a longer flight duration in 3D mode
            maxFlightDuration = 13000.0;
        }
        if (core_geometry_1.Geometry.isSmallMetricDistance(this._flightLength))
            return;
        // The peak of the flight varies based on total distance to travel. The larger the distance, the higher the peak of the flight will be.
        this._midHeight = (0, ViewGlobalLocation_1.metersToRange)(this._flightLength, ViewGlobalLocation_1.ViewGlobalLocationConstants.birdHeightAboveEarthInMeters, ViewGlobalLocation_1.ViewGlobalLocationConstants.satelliteHeightAboveEarthInMeters * 4, ViewGlobalLocation_1.ViewGlobalLocationConstants.largestEarthArc);
        // We will "fix" the initial frustum so it smoothly transitions to some point along the travel arc depending on the starting height.
        // Alternatively, if the distance to travel is small enough, we will _only_ do a frustum transition to the destination location - ignoring the flight arc.
        const beforeTakeoff = viewport.getWorldFrustum();
        if (view.globeMode === core_common_1.GlobeMode.Plane) {
            /// Do not "fix" the take-off for plane mode; SmoothTransformBetweenFrusta can behave wrongly.
            // However, if within driving distance, still use SmoothTransformBetweenFrusta to navigate there without flight.
            this._fixTakeoffFraction = this._flightLength <= ViewGlobalLocation_1.ViewGlobalLocationConstants.maximumDistanceToDrive ? 1.0 : 0.0;
        }
        else {
            this._fixTakeoffFraction = this._flightLength <= ViewGlobalLocation_1.ViewGlobalLocationConstants.maximumDistanceToDrive ? 1.0 : (0, ViewGlobalLocation_1.metersToRange)(this._startHeight, 0.1, 0.4, ViewGlobalLocation_1.ViewGlobalLocationConstants.birdHeightAboveEarthInMeters);
        }
        if (this._fixTakeoffFraction > 0.0) {
            this._moveFlightToFraction(this._fixTakeoffFraction);
            const afterTakeoff = viewport.getWorldFrustum();
            this._fixTakeoffInterpolator = core_geometry_1.SmoothTransformBetweenFrusta.create(beforeTakeoff.points, afterTakeoff.points);
        }
        // The duration of the animation will increase the larger the distance to travel.
        const flightDurationInMilliseconds = (0, ViewGlobalLocation_1.metersToRange)(this._flightLength, 1000, maxFlightDuration, ViewGlobalLocation_1.ViewGlobalLocationConstants.largestEarthArc);
        // Specify the tweening behavior for this animation.
        this._flightTweens.create({ fraction: 0.0 }, {
            to: { fraction: 1.0 },
            duration: flightDurationInMilliseconds,
            easing: core_common_1.Easing.Cubic.InOut,
            start: true,
            onUpdate: (obj) => this._moveFlightToFraction(obj.fraction),
        });
    }
    /** @internal */
    animate() {
        if (this._flightLength <= 0) {
            this._moveFlightToFraction(1.0); // Skip to final frustum
            return true;
        }
        return !this._flightTweens.update();
    }
    /** @internal */
    interrupt() {
        this._moveFlightToFraction(1.0); // Skip to final frustum
    }
}
exports.GlobeAnimator = GlobeAnimator;
//# sourceMappingURL=GlobeAnimator.js.map