itowns
Version:
A JS/WebGL framework for 3D geospatial data visualization
529 lines (445 loc) • 20.1 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
var THREE = _interopRequireWildcard(require("three"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function () { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
var CONTROL_KEYS = {
LEFT: 37,
UP: 38,
RIGHT: 39,
BOTTOM: 40,
SPACE: 32,
SHIFT: 16,
CTRL: 17,
S: 83
}; // TODO : a class should be made for `State`, and the properties marked with `_` prefix should be made private
var DEFAULT_STATES = {
ORBIT: {
enable: true,
mouseButton: THREE.MOUSE.LEFT,
"double": false,
keyboard: CONTROL_KEYS.CTRL,
finger: 2,
_event: 'rotate'
},
MOVE_GLOBE: {
enable: true,
mouseButton: THREE.MOUSE.LEFT,
"double": false,
finger: 1,
_event: 'drag'
},
DOLLY: {
enable: true,
mouseButton: THREE.MOUSE.MIDDLE,
"double": false,
finger: 2,
_event: 'dolly'
},
PAN: {
enable: true,
mouseButton: THREE.MOUSE.RIGHT,
"double": false,
finger: 3,
_event: 'pan'
},
PANORAMIC: {
enable: true,
mouseButton: THREE.MOUSE.LEFT,
"double": false,
keyboard: CONTROL_KEYS.SHIFT,
_event: 'panoramic'
},
TRAVEL_IN: {
enable: true,
mouseButton: THREE.MOUSE.LEFT,
"double": true,
_event: 'travel_in',
_trigger: true,
_direction: 'in'
},
TRAVEL_OUT: {
enable: false,
"double": false,
_event: 'travel_out',
_trigger: true,
_direction: 'out'
},
ZOOM: {
enable: true,
_event: 'zoom',
_trigger: true
},
PAN_UP: {
enable: true,
keyboard: CONTROL_KEYS.UP,
"double": false,
_event: 'pan',
_trigger: true,
_direction: 'up'
},
PAN_BOTTOM: {
enable: true,
keyboard: CONTROL_KEYS.BOTTOM,
"double": false,
_event: 'pan',
_trigger: true,
_direction: 'bottom'
},
PAN_LEFT: {
enable: true,
keyboard: CONTROL_KEYS.LEFT,
"double": false,
_event: 'pan',
_trigger: true,
_direction: 'left'
},
PAN_RIGHT: {
enable: true,
keyboard: CONTROL_KEYS.RIGHT,
"double": false,
_event: 'pan',
_trigger: true,
_direction: 'right'
}
};
var viewCoords = new THREE.Vector2();
/**
* @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.
*/
/**
* It represents the control's states.
* Each {@link State} is a control mode of the camera and how to interact with
* the interface to activate this mode.
* @class StateControl
*
* @property {State} NONE {@link State} when camera is idle.
* @property {State} ORBIT {@link State} describing camera orbiting movement : the camera moves around its
* target at a constant distance from it.
* @property {State} DOLLY {@link State} describing camera dolly movement : the camera moves forward or
* backward from its target.
* @property {State} PAN {@link State} describing camera pan movement : the camera moves parallel to the
* current view plane.
* @property {State} MOVE_GLOBE {@link State} describing camera drag movement : the camera is moved around the view
* 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 target position depends on the key/mouse binding of this
* state. If bound to a mouse button, the target position is the mouse position.
* Otherwise, it is the center of the screen.
* @property {State} TRAVEL_OUT {@link State} describing camera travel out movement : the camera is zoomed out from
* a given position. The target position depends on the key/mouse binding of this
* state. If bound to a mouse button, the target position is the mouse position.
* Otherwise, it is the center of the screen. It is disabled by default.
* @property {State} ZOOM {@link State} describing camera zoom in and out movement.
* @property {boolean} enable Defines whether all input will be communicated to the associated `Controls` or not.
* Default is true.
* @property {boolean} enableKeys Defines whether keyboard input will be communicated to the associated `Controls` or
* not. Default is true.
*/
var StateControl = /*#__PURE__*/function (_THREE$EventDispatche) {
(0, _inherits2["default"])(StateControl, _THREE$EventDispatche);
var _super = _createSuper(StateControl);
function StateControl(view) {
var _this;
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
(0, _classCallCheck2["default"])(this, StateControl);
_this = _super.call(this);
_this._view = view;
_this._domElement = view.domElement;
var enabled = true;
Object.defineProperty((0, _assertThisInitialized2["default"])(_this), 'enabled', {
get: function get() {
return enabled;
},
set: function set(value) {
if (!value) {
_this.onKeyUp();
_this.onPointerUp();
}
enabled = value;
}
}); // Set to true to disable use of the keys
var enableKeys = true;
Object.defineProperty((0, _assertThisInitialized2["default"])(_this), 'enableKeys', {
get: function get() {
return enableKeys;
},
set: function set(value) {
if (!value) {
_this.onKeyUp();
}
enableKeys = value;
}
});
_this.NONE = {};
var currentState = _this.NONE;
Object.defineProperty((0, _assertThisInitialized2["default"])(_this), 'currentState', {
get: function get() {
return currentState;
},
set: function set(newState) {
if (currentState !== newState) {
var previous = currentState;
currentState = newState;
_this.dispatchEvent({
type: 'state-changed',
viewCoords: viewCoords,
previous: previous
});
}
}
}); // TODO : the 4 next properties should be made private when ES6 allows it
_this._clickTimeStamp = 0;
_this._lastMousePressed = {
viewCoords: new THREE.Vector2()
};
_this._currentMousePressed = undefined;
_this._currentKeyPressed = undefined;
_this._onPointerDown = _this.onPointerDown.bind((0, _assertThisInitialized2["default"])(_this));
_this._onPointerMove = _this.onPointerMove.bind((0, _assertThisInitialized2["default"])(_this));
_this._onPointerUp = _this.onPointerUp.bind((0, _assertThisInitialized2["default"])(_this));
_this._onMouseWheel = _this.onMouseWheel.bind((0, _assertThisInitialized2["default"])(_this));
_this._onKeyDown = _this.onKeyDown.bind((0, _assertThisInitialized2["default"])(_this));
_this._onKeyUp = _this.onKeyUp.bind((0, _assertThisInitialized2["default"])(_this));
_this._onBlur = _this.onBlur.bind((0, _assertThisInitialized2["default"])(_this));
_this._onContextMenu = _this.onContextMenu.bind((0, _assertThisInitialized2["default"])(_this));
_this._domElement.addEventListener('pointerdown', _this._onPointerDown, false);
_this._domElement.addEventListener('wheel', _this._onMouseWheel, false); // The event listener is added on `window` so that key input can be accounted event if the view does not have
// the focus. This can occur at page loading, when a mini map is displayed : the minimap initially has the focus
// and key input would not be considered on the view's domElement.
window.addEventListener('keydown', _this._onKeyDown, false);
window.addEventListener('keyup', _this._onKeyUp, false); // Reset key/mouse when window loose focus
window.addEventListener('blur', _this._onBlur); // disable context menu when right-clicking
_this._domElement.addEventListener('contextmenu', _this._onContextMenu, false);
_this.setFromOptions(options);
return _this;
}
/**
* get the state corresponding to the mouse button and the keyboard key. If the input relates to a trigger - a
* single event which triggers movement, without the move of the mouse for instance -, dispatch a relevant event.
* @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
*/
(0, _createClass2["default"])(StateControl, [{
key: "inputToState",
value: function inputToState(mouseButton, keyboard) {
var _double = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
for (var _i = 0, _Object$keys = Object.keys(DEFAULT_STATES); _i < _Object$keys.length; _i++) {
var key = _Object$keys[_i];
var state = this[key];
if (state.enable && state.mouseButton === mouseButton && state.keyboard === keyboard && state["double"] === _double) {
// If the input relates to a state, returns it
if (!state._trigger) {
return state;
} // If the input relates to a trigger (TRAVEL_IN, TRAVEL_OUT), dispatch a relevant event.
this.dispatchEvent({
type: state._event,
// Dont pass viewCoords if the input is only a keyboard input.
viewCoords: mouseButton !== undefined && viewCoords,
direction: state._direction
});
}
}
return this.NONE;
}
/**
* get the state corresponding to the number of finger on the pad
*
* @param {Number} finger The number of finger
* @return {state} the state corresponding
*/
}, {
key: "touchToState",
value: function touchToState(finger) {
for (var _i2 = 0, _Object$keys2 = Object.keys(DEFAULT_STATES); _i2 < _Object$keys2.length; _i2++) {
var key = _Object$keys2[_i2];
var state = this[key];
if (state.enable && finger == state.finger) {
return state;
}
}
return this.NONE;
}
/**
* Set the current StateControl {@link State} properties to given values.
* @param {Object} options Object containing the `State` values to set current `StateControl` properties to.
* The `enable` property do not necessarily need to be specified. In that case, the
* previous value of this property will be kept for the new {@link State}.
*
* @example
* // Switch bindings for PAN and MOVE_GLOBE actions, and disabling PANORAMIC movement :
* view.controls.states.setFromOptions({
* PAN: {
* mouseButton: itowns.THREE.MOUSE.LEFT,
* },
* MOVE_GLOBE: {
* mouseButton: itowns.THREE.MOUSE.RIGHT,
* },
* PANORAMIC: {
* enable: false,
* },
* };
*/
}, {
key: "setFromOptions",
value: function setFromOptions(options) {
for (var state in DEFAULT_STATES) {
if ({}.hasOwnProperty.call(DEFAULT_STATES, state)) {
var newState = {};
newState = options[state] || this[state] || Object.assign(newState, DEFAULT_STATES[state]); // Copy the previous value of `enable` property if not defined in options
if (options[state] && options[state].enable === undefined) {
newState.enable = this[state].enable;
} // If no value is provided for the `double` property,
// defaults it to `false` instead of leaving it undefined
newState["double"] = !!newState["double"]; // Copy the `_event` and `_trigger` properties
newState._event = DEFAULT_STATES[state]._event;
newState._trigger = DEFAULT_STATES[state]._trigger;
newState._direction = DEFAULT_STATES[state]._direction;
this[state] = newState;
}
}
} // ---------- POINTER EVENTS : ----------
}, {
key: "onPointerDown",
value: function onPointerDown(event) {
if (!this.enabled) {
return;
}
viewCoords.copy(this._view.eventToViewCoords(event));
switch (event.pointerType) {
case 'mouse':
this._currentMousePressed = event.button;
this.currentState = this.inputToState(this._currentMousePressed, this._currentKeyPressed, // Detect if the mouse button was pressed less than 500 ms before, and if the cursor has not moved two much
// since previous click. If so, set dblclick to true.
event.timeStamp - this._clickTimeStamp < 500 && this._lastMousePressed.button === this._currentMousePressed && this._lastMousePressed.viewCoords.distanceTo(viewCoords) < 5);
this._clickTimeStamp = event.timeStamp;
this._lastMousePressed.button = this._currentMousePressed;
this._lastMousePressed.viewCoords.copy(viewCoords);
break;
// TODO : add touch event management
default:
}
this._domElement.addEventListener('pointermove', this._onPointerMove, false);
this._domElement.addEventListener('pointerup', this._onPointerUp, false);
this._domElement.addEventListener('mouseleave', this._onPointerUp, false);
}
}, {
key: "onPointerMove",
value: function onPointerMove(event) {
event.preventDefault();
if (!this.enabled) {
return;
}
viewCoords.copy(this._view.eventToViewCoords(event));
switch (event.pointerType) {
case 'mouse':
this.dispatchEvent({
type: this.currentState._event,
viewCoords: viewCoords
});
break;
// TODO : add touch event management
default:
}
}
}, {
key: "onPointerUp",
value: function onPointerUp() {
if (!this.enabled) {
return;
}
this._currentMousePressed = undefined;
this._domElement.removeEventListener('pointermove', this._onPointerMove, false);
this._domElement.removeEventListener('pointerup', this._onPointerUp, false);
this._domElement.removeEventListener('mouseleave', this._onPointerUp, false);
this.currentState = this.NONE;
} // ---------- WHEEL EVENT : ----------
}, {
key: "onMouseWheel",
value: function onMouseWheel(event) {
event.preventDefault();
if (this.enabled && this.ZOOM.enable) {
this.dispatchEvent({
type: this.ZOOM._event,
delta: event.deltaY
});
}
} // ---------- KEYBOARD EVENTS : ----------
}, {
key: "onKeyDown",
value: function onKeyDown(event) {
if (!this.enabled || !this.enableKeys) {
return;
}
this._currentKeyPressed = event.keyCode;
this.inputToState(this._currentMousePressed, this._currentKeyPressed);
}
}, {
key: "onKeyUp",
value: function onKeyUp() {
if (!this.enabled || !this.enableKeys) {
return;
}
this._currentKeyPressed = undefined;
if (this._currentMousePressed === undefined) {
this.currentState = this.NONE;
}
}
}, {
key: "onBlur",
value: function onBlur() {
this.onKeyUp();
this.onPointerUp();
}
}, {
key: "onContextMenu",
value: function onContextMenu(event) {
event.preventDefault();
}
/**
* Remove all event listeners created within this instance of `StateControl`
*/
}, {
key: "dispose",
value: function dispose() {
this._clickTimeStamp = 0;
this._lastMousePressed = undefined;
this._currentKeyPressed = undefined;
this._domElement.removeEventListener('pointerdown', this._onPointerDown, false);
this._domElement.removeEventListener('pointermove', this._onPointerMove, false);
this._domElement.removeEventListener('pointerup', this._onPointerUp, false);
this._domElement.removeEventListener('wheel', this._onMouseWheel, false);
this._domElement.removeEventListener('keydown', this._onKeyDown, false);
this._domElement.removeEventListener('keyup', this._onKeyUp, false);
window.removeEventListener('blur', this._onBlur);
this._domElement.removeEventListener('contextmenu', this._onContextMenu, false);
}
}]);
return StateControl;
}(THREE.EventDispatcher);
var _default = StateControl;
exports["default"] = _default;