From 2a5e295ea8b06068fa7430070bee730aa5015890 Mon Sep 17 00:00:00 2001 From: Gerald Choqueux Date: Wed, 3 May 2017 18:00:54 +0200 Subject: [PATCH] refacto (GlobeControls) : move functions of control from ApiGlobe to GlobeControl --- package.json | 2 +- src/Core/AnimationPlayer.js | 31 +- src/Core/Prefab/GlobeView.js | 5 + .../Interfaces/ApiInterface/ApiGlobe.js | 431 +---------------- src/Main.js | 3 +- src/Process/GlobeTileProcessing.js | 2 +- src/Renderer/ThreeExtended/GlobeControls.js | 450 +++++++++++++++--- utils/debug/Debug.js | 10 +- 8 files changed, 441 insertions(+), 493 deletions(-) diff --git a/package.json b/package.json index 0e07b3f330..bc4e2b2e4c 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "lib/Main.js", "scripts": { "lint": "eslint \"src/**/*.js\" \"test/**/*.js\" \"examples/**/*.js\"", - "doc": "jsdoc src/Core/Scheduler/Interfaces/ApiInterface/*", + "doc": "jsdoc src/Core/Scheduler/Interfaces/ApiInterface/* src/Renderer/ThreeExtended/GlobeControls.js", "doclint": "npm run doc -- -t templates/silent", "test": "npm run lint && npm run build && npm run doclint", "build": "webpack -p", diff --git a/src/Core/AnimationPlayer.js b/src/Core/AnimationPlayer.js index f574dc2a1a..94891e33e6 100644 --- a/src/Core/AnimationPlayer.js +++ b/src/Core/AnimationPlayer.js @@ -1,3 +1,5 @@ +import * as THREE from 'three'; + const FRAMERATE = 60; const FRAME_DURATION = 1000 / FRAMERATE; // if is true console.log are enabled to sniff animation'state @@ -54,6 +56,16 @@ const resetTimer = function resetTimer(player) { // finish animation and re-init parameter const finishAnimation = function finishAnimation(player) { resetTimer(player); + if (player.isEnded()) { + player.dispatchEvent({ + type: 'animation-ended', + animation: player.animation, + }); + } + player.dispatchEvent({ + type: 'animation-stopped', + animation: player.animation, + }); player.animation = null; if (player.resolve) { player.resolve(); @@ -67,10 +79,6 @@ const setPlayerState = function setPlayerState(player, state) { _DEBUG(debugMsg[state], player.animation); }; -const frameEvent = new CustomEvent('frameAnimation'); -const stopEvent = new CustomEvent('stopAnimation'); -const endEvent = new CustomEvent('endAnimation'); - /** * AnimationPlayer * It can play, pause or stop Animation or AnimationExpression (See below). @@ -80,9 +88,9 @@ const endEvent = new CustomEvent('endAnimation'); * - when Animation is stopped * - when Animation is ending */ -class AnimationPlayer { - constructor(dom) { - this.dom = dom; +class AnimationPlayer extends THREE.EventDispatcher { + constructor() { + super(); this.id = null; this.keyframe = 0; this.animation = null; @@ -115,6 +123,9 @@ class AnimationPlayer { */ play(animation) { this.animation = animation; + this.dispatchEvent({ + type: 'animation-started', + animation }); setPlayerState(this, PLAYER_STATE.PLAY); resetTimer(this); this.id = setInterval(this.frame.bind(this), FRAME_DURATION); @@ -145,7 +156,6 @@ class AnimationPlayer { stop() { setPlayerState(this, PLAYER_STATE.STOP); finishAnimation(this); - this.dom.dispatchEvent(stopEvent); // needed to return promise to wait sync return Promise.resolve(); } @@ -159,12 +169,13 @@ class AnimationPlayer { this.animation.animate(this.keyframe); } this.keyframe++; - this.dom.dispatchEvent(frameEvent); + this.dispatchEvent({ + type: 'animation-frame', + }); } else { setPlayerState(this, PLAYER_STATE.END); finishAnimation(this); - this.dom.dispatchEvent(endEvent); } } } diff --git a/src/Core/Prefab/GlobeView.js b/src/Core/Prefab/GlobeView.js index 017b0d79d9..bde7b113d9 100644 --- a/src/Core/Prefab/GlobeView.js +++ b/src/Core/Prefab/GlobeView.js @@ -106,6 +106,7 @@ function GlobeView(viewerDiv, coordCarto) { this.camera.camera3D, positionTargetCamera.as('EPSG:4978').xyz(), engine.renderer.domElement, + viewerDiv, engine, size, 'EPSG:4978', @@ -144,6 +145,10 @@ function GlobeView(viewerDiv, coordCarto) { this.mainLoop.addEventListener('command-queue-empty', () => { viewerDiv.dispatchEvent(new CustomEvent('globe-built')); }); + + window.addEventListener('resize', () => { + this.controls.updateCamera(this.camera, this.viewerDiv.clientWidth, this.viewerDiv.clientHeight); + }, false); } GlobeView.prototype = Object.create(View.prototype); diff --git a/src/Core/Scheduler/Interfaces/ApiInterface/ApiGlobe.js b/src/Core/Scheduler/Interfaces/ApiInterface/ApiGlobe.js index 9dd67652ac..b3e2457ee8 100644 --- a/src/Core/Scheduler/Interfaces/ApiInterface/ApiGlobe.js +++ b/src/Core/Scheduler/Interfaces/ApiInterface/ApiGlobe.js @@ -6,19 +6,13 @@ import CustomEvent from 'custom-event'; import { ImageryLayers } from '../../../Layer/Layer'; -import { C } from '../../../Geographic/Coordinates'; import loadGpx from '../../Providers/GpxUtils'; import Fetcher from '../../Providers/Fetcher'; -import { computeTileZoomFromDistanceCamera, computeDistanceCameraFromTileZoom } from '../../../../Process/GlobeTileProcessing'; import CoordStars from '../../../Geographic/CoordStars'; import GlobeView from '../../../Prefab/GlobeView'; -var sceneIsLoaded = false; export const INITIALIZED_EVENT = 'initialized'; -var eventRange = new CustomEvent('rangeChanged'); -var eventOrientation = new CustomEvent('orientationchanged'); -var eventPan = new CustomEvent('panchanged'); var eventLayerAdded = new CustomEvent('layeradded'); var eventLayerRemoved = new CustomEvent('layerremoved'); var eventLayerChanged = new CustomEvent('layerchanged'); @@ -26,17 +20,12 @@ var eventLayerChangedVisible = new CustomEvent('layerchanged:visible'); var eventLayerChangedOpacity = new CustomEvent('layerchanged:opacity'); var eventLayerChangedIndex = new CustomEvent('layerchanged:index'); -var enableAnimation = false; - -const defer = function defer() { - const deferedPromise = {}; - deferedPromise.promise = new Promise((resolve, reject) => { - deferedPromise.resolve = resolve; - deferedPromise.reject = reject; - }); - return deferedPromise; -}; +/** + * Api + * @deprecated for the release + * @constructor + */ function ApiGlobe() { this.scene = null; this.viewerDiv = null; @@ -99,7 +88,6 @@ ApiGlobe.prototype.addGeometryLayer = function addGeometryLayer(layer, parentLay /** * This function adds an imagery layer to the scene. The layer id must be unique. * The protocol rules wich parameters are then needed for the function. - * @constructor * @param {Layer} Layer */ ApiGlobe.prototype.addImageryLayer = function addImageryLayer(layer) { @@ -113,16 +101,13 @@ ApiGlobe.prototype.addImageryLayer = function addImageryLayer(layer) { layer.sequence = colorLayerCount - 1; this.globeview.notifyChange(1, true); - this.setSceneLoaded().then(() => { - this.viewerDiv.dispatchEvent(eventLayerAdded); - }); + this.viewerDiv.dispatchEvent(eventLayerAdded); return layer; }; /** * This function adds an imagery layer to the scene using a JSON file. The layer id must be unique. The protocol rules wich parameters are then needed for the function. - * @constructor * @param {Layer} layer. * @return {layer} The Layer. */ @@ -133,7 +118,6 @@ ApiGlobe.prototype.addImageryLayerFromJSON = function addImageryLayerFromJSON(ur /** * This function adds an imagery layer to the scene using an array of JSON files. The layer id must be unique. The protocol rules wich parameters are then needed for the function. - * @constructor * @param {Layers} array - An array of JSON files. * @return {layer} The Layers. */ @@ -153,7 +137,6 @@ ApiGlobe.prototype.addImageryLayersFromJSONArray = function addImageryLayersFrom * with best resolution is used (or the first one is resolution are identical). * The layer id must be unique amongst all layers already inserted. * The protocol rules which parameters are then needed for the function. - * @constructor * @param {Layer} layer */ @@ -168,10 +151,7 @@ ApiGlobe.prototype.addElevationLayer = function addElevationLayer(layer) { layer.frozen = false; this.globeview.notifyChange(1, true); - this.setSceneLoaded().then(() => { - this.viewerDiv.dispatchEvent(eventLayerAdded); - }); - + this.viewerDiv.dispatchEvent(eventLayerAdded); return layer; }; @@ -182,7 +162,6 @@ ApiGlobe.prototype.addElevationLayer = function addElevationLayer(layer) { * with best resolution is used (or the first one is resolution are identical). * The layer id must be unique amongst all layers already inserted. * The protocol rules which parameters are then needed for the function. - * @constructor * @param {Layers} array - An array of JSON files. * @return {layer} The Layers. */ @@ -198,7 +177,6 @@ ApiGlobe.prototype.addElevationLayersFromJSON = function addElevationLayersFromJ * with best resolution is used (or the first one is resolution are identical). * The layer id must be unique amongst all layers already inserted. * The protocol rules which parameters are then needed for the function. - * @constructor * @param {Layer} layer. * @return {layer} The Layers. */ @@ -245,7 +223,6 @@ ApiGlobe.prototype.moveLayerDown = function moveLayerDown(layerId) { /** * Moves a specific layer to a specific index in the layer list. This function has no effect if the layer is moved to its current index. - * @constructor * @param {string} layerId The layer's idendifiant * @param {number} newIndex The new index */ @@ -298,7 +275,6 @@ ApiGlobe.prototype.removeImageryLayer = function removeImageryLayer(id) { /** * Gets the minimum zoom level of the chosen layer. * - * @constructor * @param {index} index - The index of the layer. * @return {number} The min of the level. */ @@ -321,7 +297,6 @@ ApiGlobe.prototype.getMinZoomLevel = function getMinZoomLevel(index) { /** * Gets the maximun zoom level of the chosen layer. * - * @constructor * @param {index} index - The index of the layer. * @return {number} The max of the level. */ @@ -343,7 +318,6 @@ ApiGlobe.prototype.getMaxZoomLevel = function getMaxZoomLevel(index) { /** * Return the list of all layers in the scene in the order of how they are stacked on top of each other. - * @constructor * @return {layer} The Layers. */ ApiGlobe.prototype.getImageryLayers = function getImageryLayers() { @@ -354,31 +328,20 @@ ApiGlobe.prototype.getImageryLayers = function getImageryLayers() { * Creates the scene (the globe of iTowns). * The first parameter is the coordinates on wich the globe will be centered at the initialization. * The second one is the HTML div in wich the scene will be created. - * @constructor * @param {Coords} coords. * @params {Div} string. */ ApiGlobe.prototype.createSceneGlobe = function createSceneGlobe(globeLayerId, coordCarto, viewerDiv) { this.viewerDiv = viewerDiv; - this.sceneLoadedDeferred = defer(); - viewerDiv.addEventListener('globe-built', () => { - if (!sceneIsLoaded) { - sceneIsLoaded = true; - this.sceneLoadedDeferred.resolve(); - this.sceneLoadedDeferred = defer(); - } + viewerDiv.addEventListener('globe-built', function fn() { + viewerDiv.removeEventListener('globe-built', fn); + viewerDiv.dispatchEvent(new CustomEvent(INITIALIZED_EVENT)); }, false); this.globeview = new GlobeView(viewerDiv, coordCarto); - this.setSceneLoaded().then(() => { - this.globeview.controls.updateCameraTransformation(); - this.globeview.notifyChange(0, true); - this.viewerDiv.dispatchEvent(new CustomEvent(INITIALIZED_EVENT)); - }); - return this.globeview; }; @@ -415,7 +378,6 @@ ApiGlobe.prototype.setLightingPos = function setLightingPos(pos) { /** * Sets the visibility of a layer. If the layer is not visible in the scene, this function will no effect until the camera looks at the layer. - * @constructor * @param {layer} a layer. * @params {visible} boolean. */ @@ -439,11 +401,9 @@ ApiGlobe.prototype.setLayerVisibility = function setLayerVisibility(layer, visib /** * Sets the opacity of a layer. If the layer is not visible in the scene, this function will no effect until the layer becomes visible. - * @constructor * @param {layer} a layer. * @params {visible} boolean. */ - ApiGlobe.prototype.setLayerOpacity = function setLayerOpacity(layer, opacity) { layer.opacity = opacity; this.globeview.notifyChange(0, true); @@ -452,380 +412,11 @@ ApiGlobe.prototype.setLayerOpacity = function setLayerOpacity(layer, opacity) { this.viewerDiv.dispatchEvent(eventLayerChangedOpacity); }; -/** - * Returns the orientation angles of the current camera, in degrees. - * - * @constructor - */ -ApiGlobe.prototype.getCameraOrientation = function getCameraOrientation() { - var tiltCam = this.globeview.controls.getTilt(); - var headingCam = this.globeview.controls.getHeading(); - return [tiltCam, headingCam]; -}; - -/** - * Returns the camera location projected on the ground in lat,lon. - * - * @constructor - * @return {Position} position - */ - -ApiGlobe.prototype.getCameraLocation = function getCameraLocation() { - return C.fromXYZ('EPSG:4978', this.globeview.camera.camera3D.position).as('EPSG:4326'); -}; - -/** - * Retuns the coordinates of the central point on screen. - * - * @constructor - * @return {Position} position - */ - -ApiGlobe.prototype.getCameraTargetGeoPosition = function getCameraTargetGeoPosition() { - return C.fromXYZ('EPSG:4978', this.globeview.controls.getCameraTargetPosition()).as('EPSG:4326'); -}; - -/** - * Sets orientation angles of the current camera, in degrees. - * - * @constructor - * @param {object} orientation The angle of the rotation in degrees - * @param {boolean} isAnimated Indicates if animated - * @return {Promise} { description_of_the_return_value } - */ -ApiGlobe.prototype.setCameraOrientation = function setCameraOrientation(orientation, isAnimated) { - return this.globeview.controls.setOrbitalPosition(undefined, orientation.heading, orientation.tilt, isAnimated).then(() => { - this.viewerDiv.dispatchEvent(eventOrientation); - }); -}; - -/** - * Pick a position on the globe at the given position. - * @constructor - * @param {number | MouseEvent} x|event - The x-position inside the Globe element or a mouse event. - * @param {number | undefined} y - The y-position inside the Globe element. - * @return {Position} position - */ -ApiGlobe.prototype.pickPosition = function pickPosition(mouse, y) { - var screenCoords = { - x: mouse.clientX || mouse, - y: mouse.clientY || y, - }; - - var pickedPosition = this.scene.getPickPosition(screenCoords); - - this.scene.renderScene3D(); - - if (!pickedPosition) { - return; - } - - return C.fromXYZ('EPSG:4978', pickedPosition).as('EPSG:4326'); -}; - -/** - * Returns the tilt in degrees. - * - * @constructor - * @return {Angle} number - The angle of the rotation in degrees. - */ - -ApiGlobe.prototype.getTilt = function getTilt() { - var tiltCam = this.globeview.controls.getTilt(); - return tiltCam; -}; - -/** - * Returns the heading in degrees. - * - * @constructor - * @return {Angle} number - The angle of the rotation in degrees. - */ - -ApiGlobe.prototype.getHeading = function getHeading() { - var headingCam = this.globeview.controls.getHeading(); - return headingCam; -}; - -/** - * Returns the "range": the distance in meters between the camera and the current central point on the screen. - * - * @constructor - * @return {number} number - */ - -ApiGlobe.prototype.getRange = function getRange() { - return this.globeview.controls.getRange(); -}; - -ApiGlobe.prototype.getRangeFromEllipsoid = function getRangeFromEllipsoid() { - // TODO: error is distance is big with ellipsoid.intersection(ray) because d < 0 - var controlCam = this.globeview.controls; - var ray = controlCam.getRay(); - var intersection = this.ellipsoid.intersection(ray); - var camPosition = this.globeview.camera.position(); - var range = intersection.distanceTo(camPosition); - - return range; -}; - -/** - * Sets the animation enabled. - * @constructor - * @param {boolean} enable The enable - */ -ApiGlobe.prototype.setAnimationEnabled = function setAnimationEnabled(enable) { - enableAnimation = enable; -}; - -/** - * Determines if animation enabled. - * - * @return {boolean} True if animation enabled, False otherwise. - */ -ApiGlobe.prototype.isAnimationEnabled = function isAnimationEnabled() { - return enableAnimation; -}; - -/** - * Change the tilt. - * - * @constructor - * @param {Angle} Number - The angle. - * @param {boolean} isAnimated Indicates if animated - * @return {Promise} - */ -ApiGlobe.prototype.setTilt = function setTilt(tilt, isAnimated) { - isAnimated = isAnimated || this.isAnimationEnabled(); - eventOrientation.oldTilt = this.getTilt(); - return this.globeview.controls.setTilt(tilt, isAnimated).then(() => { - this.viewerDiv.dispatchEvent(eventOrientation); - this.globeview.notifyChange(1, true); - }); -}; - -/** - * Change the heading. - * - * @constructor - * @param {Angle} Number - The angle. - * @param {boolean} isAnimated Indicates if animated - * @return {Promise} - */ -ApiGlobe.prototype.setHeading = function setHeading(heading, isAnimated) { - isAnimated = isAnimated || this.isAnimationEnabled(); - eventOrientation.oldHeading = this.getHeading(); - return this.globeview.controls.setHeading(heading, isAnimated).then(() => { - this.viewerDiv.dispatchEvent(eventOrientation); - this.globeview.notifyChange(1, true); - }); -}; - -/** - * Resets camera tilt -> sets the tilt to 0°. - * @constructor - * @param {boolean} isAnimated Indicates if animated - * @return {Promise} - */ -ApiGlobe.prototype.resetTilt = function resetTilt(isAnimated) { - isAnimated = isAnimated || this.isAnimationEnabled(); - return this.globeview.controls.setTilt(0, isAnimated); -}; - -/** - * Resets camera heading -> sets the heading to 0°. - * @constructor - * @param {boolean} isAnimated Indicates if animated - * @return {Promise} - */ -ApiGlobe.prototype.resetHeading = function resetHeading(isAnimated) { - isAnimated = isAnimated || this.isAnimationEnabled(); - return this.globeview.controls.setHeading(0, isAnimated); -}; - -/** - * Returns the distance in meter between two geographic positions. - * - * @constructor - * @param {Position} First - Position. - * @param {Position} Second - Position. - * @return {Number} distance - */ - -ApiGlobe.prototype.setSceneLoaded = function setSceneLoaded() { - sceneIsLoaded = false; - return this.sceneLoadedDeferred.promise; -}; - -/** - * Changes the center of the scene on screen to the specified coordinates. - * - * @param {Object} coordinates - The globe coordinates in EPSG_4326 projection to aim to - * @param {number} coordinates.latitude - * @param {number} coordinates.longitude - * @param {number} coordinates.range - * @param {boolean} isAnimated - if the movement should be animated - * @return {Promise} A promise that resolves when the next 'globe-loaded' event fires. - */ -ApiGlobe.prototype.setCameraTargetGeoPosition = function setCameraTargetGeoPosition(coordinates, isAnimated) { - isAnimated = isAnimated || this.isAnimationEnabled(); - const position3D = coordinates.as('EPSG:4978').xyz(); - position3D.range = coordinates.range; - return this.globeview.controls.setCameraTargetPosition(position3D, isAnimated).then(() => { - this.globeview.notifyChange(1, true); - return this.setSceneLoaded().then(() => { - this.globeview.controls.updateCameraTransformation(); - }); - }); -}; - -/** - * Changes the center of the scene on screen to the specified coordinates. - * This function allows to change the central position, the zoom level, the range, the scale and the camera orientation at the same time. - * The level has to be between the [getMinZoomLevel(), getMaxZoomLevel()]. - * The zoom level and the scale can't be set at the same time. - * - * @param {Position} position - * @param {number} position.longitude Coordinate longitude WGS84 in degree - * @param {number} position.latitude Coordinate latitude WGS84 in degree - * @param {number} [position.tilt] Camera tilt in degree - * @param {number} [position.heading] Camera heading in degree - * @param {number} [position.range] The camera distance to the target center - * @param {number} [position.level] level, ignored if range is set - * @param {number} [position.scale] scale, ignored if the zoom level or range is set. For a scale of 1/500 it is necessary to write 0,002. - * @param {boolean} isAnimated Indicates if animated - * @return {Promise} - */ -ApiGlobe.prototype.setCameraTargetGeoPositionAdvanced = function setCameraTargetGeoPositionAdvanced(position, isAnimated) { - isAnimated = isAnimated || this.isAnimationEnabled(); - if (position.level) { - position.range = computeDistanceCameraFromTileZoom(position.level); - } else if (position.scale) { - position.range = this.getRangeFromScale(position.scale); - } - return this.setCameraTargetGeoPosition(position, isAnimated).then(() => { - position.range = position.range || this.getRange(); - position.tilt = position.tilt || this.getTilt(); - position.heading = position.heading || this.getHeading(); - return this.globeview.controls.setOrbitalPosition(position.range, position.heading, position.tilt, isAnimated).then(() => { - this.globeview.notifyChange(1); - return this.setSceneLoaded().then(() => { - this.globeview.controls.updateCameraTransformation(); - }); - }); - }); -}; - -/** - * Sets the "range": the distance in meters between the camera and the current central point on the screen. - * - * @constructor - * @param {Number} pRange - The camera altitude. - * @param {boolean} isAnimated Indicates if animated - * @return {Promise} - */ -ApiGlobe.prototype.setRange = function setRange(pRange, isAnimated) { - isAnimated = isAnimated || this.isAnimationEnabled(); - eventRange.oldRange = this.getRange(); - - return this.globeview.controls.setRange(pRange, isAnimated).then(() => { - this.globeview.notifyChange(1); - return this.setSceneLoaded().then(() => { - this.globeview.controls.updateCameraTransformation(); - this.viewerDiv.dispatchEvent(eventRange); - }); - }); -}; - -/** - * Displaces the central point to a specific amount of pixels from its current position. - * The view flies to the desired coordinate, i.e.is not teleported instantly. Note : The results can be strange in some cases, if ever possible, when e.g.the camera looks horizontally or if the displaced center would not pick the ground once displaced. - * @constructor - * @param {vector} pVector The vector - */ -ApiGlobe.prototype.pan = function pan(pVector) { - this.globeview.controls.pan(pVector.x, pVector.y); - this.globeview.notifyChange(1); - this.setSceneLoaded().then(() => { - this.globeview.controls.updateCameraTransformation(); - this.viewerDiv.dispatchEvent(eventPan); - }); -}; - -/** - * Returns the actual zoom level. The level will always be between the [getMinZoomLevel(), getMaxZoomLevel()]. - * @constructor - * @return {number} The zoom level. - */ -ApiGlobe.prototype.getZoomLevel = function getZoomLevel() { - return computeTileZoomFromDistanceCamera(this.globeview.camera, this.getRange()); -}; - -/** - * Gets the current zoom level, which is an index in the logical scales predefined for the application. - * The higher the level, the closer to the ground. - * The level is always in the [getMinZoomLevel(), getMaxZoomLevel()] range. - * @constructor - * @param {number} zoom The zoom - * @param {boolean} isAnimated Indicates if animated - * @return {Promise} - */ -ApiGlobe.prototype.setZoomLevel = function setZoomLevel(zoom, isAnimated) { - const range = computeDistanceCameraFromTileZoom(zoom); - return this.setRange(range, isAnimated); -}; - -/** - * Return the current zoom scale at the central point of the view. - * This function compute the scale of a map - * @constructor - * @param {number} pitch Screen pitch, in millimeters ; 0.28 by default - * @return {number} The zoom scale. - */ -ApiGlobe.prototype.getZoomScale = function getZoomScale(pitch) { - // TODO: Why error div size height in Chrome? - // Screen pitch, in millimeters - pitch = (pitch || 0.28) / 1000; - const camera = this.globeview.camera; - const FOV = camera.FOV / 180 * Math.PI * 0.5; - // projection one unit on screen - const unitProjection = camera.height / (2 * this.getRange() * Math.tan(FOV)); - return pitch * unitProjection; -}; - -/** - * Changes the zoom level of the central point of screen so that screen acts as a map with a specified scale. - * The view flies to the desired zoom scale; - * @constructor - * @param {number} zoomScale The zoom scale - * @param {number} pitch The pitch - * @param {boolean} isAnimated Indicates if animated - * @return {Promise} - */ -ApiGlobe.prototype.setZoomScale = function setZoomScale(zoomScale, pitch, isAnimated) { - const range = this.getRangeFromScale(zoomScale); - return this.setRange(range, isAnimated); -}; - -ApiGlobe.prototype.getRangeFromScale = function getRangeFromScale(zoomScale, pitch) { - // Screen pitch, in millimeters - pitch = (pitch || 0.28) / 1000; - - const camera = this.globeview.camera; - const alpha = camera.FOV / 180 * Math.PI * 0.5; - // Invert one unit projection (see getZoomScale) - const range = pitch * camera.height / (zoomScale * 2 * Math.tan(alpha)); - - return range; -}; - /** * Some event return the old value before the change. The available events are centerchanged, zoomchanged, orientationchanged, layerchanged:opacity, layerchanged:visible, layerchanged:ipr and layerchanged:index. - * @constructor * @param {string} Eventname - The name of the event. * @param {callback} Callback - The callback that is called when the event is heard. */ - ApiGlobe.prototype.addEventListener = function addEventListenerProto(eventname, callback) { if (eventname == 'layerchanged') { this.viewerDiv.addEventListener('layerchanged', callback, false); @@ -847,7 +438,6 @@ ApiGlobe.prototype.callbackLayerChanged = function callbackLayerChanged() { /** * Remove the event of events listener from the event target. - * @constructor * @param {string} Eventname - The name of the event. * @param {callback} Callback - The callback that is called when the event is heard. */ @@ -887,7 +477,6 @@ ApiGlobe.prototype.getLayersAttribution = function getLayersAttribution() { * The type can be 'color', 'elevation' and 'geometry'. If the type is not specified, the function return all the layers. * @param {type} Type - The type of the layers wanted. */ - ApiGlobe.prototype.getLayers = function getLayers(type) { if (type === undefined) { return this.globeview.getLayers(); diff --git a/src/Main.js b/src/Main.js index fc80678cf1..a1e8d2af5d 100644 --- a/src/Main.js +++ b/src/Main.js @@ -2,6 +2,7 @@ import * as THREE from 'three'; import proj4 from 'proj4'; import ApiGlobe, { INITIALIZED_EVENT } from './Core/Scheduler/Interfaces/ApiInterface/ApiGlobe'; +import { CONTROL_EVENTS } from './Renderer/ThreeExtended/GlobeControls'; import View from './Core/View'; import GlobeView from './Core/Prefab/GlobeView'; import PlanarView from './Core/Prefab/PlanarView'; @@ -9,7 +10,7 @@ import Extent from './Core/Geographic/Extent'; import Coordinates from './Core/Geographic/Coordinates'; // Then exported as non-default here. -export { ApiGlobe, INITIALIZED_EVENT }; +export { ApiGlobe, INITIALIZED_EVENT, CONTROL_EVENTS }; export { View }; export { GlobeView }; export { PlanarView }; diff --git a/src/Process/GlobeTileProcessing.js b/src/Process/GlobeTileProcessing.js index 7c950d82c7..b522caa4e2 100644 --- a/src/Process/GlobeTileProcessing.js +++ b/src/Process/GlobeTileProcessing.js @@ -127,7 +127,7 @@ export function globeSchemeTileWMTS(type) { return schemeT; } -export function computeTileZoomFromDistanceCamera(camera, distance) { +export function computeTileZoomFromDistanceCamera(distance) { const sizeEllipsoid = ellipsoidSizes().x; const preSinus = SIZE_TEXTURE_TILE * (SSE_SUBDIVISION_THRESHOLD * 0.5) / preSSE / sizeEllipsoid; diff --git a/src/Renderer/ThreeExtended/GlobeControls.js b/src/Renderer/ThreeExtended/GlobeControls.js index 67f4609f88..2a8f9d23fd 100644 --- a/src/Renderer/ThreeExtended/GlobeControls.js +++ b/src/Renderer/ThreeExtended/GlobeControls.js @@ -10,6 +10,7 @@ import * as THREE from 'three'; import Sphere from '../../Core/Math/Sphere'; import AnimationPlayer, { Animation, AnimatedExpression } from '../../Core/AnimationPlayer'; import Coordinates, { C } from '../../Core/Geographic/Coordinates'; +import { computeTileZoomFromDistanceCamera, computeDistanceCameraFromTileZoom } from '../../Process/GlobeTileProcessing'; // TODO: // Recast touch for globe @@ -107,6 +108,7 @@ const rotateStart = new THREE.Vector2(); const rotateEnd = new THREE.Vector2(); const rotateDelta = new THREE.Vector2(); const spherical = new THREE.Spherical(1.0, 0.01, Math.PI * 0.5); +const snapShotSpherical = new THREE.Spherical(1.0, 0.01, Math.PI * 0.5); const sphericalDelta = new THREE.Spherical(1.0, 0, 0); const sphericalTo = new THREE.Spherical(); const orbit = { @@ -145,6 +147,10 @@ const ctrl = { ctrl.qDelta.presiceSlerp = presiceSlerp; quatGlobe.presiceSlerp = presiceSlerp; +// Animation + +let enableAnimation = true; + // Animation player var player = null; // Save 2 last rotation globe for damping @@ -174,10 +180,10 @@ var animationOrbitExpression = function animationOrbitExpression(root, progress) }; // Animations -const animationDampingMove = new AnimatedExpression({ duration: 120, root: ctrl, expression: dampingMoveAnimatedExpression, name: 'Damping Move' }); +const animationDampingMove = new AnimatedExpression({ duration: 120, root: ctrl, expression: dampingMoveAnimatedExpression, name: 'damping-move' }); const animationZoomCenter = new AnimatedExpression({ duration: 45, root: ctrl, expression: zoomCenterAnimatedExpression, name: 'Zoom Center' }); const animationOrbit = new AnimatedExpression({ duration: 30, root: orbit, expression: animationOrbitExpression, name: 'set Orbit' }); -const dampingOrbitalMvt = new Animation({ duration: 60, name: 'orbit damping' }); +const dampingOrbitalMvt = new Animation({ duration: 60, name: 'damping-orbit' }); // Replace matrix float by matrix double cameraTargetOnGlobe.matrixWorld.elements = new Float64Array(16); @@ -224,9 +230,22 @@ if (enableTargetHelper) { var _handlerMouseMove; var _handlerMouseUp; +let getPickingPosition; + // Pseudo collision const radiusCollision = 50; + +// Event +let enableEventPositionChanged = true; + +export const CONTROL_EVENTS = { + PAN_CHANGED: 'pan-changed', + ORIENTATION_CHANGED: 'orientation-changed', + RANGE_CHANGED: 'range-changed', + CAMERA_TARGET_CHANGED: 'camera-target-changed', +}; + // SnapCamera saves transformation's camera // It's use to globe move function SnapCamera(camera) { @@ -270,18 +289,34 @@ function SnapCamera(camera) { var snapShotCamera = null; -// /////////////////////// +function defer() { + const deferedPromise = {}; + deferedPromise.promise = new Promise((resolve, reject) => { + deferedPromise.resolve = resolve; + deferedPromise.reject = reject; + }); + return deferedPromise; +} /* globals document,window */ -function GlobeControls(camera, target, domElement, engine, radius, referenceCrs, getPickingPosition) { - player = new AnimationPlayer(domElement); +/** @class GlobeControls */ +function GlobeControls(camera, target, domElement, viewerDiv, engine, radius, referenceCrs, getPickingPosition) { + player = new AnimationPlayer(); this.camera = camera; this.referenceCrs = referenceCrs; snapShotCamera = new SnapCamera(camera); this.domElement = (domElement !== undefined) ? domElement : document; + this.waitSceneLoaded = function waitSceneLoaded() { + const deferedPromise = defer(); + viewerDiv.addEventListener('globe-built', () => { + deferedPromise.resolve(); + }); + return deferedPromise.promise; + }; + // Set to false to disable this control this.enabled = true; @@ -343,7 +378,7 @@ function GlobeControls(camera, target, domElement, engine, radius, referenceCrs, spherical.radius = tSphere.radius; sizeRendering.set(engine.width, engine.height); - + sizeRendering.FOV = camera.fov; // Note A // TODO: test before remove test code // so camera.up is the orbit axis @@ -365,13 +400,14 @@ function GlobeControls(camera, target, domElement, engine, radius, referenceCrs, snapShotCamera.init(camera.camera3D); sizeRendering.width = width; sizeRendering.height = height; + sizeRendering.FOV = camera.fov; }; this.getAutoRotationAngle = function getAutoRotationAngle() { return 2 * Math.PI / 60 / 60 * this.autoRotateSpeed; }; - this.getZoomScale = function getZoomScale() { + this.getDollyScale = function getDollyScale() { return Math.pow(0.95, this.zoomSpeed); }; @@ -446,7 +482,7 @@ function GlobeControls(camera, target, domElement, engine, radius, referenceCrs, this.dollyIn = function dollyIn(dollyScale) { if (dollyScale === undefined) { - dollyScale = this.getZoomScale(); + dollyScale = this.getDollyScale(); } if (this.camera instanceof THREE.PerspectiveCamera) { @@ -464,7 +500,7 @@ function GlobeControls(camera, target, domElement, engine, radius, referenceCrs, this.dollyOut = function dollyOut(dollyScale) { if (dollyScale === undefined) { - dollyScale = this.getZoomScale(); + dollyScale = this.getDollyScale(); } if (this.camera instanceof THREE.PerspectiveCamera) { @@ -613,9 +649,10 @@ function GlobeControls(camera, target, domElement, engine, radius, referenceCrs, }; const cT = new THREE.Vector3(); + const delta = 0.001; const updateCameraTargetOnGlobe = function updateCameraTargetOnGlobe() { - const oldCoord = new Coordinates(referenceCrs, cameraTargetOnGlobe.position); + const previousCameraTargetOnGlobe = cameraTargetOnGlobe.position.clone(); // Get distance camera DME const pickingPosition = getPickingPosition(); @@ -637,17 +674,51 @@ function GlobeControls(camera, target, domElement, engine, radius, referenceCrs, offset.copy(this.camera.position); offset.applyMatrix4(cameraTargetOnGlobe.matrixWorldInverse); spherical.setFromVector3(offset); + + if (enableEventPositionChanged) { + if (state === CONTROL_STATE.ORBIT && (Math.abs(snapShotSpherical.phi - spherical.phi) > delta || Math.abs(snapShotSpherical.theta - spherical.theta) > delta)) { + this.dispatchEvent({ + type: CONTROL_EVENTS.ORIENTATION_CHANGED, + previous: { + tilt: snapShotSpherical.phi * 180 / Math.PI, + heading: snapShotSpherical.theta * 180 / Math.PI, + }, + new: { + tilt: spherical.phi * 180 / Math.PI, + heading: spherical.theta * 180 / Math.PI, + }, + }); + } else if (state === CONTROL_STATE.PAN) { + this.dispatchEvent({ + type: CONTROL_EVENTS.PAN_CHANGED, + }); + } + + const previousRange = snapShotSpherical.radius; + const newRange = this.getRange(); + if (Math.abs(newRange - previousRange) / previousRange > 0.001) { + this.dispatchEvent({ + type: CONTROL_EVENTS.RANGE_CHANGED, + previous: { range: previousRange }, + new: { range: newRange }, + }); + } + + if (cameraTargetOnGlobe.position.distanceTo(previousCameraTargetOnGlobe) / spherical.radius > delta) { + this.dispatchEvent({ + type: CONTROL_EVENTS.CAMERATARGET_CHANGED, + previous: { cameraTarget: new Coordinates(this.referenceCrs, previousCameraTargetOnGlobe) }, + new: { cameraTarget: new Coordinates(this.referenceCrs, cameraTargetOnGlobe.position) }, + }); + } + snapShotSpherical.copy(spherical); + } + state = CONTROL_STATE.NONE; lastRotation = []; if (enableTargetHelper) { this.dispatchEvent(this.changeEvent); } - - this.dispatchEvent({ - type: 'camera-target-updated', - oldCameraTargetPosition: oldCoord, - newCameraTargetPosition: new Coordinates(this.referenceCrs, cameraTargetOnGlobe.position), - }); }; // Update helper @@ -840,7 +911,7 @@ function GlobeControls(camera, target, domElement, engine, radius, referenceCrs, if (point) { animatedScale = 0.6; - this.setCameraTargetPosition(point, true); + this.setCameraTargetPosition(point, this.isAnimationEnabled()); } } }; @@ -895,7 +966,17 @@ function GlobeControls(camera, target, domElement, engine, radius, referenceCrs, this.dollyIn(); } + const previousRange = this.getRange(); update(); + const newRange = this.getRange(); + if (Math.abs(newRange - previousRange) / previousRange > 0.001 && enableEventPositionChanged) { + this.dispatchEvent({ + type: CONTROL_EVENTS.RANGE_CHANGED, + previous: { range: previousRange }, + new: { range: newRange }, + }); + } + snapShotSpherical.copy(spherical); this.dispatchEvent(this.startEvent); this.dispatchEvent(this.endEvent); @@ -1145,7 +1226,27 @@ function GlobeControls(camera, target, domElement, engine, radius, referenceCrs, this.domElement.addEventListener('touchmove', onTouchMove.bind(this), false); // refresh control for each animation's frame - this.domElement.addEventListener('frameAnimation', update.bind(this), false); + player.addEventListener('animation-frame', update.bind(this)); + + function isAnimationWithoutDamping(animation) { + return animation && !(animation.name === 'damping-move' || animation.name === 'damping-orbit'); + } + + player.addEventListener('animation-started', (e) => { + if (isAnimationWithoutDamping(e.animation)) { + this.dispatchEvent({ + type: 'animation-started', + }); + } + }); + + player.addEventListener('animation-ended', (e) => { + if (isAnimationWithoutDamping(e.animation)) { + this.dispatchEvent({ + type: 'animation-ended', + }); + } + }); // TODO: Why windows window.addEventListener('keydown', onKeyDown.bind(this), false); @@ -1169,43 +1270,89 @@ function GlobeControls(camera, target, domElement, engine, radius, referenceCrs, initialTarget = cameraTargetOnGlobe.clone(); initialPosition = this.camera.position.clone(); initialZoom = this.camera.zoom; + snapShotSpherical.copy(spherical); _handlerMouseMove = onMouseMove.bind(this); _handlerMouseUp = onMouseUp.bind(this); + + this.waitSceneLoaded().then(() => { + this.updateCameraTransformation(); + this.dispatchEvent(this.changeEvent); + }); } GlobeControls.prototype = Object.create(THREE.EventDispatcher.prototype); GlobeControls.prototype.constructor = GlobeControls; +function getRangeFromScale(scale, pitch) { + // Screen pitch, in millimeters + pitch = (pitch || 0.28) / 1000; + const alpha = sizeRendering.FOV / 180 * Math.PI * 0.5; + // Invert one unit projection (see getDollyScale) + const range = pitch * sizeRendering.height / (scale * 2 * Math.tan(alpha)); + + return range; +} // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -// API Function +/** + * Change the tilt. + * + * @param {Angle} Number - The angle. + * @param {boolean} isAnimated Indicates if animated + * @return {Promise} + */ GlobeControls.prototype.setTilt = function setTilt(tilt, isAnimated) { - const deltaPhi = (tilt * Math.PI / 180 - this.getTiltRad()); - return this.moveOrbitalPosition(0, 0, deltaPhi, isAnimated); + return this.setOrbitalPosition({ tilt }, isAnimated); }; +/** + * + * Change the heading. + * @param {Angle} Number - The angle. + * @param {boolean} isAnimated Indicates if animated + * @return {Promise} + */ GlobeControls.prototype.setHeading = function setHeading(heading, isAnimated) { - const deltaTheta = (heading * Math.PI / 180 - this.getHeadingRad()); - return this.moveOrbitalPosition(0, deltaTheta, 0, isAnimated); + return this.setOrbitalPosition({ heading }, isAnimated); }; -GlobeControls.prototype.setRange = function setRange(pRange, isAnimated) { - const deltaRange = pRange - this.getRange(); - return this.moveOrbitalPosition(deltaRange, 0, 0, isAnimated); +/** + * Sets the "range": the distance in meters between the camera and the current central point on the screen. + * + * @param {Number} pRange - The camera altitude. + * @param {boolean} isAnimated Indicates if animated + * @return {Promise} + */ +GlobeControls.prototype.setRange = function setRange(range, isAnimated) { + return this.setOrbitalPosition({ range }, isAnimated); }; -GlobeControls.prototype.setOrbitalPosition = function setOrbitalPosition(range, heading, tilt, isAnimated) { - const deltaPhi = tilt ? tilt * Math.PI / 180 - this.getTiltRad() : 0; - const deltaTheta = heading ? heading * Math.PI / 180 - this.getHeadingRad() : 0; - const deltaRange = range ? range - this.getRange() : 0; - return this.moveOrbitalPosition(deltaRange, deltaTheta, deltaPhi, isAnimated); +/** + * Sets orientation angles of the current camera, in degrees. + * + * @param {object} orientation The angle of the rotation in degrees + * @param {boolean} isAnimated Indicates if animated + * @return {Promise} { description_of_the_return_value } + */ +GlobeControls.prototype.setOrbitalPosition = function setOrbitalPosition(position, isAnimated) { + isAnimated = isAnimated === undefined ? this.isAnimationEnabled() : isAnimated; + const deltaPhi = position.tilt === undefined ? 0 : position.tilt * Math.PI / 180 - this.getTiltRad(); + const deltaTheta = position.heading === undefined ? 0 : position.heading * Math.PI / 180 - this.getHeadingRad(); + const deltaRange = position.range === undefined ? 0 : position.range - this.getRange(); + return this.moveOrbitalPosition(deltaRange, deltaTheta, deltaPhi, isAnimated).then(() => { + this.dispatchEvent(this.changeEvent); + return this.waitSceneLoaded().then(() => { + this.updateCameraTransformation(); + }); + }); }; const destSpherical = new THREE.Spherical(); GlobeControls.prototype.moveOrbitalPosition = function moveOrbitalPosition(deltaRange, deltaTheta, deltaPhi, isAnimated) { + isAnimated = isAnimated === undefined ? this.isAnimationEnabled() : isAnimated; const range = deltaRange + this.getRange(); if (isAnimated) { destSpherical.theta = deltaTheta + spherical.theta; @@ -1216,8 +1363,9 @@ GlobeControls.prototype.moveOrbitalPosition = function moveOrbitalPosition(delta state = CONTROL_STATE.ORBIT; return player.play(animationOrbit).then(() => { // To correct errors at animation's end + // TODO : find other solution to correct error if (player.isEnded()) { - this.moveOrbitalPosition(0, destSpherical.theta - spherical.theta, destSpherical.phi - spherical.phi); + this.moveOrbitalPosition(0, destSpherical.theta - spherical.theta, destSpherical.phi - spherical.phi, false); } this.resetControls(); }); @@ -1233,7 +1381,7 @@ GlobeControls.prototype.moveOrbitalPosition = function moveOrbitalPosition(delta /** * Returns the coordinates of the globe point targeted by the camera. - * + * * @return {THREE.Vector3} position */ GlobeControls.prototype.getCameraTargetPosition = function getCameraTargetPosition() { @@ -1247,6 +1395,7 @@ GlobeControls.prototype.getCameraTargetPosition = function getCameraTargetPositi * @param {boolean} isAnimated - if we should animate the move */ GlobeControls.prototype.setCameraTargetPosition = function setCameraTargetPosition(position, isAnimated) { + isAnimated = isAnimated === undefined ? this.isAnimationEnabled() : isAnimated; const center = this.getCameraTargetPosition(); if (position.range) { @@ -1256,7 +1405,10 @@ GlobeControls.prototype.setCameraTargetPosition = function setCameraTargetPositi const targetOnEllipsoid = new C.EPSG_4326(currentTargetPosition.longitude(), currentTargetPosition.latitude(), 0) .as(this.referenceCrs).xyz(); const compensation = position.length() - targetOnEllipsoid.length(); - position.range += compensation; + // FIX ME error with compensation negative + if (compensation > 0) { + position.range += compensation; + } } snapShotCamera.shot(this.camera); @@ -1267,13 +1419,15 @@ GlobeControls.prototype.setCameraTargetPosition = function setCameraTargetPositi const vFrom = center.clone().normalize(); const vTo = position.normalize(); + let promise; + if (isAnimated) { ctrl.qDelta.setFromUnitVectors(vFrom, vTo); if (position.range) { animatedScale = 1.0 - position.range / this.getRange(); } state = CONTROL_STATE.MOVE_GLOBE; - return player.play(animationZoomCenter).then(() => { + promise = player.play(animationZoomCenter).then(() => { animatedScale = 0.0; this.resetControls(); }); @@ -1281,36 +1435,47 @@ GlobeControls.prototype.setCameraTargetPosition = function setCameraTargetPositi else { quatGlobe.setFromUnitVectors(vFrom, vTo); this.updateCameraTransformation(CONTROL_STATE.MOVE_GLOBE); - return Promise.resolve(); + if (animatedScale > 0.0 && animatedScale < 1.0) { + this.setRange(this.getRange() * animatedScale); + } + promise = Promise.resolve(); } + + return promise.then(() => { + this.dispatchEvent(this.changeEvent); + return this.waitSceneLoaded().then(() => { + this.updateCameraTransformation(); + }); + }); }; +/** + * Returns the "range": the distance in meters between the camera and the current central point on the screen. + * + * @return {number} number + */ GlobeControls.prototype.getRange = function getRange() { return this.getCameraTargetPosition().distanceTo(this.camera.position); }; -// TODO idea : remove this? used in API -GlobeControls.prototype.getRay = function getRay() { - var direction = new THREE.Vector3(0, 0, 1); - this.camera.localToWorld(direction); - direction.sub(this.camera.position).negate().normalize(); - - return { - origin: this.camera.position, - direction, - }; -}; - +/** + * Returns the tilt in degrees. + * + * @return {Angle} number - The angle of the rotation in degrees. + */ GlobeControls.prototype.getTilt = function getTilt() { return spherical.phi * 180 / Math.PI; }; +/** + * Returns the heading in degrees. + * + * @return {Angle} number - The angle of the rotation in degrees. + */ GlobeControls.prototype.getHeading = function getHeading() { return spherical.theta * 180 / Math.PI; }; -// Same functions - GlobeControls.prototype.getTiltRad = function getTiltRad() { return spherical.phi; }; @@ -1331,17 +1496,194 @@ GlobeControls.prototype.moveTarget = function moveTarget() { return movingCameraTargetOnGlobe; }; -GlobeControls.prototype.pan = function pan(deltaX, deltaY) { - this.mouseToPan(deltaX, deltaY); +/** + * Displaces the central point to a specific amount of pixels from its current position. + * The view flies to the desired coordinate, i.e.is not teleported instantly. Note : The results can be strange in some cases, if ever possible, when e.g.the camera looks horizontally or if the displaced center would not pick the ground once displaced. + * @param {vector} pVector The vector + */ +GlobeControls.prototype.pan = function pan(pVector) { + this.mouseToPan(pVector.x, pVector.y); this.updateCameraTransformation(CONTROL_STATE.PAN); + this.dispatchEvent(this.changeEvent); + return this.waitSceneLoaded().then(() => { + this.updateCameraTransformation(); + }); +}; + +/** + * Returns the orientation angles of the current camera, in degrees. + * + */ +GlobeControls.prototype.getCameraOrientation = function getCameraOrientation() { + var tiltCam = this.getTilt(); + var headingCam = this.getHeading(); + return [tiltCam, headingCam]; +}; + +/** + * Returns the camera location projected on the ground in lat,lon. + * + * @return {Position} position + */ + +GlobeControls.prototype.getCameraLocation = function getCameraLocation() { + return new Coordinates('EPSG:4978', this.camera.position).as('EPSG:4326'); +}; + +/** + * Retuns the coordinates of the central point on screen. + * + * @return {Position} position + */ + +GlobeControls.prototype.getCameraTargetGeoPosition = function getCameraTargetGeoPosition() { + return new Coordinates(this.referenceCrs, this.getCameraTargetPosition()).as('EPSG:4326'); +}; + +/** + * Sets the animation enabled. + * @param {boolean} enable enable + */ +GlobeControls.prototype.setAnimationEnabled = function setAnimationEnabled(enable) { + enableAnimation = enable; +}; + +/** + * Determines if animation enabled. + * @return {boolean} True if animation enabled, False otherwise. + */ +GlobeControls.prototype.isAnimationEnabled = function isAnimationEnabled() { + return enableAnimation; +}; + +/** + * Returns the actual zoom. The zoom will always be between the [getMinZoom(), getMaxZoom()]. + * @return {number} The zoom . + */ +GlobeControls.prototype.getZoom = function getZoom() { + return computeTileZoomFromDistanceCamera(this.getRange()); +}; + +/** + * Gets the current zoom, which is an index in the logical scales predefined for the application. + * The higher the zoom, the closer to the ground. + * The zoom is always in the [getMinZoom(), getMaxZoom()] range. + * @param {number} zoom The zoom + * @param {boolean} isAnimated Indicates if animated + * @return {Promise} + */ +GlobeControls.prototype.setZoom = function setZoom(zoom, isAnimated) { + isAnimated = isAnimated === undefined ? this.isAnimationEnabled() : isAnimated; + const range = computeDistanceCameraFromTileZoom(zoom); + return this.setRange(range, isAnimated); +}; + +/** + * Return the current zoom scale at the central point of the view. + * This function compute the scale of a map + * @param {number} pitch Screen pitch, in millimeters ; 0.28 by default + * @return {number} The zoom scale. + */ +GlobeControls.prototype.getScale = function getScale(pitch) { + // TODO: Why error div size height in Chrome? + // Screen pitch, in millimeters + pitch = (pitch || 0.28) / 1000; + const FOV = sizeRendering.FOV / 180 * Math.PI * 0.5; + // projection one unit on screen + const unitProjection = sizeRendering.height / (2 * this.getRange() * Math.tan(FOV)); + return pitch * unitProjection; +}; + +/** + * Changes the zoom of the central point of screen so that screen acts as a map with a specified scale. + * The view flies to the desired zoom scale; + * @param {number} scale The scale + * @param {number} pitch The pitch + * @param {boolean} isAnimated Indicates if animated + * @return {Promise} + */ + // TODO pas de scale supérieur à 0.05.... +GlobeControls.prototype.setScale = function setScale(scale, pitch, isAnimated) { + isAnimated = isAnimated === undefined ? this.isAnimationEnabled() : isAnimated; + const range = getRangeFromScale(scale); + return this.setRange(range, isAnimated); +}; + +/** + * Changes the center of the scene on screen to the specified coordinates. + * + * @function + * @memberOf GlobeControls + * @param {Object} coordinates - The globe coordinates in EPSG_4326 projection to aim to + * @param {number} coordinates.latitude + * @param {number} coordinates.longitude + * @param {number} coordinates.range + * @param {boolean} isAnimated - if the movement should be animated + * @return {Promise} A promise that resolves when the next 'globe-loaded' event fires. + */ +GlobeControls.prototype.setCameraTargetGeoPosition = function setCameraTargetGeoPosition(coordinates, isAnimated) { + isAnimated = isAnimated === undefined ? this.isAnimationEnabled() : isAnimated; + const position = new C.EPSG_4326(coordinates.longitude, coordinates.latitude, 0) + .as('EPSG:4978').xyz(); + position.range = coordinates.range; + return this.setCameraTargetPosition(position, isAnimated); +}; + +/** + * Changes the center of the scene on screen to the specified coordinates. + * This function allows to change the central position, the zoom the range, the scale and the camera orientation at the same time. + * The zoom has to be between the [getMinZoom(), getMaxZoom()]. + * The zoom and the scale can't be set at the same time. + * --> + * @param {Position} position + * @param {number} position.longitude Coordinate longitude WGS84 in degree + * @param {number} position.latitude Coordinate latitude WGS84 in degree + * @param {number} [position.tilt] Camera tilt in degree + * @param {number} [position.heading] Camera heading in degree + * @param {number} [position.range] The camera distance to the target center + * @param {number} [position.zoom] zoom, ignored if range is set + * @param {number} [position.scale] scale, ignored if the zoom zoom or range is set. For a scale of 1/500 it is necessary to write 0,002. + * @param {boolean} isAnimated Indicates if animated + * @return {Promise} + */ +GlobeControls.prototype.setCameraTargetGeoPositionAdvanced = function setCameraTargetGeoPositionAdvanced(position, isAnimated) { + isAnimated = isAnimated === undefined ? this.isAnimationEnabled() : isAnimated; + if (position.zoom) { + position.range = computeDistanceCameraFromTileZoom(position.zoom); + } else if (position.scale) { + position.range = getRangeFromScale(position.scale); + } + enableEventPositionChanged = false; + return this.setCameraTargetGeoPosition(position, isAnimated).then(() => { + enableEventPositionChanged = true; + return this.setOrbitalPosition(position, isAnimated); }); +}; + +/** + * Pick a position on the globe at the given position. + * @param {number | MouseEvent} x|event - The x-position inside the Globe element or a mouse event. + * @param {number | undefined} y - The y-position inside the Globe element. + * @return {Position} position + */ +GlobeControls.prototype.pickGeoPosition = function pickGeoPosition(mouse, y) { + var screenCoords = { + x: mouse.clientX || mouse, + y: mouse.clientY || y, + }; + + var pickedPosition = getPickingPosition(this.controlsActiveLayers, screenCoords); + + if (!pickedPosition) { + return; + } + + return new Coordinates('EPSG:4978', pickedPosition).as('EPSG:4326'); }; -// End API functions // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # GlobeControls.prototype.reset = function reset() { // TODO not reset target globe - state = CONTROL_STATE.NONE; this.target.copy(initialTarget); diff --git a/utils/debug/Debug.js b/utils/debug/Debug.js index 34413598bf..ca9b25b60b 100644 --- a/utils/debug/Debug.js +++ b/utils/debug/Debug.js @@ -247,7 +247,7 @@ function Debug(view, viewerDiv) { gui.add(state, 'eventsDebug').name('Debug event').onChange((() => { let eventFolder; return (newValue) => { - const controls = view.currentControls(); + const controls = view.controls; const listeners = []; if (newValue) { eventFolder = gui.addFolder('Events'); @@ -257,16 +257,16 @@ function Debug(view, viewerDiv) { const roundedLat = Math.round(initialPosition.latitude() * 10000) / 10000; const roundedLon = Math.round(initialPosition.longitude() * 10000) / 10000; state.cameraTargetUpdated = `lat: ${roundedLat} lon: ${roundedLon}`; - const cameraTargetUpdatedController = eventFolder.add(state, 'cameraTargetUpdated').name('camera-target-updated'); + const cameraTargetUpdatedController = eventFolder.add(state, 'cameraTargetUpdated').name('camera-target-changed'); const cameraTargetListener = (ev) => { - const positionGeo = ev.newCameraTargetPosition.as('EPSG:4326'); + const positionGeo = ev.new.cameraTarget.as('EPSG:4326'); const roundedLat = Math.round(positionGeo.latitude() * 10000) / 10000; const roundedLon = Math.round(positionGeo.longitude() * 10000) / 10000; state.cameraTargetUpdated = `lat: ${roundedLat} lon: ${roundedLon}`; cameraTargetUpdatedController.updateDisplay(); }; - controls.addEventListener('camera-target-updated', cameraTargetListener); - listeners.push({ type: 'camera-target-updated', stateName: 'cameraTargetUpdated', fn: cameraTargetListener }); + controls.addEventListener('camera-target-changed', cameraTargetListener); + listeners.push({ type: 'camera-target-changed', stateName: 'cameraTargetUpdated', fn: cameraTargetListener }); } else { for (const listener of listeners) { controls.removeEventListener(listener.type, listener.fn);