From 29372798be0da46935a08fd3645a1edc6426a6c8 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 29 Mar 2017 18:40:05 +0300 Subject: [PATCH 1/7] clean up easeTo/flyTo a bit --- src/ui/camera.js | 105 +++++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 59 deletions(-) diff --git a/src/ui/camera.js b/src/ui/camera.js index c9596681aad..dbd1d2e8af8 100644 --- a/src/ui/camera.js +++ b/src/ui/camera.js @@ -496,6 +496,12 @@ class Camera extends Evented { easing: util.ease }, options); + if (options.animate === false) options.duration = 0; + + if (options.smoothEasing && options.duration !== 0) { + options.easing = this._smoothOutEasing(options.duration); + } + const tr = this.transform, offset = Point.convert(options.offset), startZoom = this.getZoom(), @@ -506,8 +512,7 @@ class Camera extends Evented { bearing = 'bearing' in options ? this._normalizeBearing(options.bearing, startBearing) : startBearing, pitch = 'pitch' in options ? +options.pitch : startPitch; - let toLngLat, - toPoint; + let toLngLat, toPoint; if ('center' in options) { toLngLat = LngLat.convert(options.center); @@ -522,26 +527,11 @@ class Camera extends Evented { const fromPoint = tr.locationPoint(toLngLat); - if (options.animate === false) options.duration = 0; - this.zooming = (zoom !== startZoom); this.rotating = (startBearing !== bearing); this.pitching = (pitch !== startPitch); - if (options.smoothEasing && options.duration !== 0) { - options.easing = this._smoothOutEasing(options.duration); - } - - if (!options.noMoveStart) { - this.moving = true; - this.fire('movestart', eventData); - } - if (this.zooming) { - this.fire('zoomstart', eventData); - } - if (this.pitching) { - this.fire('pitchstart', eventData); - } + this._prepareEase(eventData, options.noMoveStart); clearTimeout(this._onEaseEnd); @@ -560,19 +550,11 @@ class Camera extends Evented { tr.setLocationAtPoint(toLngLat, fromPoint.add(toPoint.sub(fromPoint)._mult(k))); - this.fire('move', eventData); - if (this.zooming) { - this.fire('zoom', eventData); - } - if (this.rotating) { - this.fire('rotate', eventData); - } - if (this.pitching) { - this.fire('pitch', eventData); - } + this._fireMoveEvents(eventData); + }, () => { if (options.delayEndEvents) { - this._onEaseEnd = setTimeout(this._easeToEnd.bind(this, eventData), options.delayEndEvents); + this._onEaseEnd = setTimeout(() => this._easeToEnd(eventData), options.delayEndEvents); } else { this._easeToEnd(eventData); } @@ -581,6 +563,33 @@ class Camera extends Evented { return this; } + _prepareEase(eventData, noMoveStart) { + this.moving = true; + + if (!noMoveStart) { + this.fire('movestart', eventData); + } + if (this.zooming) { + this.fire('zoomstart', eventData); + } + if (this.pitching) { + this.fire('pitchstart', eventData); + } + } + + _fireMoveEvents(eventData) { + this.fire('move', eventData); + if (this.zooming) { + this.fire('zoom', eventData); + } + if (this.rotating) { + this.fire('rotate', eventData); + } + if (this.pitching) { + this.fire('pitch', eventData); + } + } + _easeToEnd(eventData) { const wasZooming = this.zooming; const wasPitching = this.pitching; @@ -596,7 +605,6 @@ class Camera extends Evented { this.fire('pitchend', eventData); } this.fire('moveend', eventData); - } /** @@ -769,14 +777,11 @@ class Camera extends Evented { options.duration = 1000 * S / V; } - this.moving = true; this.zooming = true; - if (startBearing !== bearing) this.rotating = true; - if (startPitch !== pitch) this.pitching = true; + this.rotating = (startBearing !== bearing); + this.pitching = (pitch !== startPitch); - this.fire('movestart', eventData); - this.fire('zoomstart', eventData); - if (this.pitching) this.fire('pitchstart', eventData); + this._prepareEase(eventData, false); this._ease(function (k) { // s: The distance traveled along the flight path, measured in ρ-screenfuls. @@ -785,10 +790,8 @@ class Camera extends Evented { const scale = 1 / w(s); tr.zoom = startZoom + tr.scaleZoom(scale); - tr.center = tr.unproject(from.add(to.sub(from).mult(us)).mult(scale)); - if (tr.renderWorldCopies) { - tr.center = tr.center.wrap(); - } + const newCenter = tr.unproject(from.add(to.sub(from).mult(us)).mult(scale)); + tr.center = tr.renderWorldCopies ? newCenter.wrap() : newCenter; if (this.rotating) { tr.bearing = interpolate(startBearing, bearing, k); @@ -797,25 +800,9 @@ class Camera extends Evented { tr.pitch = interpolate(startPitch, pitch, k); } - this.fire('move', eventData); - this.fire('zoom', eventData); - if (this.rotating) { - this.fire('rotate', eventData); - } - if (this.pitching) { - this.fire('pitch', eventData); - } - }, function() { - const wasPitching = this.pitching; - this.moving = false; - this.zooming = false; - this.rotating = false; - this.pitching = false; - - if (wasPitching) this.fire('pitchend', eventData); - this.fire('zoomend', eventData); - this.fire('moveend', eventData); - }, options); + this._fireMoveEvents(eventData); + + }, () => this._easeToEnd(eventData), options); return this; } From 058236eeff909ec57c73ed94049f0f350795a88d Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Tue, 4 Apr 2017 17:07:44 +0300 Subject: [PATCH 2/7] fix position interpolation in easeTo --- src/ui/camera.js | 49 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/src/ui/camera.js b/src/ui/camera.js index dbd1d2e8af8..98a4545864b 100644 --- a/src/ui/camera.js +++ b/src/ui/camera.js @@ -31,7 +31,7 @@ const Evented = require('../util/evented'); * @property {number} duration The animation's duration, measured in milliseconds. * @property {Function} easing A function taking a time in the range 0..1 and returning a number where 0 is * the initial state and 1 is the final state. - * @property {PointLike} offset `x` and `y` coordinates representing the animation's origin of movement relative to the map's center. + * @property {PointLike} offset of the target center relative to real map container center at the end of animation. * @property {boolean} animate If `false`, no animation will occur. */ @@ -77,8 +77,7 @@ class Camera extends Evented { * @see [Move symbol with the keyboard](https://www.mapbox.com/mapbox-gl-js/example/rotating-controllable-marker/) */ setCenter(center, eventData) { - this.jumpTo({center: center}, eventData); - return this; + return this.jumpTo({center: center}, eventData); } /** @@ -94,9 +93,7 @@ class Camera extends Evented { * @see [Navigate the map with game-like controls](https://www.mapbox.com/mapbox-gl-js/example/game-controls/) */ panBy(offset, options, eventData) { - this.panTo(this.transform.center, - util.extend({offset: Point.convert(offset).mult(-1)}, options), eventData); - return this; + return this.panTo(this.transform.center, util.extend({offset}, options), eventData); } /** @@ -512,20 +509,18 @@ class Camera extends Evented { bearing = 'bearing' in options ? this._normalizeBearing(options.bearing, startBearing) : startBearing, pitch = 'pitch' in options ? +options.pitch : startPitch; - let toLngLat, toPoint; + const center = LngLat.convert(options.center || tr.center); - if ('center' in options) { - toLngLat = LngLat.convert(options.center); - toPoint = tr.centerPoint.add(offset); - } else if ('around' in options) { - toLngLat = LngLat.convert(options.around); - toPoint = tr.locationPoint(toLngLat); - } else { - toPoint = tr.centerPoint.add(offset); - toLngLat = tr.pointLocation(toPoint); - } + const from = tr.point; + const adjustedPoint = tr.locationPoint(center).add(offset.div(tr.zoomScale(zoom - startZoom))); + const to = tr.project(tr.pointLocation(adjustedPoint)); + + let around, aroundPoint; - const fromPoint = tr.locationPoint(toLngLat); + if (options.around) { + around = LngLat.convert(options.around); + aroundPoint = tr.locationPoint(around); + } this.zooming = (zoom !== startZoom); this.rotating = (startBearing !== bearing); @@ -540,16 +535,21 @@ class Camera extends Evented { tr.zoom = interpolate(startZoom, zoom, k); } + if (around) { + tr.setLocationAtPoint(around, aroundPoint); + } else { + const scale = tr.zoomScale(tr.zoom - startZoom); + const newCenter = tr.unproject(from.add(to.sub(from).mult(k)).mult(scale)); + tr.center = tr.renderWorldCopies ? newCenter.wrap() : newCenter; + } + if (this.rotating) { tr.bearing = interpolate(startBearing, bearing, k); } - if (this.pitching) { tr.pitch = interpolate(startPitch, pitch, k); } - tr.setLocationAtPoint(toLngLat, fromPoint.add(toPoint.sub(fromPoint)._mult(k))); - this._fireMoveEvents(eventData); }, () => { @@ -785,12 +785,11 @@ class Camera extends Evented { this._ease(function (k) { // s: The distance traveled along the flight path, measured in ρ-screenfuls. - const s = k * S, - us = u(s); - + const s = k * S; const scale = 1 / w(s); tr.zoom = startZoom + tr.scaleZoom(scale); - const newCenter = tr.unproject(from.add(to.sub(from).mult(us)).mult(scale)); + + const newCenter = tr.unproject(from.add(to.sub(from).mult(u(s))).mult(scale)); tr.center = tr.renderWorldCopies ? newCenter.wrap() : newCenter; if (this.rotating) { From 8be35820795b5f56e4ec1dace6935e801d42616e Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 7 Apr 2017 20:38:57 +0300 Subject: [PATCH 3/7] a different approach to easeTo interpolation --- src/ui/camera.js | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/ui/camera.js b/src/ui/camera.js index 98a4545864b..0d5a0e95eaa 100644 --- a/src/ui/camera.js +++ b/src/ui/camera.js @@ -93,6 +93,7 @@ class Camera extends Evented { * @see [Navigate the map with game-like controls](https://www.mapbox.com/mapbox-gl-js/example/game-controls/) */ panBy(offset, options, eventData) { + offset = Point.convert(offset).mult(-1); return this.panTo(this.transform.center, util.extend({offset}, options), eventData); } @@ -511,9 +512,12 @@ class Camera extends Evented { const center = LngLat.convert(options.center || tr.center); - const from = tr.point; - const adjustedPoint = tr.locationPoint(center).add(offset.div(tr.zoomScale(zoom - startZoom))); - const to = tr.project(tr.pointLocation(adjustedPoint)); + const screenPoint = tr.centerPoint.add(offset); + + const from = tr.project(tr.pointLocation(screenPoint)); + const to = tr.project(center); + const delta = to.sub(from); + const finalScale = tr.zoomScale(zoom - startZoom); let around, aroundPoint; @@ -534,15 +538,6 @@ class Camera extends Evented { if (this.zooming) { tr.zoom = interpolate(startZoom, zoom, k); } - - if (around) { - tr.setLocationAtPoint(around, aroundPoint); - } else { - const scale = tr.zoomScale(tr.zoom - startZoom); - const newCenter = tr.unproject(from.add(to.sub(from).mult(k)).mult(scale)); - tr.center = tr.renderWorldCopies ? newCenter.wrap() : newCenter; - } - if (this.rotating) { tr.bearing = interpolate(startBearing, bearing, k); } @@ -550,6 +545,15 @@ class Camera extends Evented { tr.pitch = interpolate(startPitch, pitch, k); } + if (around) { + tr.setLocationAtPoint(around, aroundPoint); + } else { + const scale = tr.zoomScale(tr.zoom - startZoom); + const k2 = k * Math.pow(2, 1 - k); + const newCenter = tr.unproject(from.add(to.sub(from).mult(k2)).mult(scale)); + tr.setLocationAtPoint(tr.renderWorldCopies ? newCenter.wrap() : newCenter, screenPoint); + } + this._fireMoveEvents(eventData); }, () => { From 2ec0ae11ecc6856d19d4f614486d11cdf1678c19 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 7 Apr 2017 22:06:47 +0300 Subject: [PATCH 4/7] fix easeTo easing for zooming out and panning --- src/ui/camera.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ui/camera.js b/src/ui/camera.js index 0d5a0e95eaa..890bf4bf79e 100644 --- a/src/ui/camera.js +++ b/src/ui/camera.js @@ -549,7 +549,9 @@ class Camera extends Evented { tr.setLocationAtPoint(around, aroundPoint); } else { const scale = tr.zoomScale(tr.zoom - startZoom); - const k2 = k * Math.pow(2, 1 - k); + const k2 = k * ( + zoom > startZoom ? Math.pow(2, 1 - k) : + zoom < startZoom ? Math.pow(2, k) : 1); const newCenter = tr.unproject(from.add(to.sub(from).mult(k2)).mult(scale)); tr.setLocationAtPoint(tr.renderWorldCopies ? newCenter.wrap() : newCenter, screenPoint); } From bd7d53c29474606609ede710949b25dd67fe3e2f Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Mon, 10 Apr 2017 20:14:08 +0300 Subject: [PATCH 5/7] one more zoom out easing correction --- src/ui/camera.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/camera.js b/src/ui/camera.js index 890bf4bf79e..fe75048e76b 100644 --- a/src/ui/camera.js +++ b/src/ui/camera.js @@ -551,7 +551,7 @@ class Camera extends Evented { const scale = tr.zoomScale(tr.zoom - startZoom); const k2 = k * ( zoom > startZoom ? Math.pow(2, 1 - k) : - zoom < startZoom ? Math.pow(2, k) : 1); + zoom < startZoom ? Math.pow(2, k - 1) : 1); const newCenter = tr.unproject(from.add(to.sub(from).mult(k2)).mult(scale)); tr.setLocationAtPoint(tr.renderWorldCopies ? newCenter.wrap() : newCenter, screenPoint); } From 8dc82772f78de9c467c03daca38178118c676bc8 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Wed, 12 Apr 2017 20:19:28 +0300 Subject: [PATCH 6/7] another take at easeTo interpolation --- src/ui/camera.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ui/camera.js b/src/ui/camera.js index fe75048e76b..b9e381a6523 100644 --- a/src/ui/camera.js +++ b/src/ui/camera.js @@ -515,8 +515,7 @@ class Camera extends Evented { const screenPoint = tr.centerPoint.add(offset); const from = tr.project(tr.pointLocation(screenPoint)); - const to = tr.project(center); - const delta = to.sub(from); + const delta = tr.project(center).sub(from); const finalScale = tr.zoomScale(zoom - startZoom); let around, aroundPoint; @@ -549,10 +548,11 @@ class Camera extends Evented { tr.setLocationAtPoint(around, aroundPoint); } else { const scale = tr.zoomScale(tr.zoom - startZoom); - const k2 = k * ( - zoom > startZoom ? Math.pow(2, 1 - k) : - zoom < startZoom ? Math.pow(2, k - 1) : 1); - const newCenter = tr.unproject(from.add(to.sub(from).mult(k2)).mult(scale)); + const base = zoom > startZoom ? + Math.min(2, finalScale) : + Math.max(0.5, finalScale); + const speedup = Math.pow(base, 1 - k); + const newCenter = tr.unproject(from.add(delta.mult(k * speedup)).mult(scale)); tr.setLocationAtPoint(tr.renderWorldCopies ? newCenter.wrap() : newCenter, screenPoint); } From 5b26ff48c1efdc630a069a5ae6b4bc43f7413e3d Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 14 Apr 2017 13:43:35 +0300 Subject: [PATCH 7/7] fix offset handling in easing methods, fix tests --- src/ui/camera.js | 32 ++++++++++++++++---------------- test/unit/ui/camera.test.js | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/ui/camera.js b/src/ui/camera.js index b9e381a6523..d51f92eae75 100644 --- a/src/ui/camera.js +++ b/src/ui/camera.js @@ -501,7 +501,6 @@ class Camera extends Evented { } const tr = this.transform, - offset = Point.convert(options.offset), startZoom = this.getZoom(), startBearing = this.getBearing(), startPitch = this.getPitch(), @@ -510,11 +509,10 @@ class Camera extends Evented { bearing = 'bearing' in options ? this._normalizeBearing(options.bearing, startBearing) : startBearing, pitch = 'pitch' in options ? +options.pitch : startPitch; - const center = LngLat.convert(options.center || tr.center); - - const screenPoint = tr.centerPoint.add(offset); - - const from = tr.project(tr.pointLocation(screenPoint)); + const pointAtOffset = tr.centerPoint.add(Point.convert(options.offset)); + const locationAtOffset = tr.pointLocation(pointAtOffset); + const center = LngLat.convert(options.center || locationAtOffset); + const from = tr.project(locationAtOffset); const delta = tr.project(center).sub(from); const finalScale = tr.zoomScale(zoom - startZoom); @@ -553,7 +551,7 @@ class Camera extends Evented { Math.max(0.5, finalScale); const speedup = Math.pow(base, 1 - k); const newCenter = tr.unproject(from.add(delta.mult(k * speedup)).mult(scale)); - tr.setLocationAtPoint(tr.renderWorldCopies ? newCenter.wrap() : newCenter, screenPoint); + tr.setLocationAtPoint(tr.renderWorldCopies ? newCenter.wrap() : newCenter, pointAtOffset); } this._fireMoveEvents(eventData); @@ -685,16 +683,19 @@ class Camera extends Evented { }, options); const tr = this.transform, - offset = Point.convert(options.offset), startZoom = this.getZoom(), startBearing = this.getBearing(), startPitch = this.getPitch(); - const center = 'center' in options ? LngLat.convert(options.center) : this.getCenter(); const zoom = 'zoom' in options ? +options.zoom : startZoom; const bearing = 'bearing' in options ? this._normalizeBearing(options.bearing, startBearing) : startBearing; const pitch = 'pitch' in options ? +options.pitch : startPitch; + const scale = tr.zoomScale(zoom - startZoom); + const pointAtOffset = tr.centerPoint.add(Point.convert(options.offset)); + const locationAtOffset = tr.pointLocation(pointAtOffset); + const center = LngLat.convert(options.center || locationAtOffset); + // If a path crossing the antimeridian would be shorter, extend the final coordinate so that // interpolating between the two endpoints will cross it. if (tr.renderWorldCopies && Math.abs(tr.center.lng) + Math.abs(center.lng) > 180) { @@ -705,9 +706,8 @@ class Camera extends Evented { } } - const scale = tr.zoomScale(zoom - startZoom), - from = tr.point, - to = 'center' in options ? tr.project(center).sub(offset.div(scale)) : from; + const from = tr.project(locationAtOffset); + const delta = tr.project(center).sub(from); let rho = options.curve; @@ -717,7 +717,7 @@ class Camera extends Evented { w1 = w0 / scale, // Length of the flight path as projected onto the ground plane, measured in pixels from // the world image origin at the initial scale. - u1 = to.sub(from).mag(); + u1 = delta.mag(); if ('minZoom' in options) { const minZoom = util.clamp(Math.min(options.minZoom, startZoom, zoom), tr.minZoom, tr.maxZoom); @@ -795,9 +795,6 @@ class Camera extends Evented { const scale = 1 / w(s); tr.zoom = startZoom + tr.scaleZoom(scale); - const newCenter = tr.unproject(from.add(to.sub(from).mult(u(s))).mult(scale)); - tr.center = tr.renderWorldCopies ? newCenter.wrap() : newCenter; - if (this.rotating) { tr.bearing = interpolate(startBearing, bearing, k); } @@ -805,6 +802,9 @@ class Camera extends Evented { tr.pitch = interpolate(startPitch, pitch, k); } + const newCenter = tr.unproject(from.add(delta.mult(u(s))).mult(scale)); + tr.setLocationAtPoint(tr.renderWorldCopies ? newCenter.wrap() : newCenter, pointAtOffset); + this._fireMoveEvents(eventData); }, () => this._easeToEnd(eventData), options); diff --git a/test/unit/ui/camera.test.js b/test/unit/ui/camera.test.js index 5808ae12f44..0b0d51c3fac 100644 --- a/test/unit/ui/camera.test.js +++ b/test/unit/ui/camera.test.js @@ -1053,7 +1053,7 @@ test('camera', (t) => { let crossedAntimeridian; camera.on('move', () => { - if (camera.getCenter().lng < -170) { + if (fixedLngLat(camera.getCenter(), 10).lng < -170) { crossedAntimeridian = true; } });