UNPKG

@itwin/core-frontend

Version:
189 lines • 11.2 kB
"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 { _flightTweens = new core_common_1.Tweens(); _viewport; _startCartographic; _ellipsoidArc; _columbusLine = []; _flightLength = 0; _endLocation; _endHeight; _midHeight; _startHeight; _fixTakeoffInterpolator; _fixTakeoffFraction; _fixLandingInterpolator; _afterLanding; _afterFocusDistance; _fixLandingFraction = 0.9; _scratchFrustum = new core_common_1.Frustum(); _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._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