Skip to content

Commit

Permalink
refacto(GlobeControls): switch travel animation to StateControl
Browse files Browse the repository at this point in the history
  • Loading branch information
mgermerie authored and gchoqueux committed Jul 29, 2021
1 parent 9ce7213 commit d99827d
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 15 deletions.
17 changes: 10 additions & 7 deletions src/Controls/GlobeControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class GlobeControls extends THREE.EventDispatcher {
this.camera = view.camera.camera3D;

// State control
this.states = new StateControl();
this.states = new StateControl(this.view);
this.state = this.states.NONE;

// Set to false to disable this control
Expand Down Expand Up @@ -242,7 +242,7 @@ class GlobeControls extends THREE.EventDispatcher {
this._onMouseDown = this.onMouseDown.bind(this);
this._onMouseWheel = this.onMouseWheel.bind(this);
this._onContextMenuListener = this.onContextMenuListener.bind(this);
this._ondblclick = this.ondblclick.bind(this);
this._onTravel = this.travel.bind(this);
this._onTouchStart = this.onTouchStart.bind(this);
this._update = this.update.bind(this);
this._onTouchMove = this.onTouchMove.bind(this);
Expand All @@ -253,12 +253,13 @@ class GlobeControls extends THREE.EventDispatcher {
this.view.domElement.addEventListener('contextmenu', this._onContextMenuListener, false);
this.view.domElement.addEventListener('mousedown', this._onMouseDown, false);
this.view.domElement.addEventListener('mousewheel', this._onMouseWheel, false);
this.view.domElement.addEventListener('dblclick', this._ondblclick, false);
this.view.domElement.addEventListener('DOMMouseScroll', this._onMouseWheel, false); // firefox
this.view.domElement.addEventListener('touchstart', this._onTouchStart, false);
this.view.domElement.addEventListener('touchend', this._onMouseUp, false);
this.view.domElement.addEventListener('touchmove', this._onTouchMove, false);

this.states.addEventListener('travel_in', this._onTravel, false);

// refresh control for each animation's frame
this.player.addEventListener('animation-frame', this._update);

Expand Down Expand Up @@ -675,10 +676,10 @@ class GlobeControls extends THREE.EventDispatcher {
}
}

ondblclick(event) {
if (this.enabled === false || currentKey) { return; }
travel(event) {
if (this.enabled === false) { return; }
this.player.stop();
const point = this.view.getPickingPositionFromDepth(this.view.eventToViewCoords(event));
const point = this.view.getPickingPositionFromDepth(event.viewCoords);
const range = this.getRange(point);
if (point && range > this.minDistance) {
return this.lookAtCoordinate({
Expand Down Expand Up @@ -910,12 +911,14 @@ class GlobeControls extends THREE.EventDispatcher {
this.view.domElement.removeEventListener('DOMMouseScroll', this._onMouseWheel, false); // firefox
this.view.domElement.removeEventListener('mouseup', this._onMouseUp, false);
this.view.domElement.removeEventListener('mouseleave', this._onMouseUp, false);
this.view.domElement.removeEventListener('dblclick', this._ondblclick, false);

this.view.domElement.removeEventListener('touchstart', this._onTouchStart, false);
this.view.domElement.removeEventListener('touchend', this._onMouseUp, false);
this.view.domElement.removeEventListener('touchmove', this._onTouchMove, false);

this.states.dispose();
this.states.removeEventListener('travel_in', this._onTravel, false);

this.player.removeEventListener('animation-frame', this._onKeyUp);

window.removeEventListener('keydown', this._onKeyDown, false);
Expand Down
65 changes: 61 additions & 4 deletions src/Controls/StateControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,28 @@ const CONTROL_KEYS = {
};


function stateToTrigger(state) {
if (!state) {
return undefined;
} else if (state.mouseButton === THREE.MOUSE.LEFT && state.double) {
return 'dblclick';
} else if (state.mouseButton === THREE.MOUSE.RIGHT && state.double) {
return 'dblclick-right';
} else if (state.keyboard) {
return 'keydown';
}
}


/**
* @typedef {Object} StateControl~State
* @property {boolean} enable=true Indicate whether the state is enabled or not.
* @property {Number} [mouseButton] The mouse button bound to this state.
* @property {Number} [keyboard] The keyCode of the keyboard input bound to this state.
* @property {Number} [finger] The number of fingers on the pad bound to this state.
* @property {boolean} [double] True if the mouse button bound to this state must be pressed twice. For
* example, if `double` is set to true with a `mouseButton` set to left click,
* the State will be bound to a double click mouse button.
*/

/**
Expand All @@ -37,24 +53,48 @@ const CONTROL_KEYS = {
* to give the feeling that the view is dragged under a static camera.
* @property {State} PANORAMIC {@link State} describing camera panoramic movement : the camera is rotated around
* its own position.
* @property {State} TRAVEL_IN {@link State} describing camera travel in movement : the camera is zoomed in toward
* a given position. The choice of the target position is made in the Controls
* associated to this StateControl.
* This state can only be associated to double click on mouse buttons (left or right)
* or a keyboard key.
*/
class StateControl {
constructor(options = {}) {
class StateControl extends THREE.EventDispatcher {
constructor(view, options = {}) {
super();

this._view = view;
this._domElement = view.domElement;

this.NONE = {};

this._handleTravelInEvent = (event) => {
if (this.TRAVEL_IN === this.inputToState(event.button, event.keyCode, this.TRAVEL_IN.double)) {
this.dispatchEvent({
type: 'travel_in',
viewCoords: this._view.eventToViewCoords(event),
});
}
};

this.setFromOptions(options);
}

/**
* get the state corresponding to the mouse button and the keyboard key
* @param {Number} mouseButton The mouse button
* @param {Number} keyboard The keyboard
* @param {Boolean} [double] Value of the searched state `double` property
* @return {state} the state corresponding
*/
inputToState(mouseButton, keyboard) {
inputToState(mouseButton, keyboard, double) {
for (const key of Object.keys(this)) {
const state = this[key];
if (state.enable && state.mouseButton === mouseButton && state.keyboard === keyboard) {
if (state.enable
&& state.mouseButton === mouseButton
&& state.keyboard === keyboard
&& state.double === double
) {
return state;
}
}
Expand Down Expand Up @@ -124,6 +164,23 @@ class StateControl {
keyboard: CONTROL_KEYS.SHIFT,
enable: true,
};

const newTravelIn = options.TRAVEL_IN || this.TRAVEL_IN || {
enable: true,
mouseButton: THREE.MOUSE.LEFT,
double: true,
};

this._domElement.removeEventListener(stateToTrigger(this.TRAVEL_IN), this._handleTravelInEvent, false);
this._domElement.addEventListener(stateToTrigger(newTravelIn), this._handleTravelInEvent, false);
this.TRAVEL_IN = newTravelIn;
}

/**
* Remove all event listeners created within this instance of `StateControl`
*/
dispose() {
this._domElement.removeEventListener(this.TRAVEL_IN.trigger, this._handleTravelInEvent, false);
}
}

Expand Down
6 changes: 4 additions & 2 deletions test/unit/globecontrol.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,12 @@ describe('GlobeControls', function () {
assert.ok(controls.getRange() < startRange);
});

it('mouse dblclick', function (done) {
it('travel in', function (done) {
controls.setAnimationEnabled(false);
const startRange = controls.getRange();
controls.ondblclick(event).then(() => {
controls.travel({
viewCoords: viewer.eventToViewCoords(event),
}).then(() => {
assert.ok(controls.getRange() < startRange);
done();
});
Expand Down
16 changes: 14 additions & 2 deletions test/unit/statecontrol.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { MOUSE } from 'three';
import assert from 'assert';
import StateControl from 'Controls/StateControl';
import Coordinates from 'Core/Geographic/Coordinates';
import GlobeView from 'Core/Prefab/GlobeView';
import Renderer from './bootstrap';

describe('StateControl', function () {
const states = new StateControl();
const renderer = new Renderer();

const placement = { coord: new Coordinates('EPSG:4326', 2.351323, 48.856712), range: 250000, proxy: false };
const viewer = new GlobeView(renderer.domElement, placement, { renderer });
const states = viewer.controls.states;

it('inputToState should return the correct state', function () {
assert.strictEqual(
Expand Down Expand Up @@ -44,6 +50,7 @@ describe('StateControl', function () {
ORBIT: { enable: true, mouseButton: MOUSE.MIDDLE },
DOLLY: { enable: true, mouseButton: MOUSE.RIGHT },
PANORAMIC: { enable: true, mouseButton: MOUSE.LEFT, keyboard: 17 },
TRAVEL_IN: { enable: true, mouseButton: MOUSE.LEFT, double: true },
};
states.setFromOptions(options);

Expand All @@ -52,5 +59,10 @@ describe('StateControl', function () {
assert.strictEqual(JSON.stringify(options.ORBIT), JSON.stringify(states.ORBIT));
assert.strictEqual(JSON.stringify(options.DOLLY), JSON.stringify(states.DOLLY));
assert.strictEqual(JSON.stringify(options.PANORAMIC), JSON.stringify(states.PANORAMIC));
assert.strictEqual(JSON.stringify(options.TRAVEL_IN), JSON.stringify(states.TRAVEL_IN));
});

it('should dispose event listeners', function () {
states.dispose();
});
});

0 comments on commit d99827d

Please sign in to comment.