@sauskylark/potree
Version:
WebGL point cloud viewer
719 lines (580 loc) • 16.8 kB
JavaScript
/**
* @author mschuetz / http://mschuetz.at
*
*
*/
import * as THREE from "../../libs/three.js/build/three.module.js";
import {KeyCodes} from "../KeyCodes.js";
import {Utils} from "../utils.js";
import {EventDispatcher} from "../EventDispatcher.js";
export class InputHandler extends EventDispatcher {
constructor (viewer) {
super();
this.viewer = viewer;
this.renderer = viewer.renderer;
this.domElement = this.renderer.domElement;
this.enabled = true;
this.scene = null;
this.interactiveScenes = [];
this.interactiveObjects = new Set();
this.inputListeners = [];
this.blacklist = new Set();
this.drag = null;
this.mouse = new THREE.Vector2(0, 0);
this.selection = [];
this.hoveredElements = [];
this.pressedKeys = {};
this.wheelDelta = 0;
this.speed = 1;
this.logMessages = false;
if (this.domElement.tabIndex === -1) {
this.domElement.tabIndex = 2222;
}
this.domElement.addEventListener('contextmenu', (event) => { event.preventDefault(); }, false);
this.domElement.addEventListener('click', this.onMouseClick.bind(this), false);
this.domElement.addEventListener('mousedown', this.onMouseDown.bind(this), false);
this.domElement.addEventListener('mouseup', this.onMouseUp.bind(this), false);
this.domElement.addEventListener('mousemove', this.onMouseMove.bind(this), false);
this.domElement.addEventListener('mousewheel', this.onMouseWheel.bind(this), false);
this.domElement.addEventListener('DOMMouseScroll', this.onMouseWheel.bind(this), false); // Firefox
this.domElement.addEventListener('dblclick', this.onDoubleClick.bind(this));
this.domElement.addEventListener('keydown', this.onKeyDown.bind(this));
this.domElement.addEventListener('keyup', this.onKeyUp.bind(this));
this.domElement.addEventListener('touchstart', this.onTouchStart.bind(this));
this.domElement.addEventListener('touchend', this.onTouchEnd.bind(this));
this.domElement.addEventListener('touchmove', this.onTouchMove.bind(this));
}
addInputListener (listener) {
this.inputListeners.push(listener);
}
removeInputListener (listener) {
this.inputListeners = this.inputListeners.filter(e => e !== listener);
}
getSortedListeners(){
return this.inputListeners.sort( (a, b) => {
let ia = (a.importance !== undefined) ? a.importance : 0;
let ib = (b.importance !== undefined) ? b.importance : 0;
return ib - ia;
});
}
onTouchStart (e) {
if (this.logMessages) console.log(this.constructor.name + ': onTouchStart');
e.preventDefault();
if (e.touches.length === 1) {
let rect = this.domElement.getBoundingClientRect();
let x = e.touches[0].pageX - rect.left;
let y = e.touches[0].pageY - rect.top;
this.mouse.set(x, y);
this.startDragging(null);
}
for (let inputListener of this.getSortedListeners()) {
inputListener.dispatchEvent({
type: e.type,
touches: e.touches,
changedTouches: e.changedTouches
});
}
}
onTouchEnd (e) {
if (this.logMessages) console.log(this.constructor.name + ': onTouchEnd');
e.preventDefault();
for (let inputListener of this.getSortedListeners()) {
inputListener.dispatchEvent({
type: 'drop',
drag: this.drag,
viewer: this.viewer
});
}
this.drag = null;
for (let inputListener of this.getSortedListeners()) {
inputListener.dispatchEvent({
type: e.type,
touches: e.touches,
changedTouches: e.changedTouches
});
}
}
onTouchMove (e) {
if (this.logMessages) console.log(this.constructor.name + ': onTouchMove');
e.preventDefault();
if (e.touches.length === 1) {
let rect = this.domElement.getBoundingClientRect();
let x = e.touches[0].pageX - rect.left;
let y = e.touches[0].pageY - rect.top;
this.mouse.set(x, y);
if (this.drag) {
this.drag.mouse = 1;
this.drag.lastDrag.x = x - this.drag.end.x;
this.drag.lastDrag.y = y - this.drag.end.y;
this.drag.end.set(x, y);
if (this.logMessages) console.log(this.constructor.name + ': drag: ');
for (let inputListener of this.getSortedListeners()) {
inputListener.dispatchEvent({
type: 'drag',
drag: this.drag,
viewer: this.viewer
});
}
}
}
for (let inputListener of this.getSortedListeners()) {
inputListener.dispatchEvent({
type: e.type,
touches: e.touches,
changedTouches: e.changedTouches
});
}
// DEBUG CODE
// let debugTouches = [...e.touches, {
// pageX: this.domElement.clientWidth / 2,
// pageY: this.domElement.clientHeight / 2}];
// for(let inputListener of this.getSortedListeners()){
// inputListener.dispatchEvent({
// type: e.type,
// touches: debugTouches,
// changedTouches: e.changedTouches
// });
// }
}
onKeyDown (e) {
if (this.logMessages) console.log(this.constructor.name + ': onKeyDown');
// DELETE
if (e.keyCode === KeyCodes.DELETE && this.selection.length > 0) {
this.dispatchEvent({
type: 'delete',
selection: this.selection
});
this.deselectAll();
}
this.dispatchEvent({
type: 'keydown',
keyCode: e.keyCode,
event: e
});
// for(let l of this.getSortedListeners()){
// l.dispatchEvent({
// type: "keydown",
// keyCode: e.keyCode,
// event: e
// });
// }
this.pressedKeys[e.keyCode] = true;
// e.preventDefault();
}
onKeyUp (e) {
if (this.logMessages) console.log(this.constructor.name + ': onKeyUp');
delete this.pressedKeys[e.keyCode];
e.preventDefault();
}
onDoubleClick (e) {
if (this.logMessages) console.log(this.constructor.name + ': onDoubleClick');
let consumed = false;
for (let hovered of this.hoveredElements) {
if (hovered._listeners && hovered._listeners['dblclick']) {
hovered.object.dispatchEvent({
type: 'dblclick',
mouse: this.mouse,
object: hovered.object
});
consumed = true;
break;
}
}
if (!consumed) {
for (let inputListener of this.getSortedListeners()) {
inputListener.dispatchEvent({
type: 'dblclick',
mouse: this.mouse,
object: null
});
}
}
e.preventDefault();
}
onMouseClick (e) {
if (this.logMessages) console.log(this.constructor.name + ': onMouseClick');
e.preventDefault();
}
onMouseDown (e) {
if (this.logMessages) console.log(this.constructor.name + ': onMouseDown');
e.preventDefault();
let consumed = false;
let consume = () => { return consumed = true; };
if (this.hoveredElements.length === 0) {
for (let inputListener of this.getSortedListeners()) {
inputListener.dispatchEvent({
type: 'mousedown',
viewer: this.viewer,
mouse: this.mouse
});
}
}else{
for(let hovered of this.hoveredElements){
let object = hovered.object;
object.dispatchEvent({
type: 'mousedown',
viewer: this.viewer,
consume: consume
});
if(consumed){
break;
}
}
}
if (!this.drag) {
let target = this.hoveredElements
.find(el => (
el.object._listeners &&
el.object._listeners['drag'] &&
el.object._listeners['drag'].length > 0));
if (target) {
this.startDragging(target.object, {location: target.point});
} else {
this.startDragging(null);
}
}
if (this.scene) {
this.viewStart = this.scene.view.clone();
}
}
onMouseUp (e) {
if (this.logMessages) console.log(this.constructor.name + ': onMouseUp');
e.preventDefault();
let noMovement = this.getNormalizedDrag().length() === 0;
let consumed = false;
let consume = () => { return consumed = true; };
if (this.hoveredElements.length === 0) {
for (let inputListener of this.getSortedListeners()) {
inputListener.dispatchEvent({
type: 'mouseup',
viewer: this.viewer,
mouse: this.mouse,
consume: consume
});
if(consumed){
break;
}
}
}else{
let hovered = this.hoveredElements
.map(e => e.object)
.find(e => (e._listeners && e._listeners['mouseup']));
if(hovered){
hovered.dispatchEvent({
type: 'mouseup',
viewer: this.viewer,
consume: consume
});
}
}
if (this.drag) {
if (this.drag.object) {
if (this.logMessages) console.log(`${this.constructor.name}: drop ${this.drag.object.name}`);
this.drag.object.dispatchEvent({
type: 'drop',
drag: this.drag,
viewer: this.viewer
});
} else {
for (let inputListener of this.getSortedListeners()) {
inputListener.dispatchEvent({
type: 'drop',
drag: this.drag,
viewer: this.viewer
});
}
}
// check for a click
let clicked = this.hoveredElements.map(h => h.object).find(v => v === this.drag.object) !== undefined;
if(clicked){
if (this.logMessages) console.log(`${this.constructor.name}: click ${this.drag.object.name}`);
this.drag.object.dispatchEvent({
type: 'click',
viewer: this.viewer,
consume: consume,
});
}
this.drag = null;
}
if(!consumed){
if (e.button === THREE.MOUSE.LEFT) {
if (noMovement) {
let selectable = this.hoveredElements
.find(el => el.object._listeners && el.object._listeners['select']);
if (selectable) {
selectable = selectable.object;
if (this.isSelected(selectable)) {
this.selection
.filter(e => e !== selectable)
.forEach(e => this.toggleSelection(e));
} else {
this.deselectAll();
this.toggleSelection(selectable);
}
} else {
this.deselectAll();
}
}
} else if ((e.button === THREE.MOUSE.RIGHT) && noMovement) {
this.deselectAll();
}
}
}
onMouseMove (e) {
e.preventDefault();
let rect = this.domElement.getBoundingClientRect();
let x = e.clientX - rect.left;
let y = e.clientY - rect.top;
this.mouse.set(x, y);
let hoveredElements = this.getHoveredElements();
if(hoveredElements.length > 0){
let names = hoveredElements.map(h => h.object.name).join(", ");
if (this.logMessages) console.log(`${this.constructor.name}: onMouseMove; hovered: '${names}'`);
}
if (this.drag) {
this.drag.mouse = e.buttons;
this.drag.lastDrag.x = x - this.drag.end.x;
this.drag.lastDrag.y = y - this.drag.end.y;
this.drag.end.set(x, y);
if (this.drag.object) {
if (this.logMessages) console.log(this.constructor.name + ': drag: ' + this.drag.object.name);
this.drag.object.dispatchEvent({
type: 'drag',
drag: this.drag,
viewer: this.viewer
});
} else {
if (this.logMessages) console.log(this.constructor.name + ': drag: ');
let dragConsumed = false;
for (let inputListener of this.getSortedListeners()) {
inputListener.dispatchEvent({
type: 'drag',
drag: this.drag,
viewer: this.viewer,
consume: () => {dragConsumed = true;}
});
if(dragConsumed){
break;
}
}
}
}else{
let curr = hoveredElements.map(a => a.object).find(a => true);
let prev = this.hoveredElements.map(a => a.object).find(a => true);
if(curr !== prev){
if(curr){
if (this.logMessages) console.log(`${this.constructor.name}: mouseover: ${curr.name}`);
curr.dispatchEvent({
type: 'mouseover',
object: curr,
});
}
if(prev){
if (this.logMessages) console.log(`${this.constructor.name}: mouseleave: ${prev.name}`);
prev.dispatchEvent({
type: 'mouseleave',
object: prev,
});
}
}
if(hoveredElements.length > 0){
let object = hoveredElements
.map(e => e.object)
.find(e => (e._listeners && e._listeners['mousemove']));
if(object){
object.dispatchEvent({
type: 'mousemove',
object: object
});
}
}
}
// for (let inputListener of this.getSortedListeners()) {
// inputListener.dispatchEvent({
// type: 'mousemove',
// object: null
// });
// }
this.hoveredElements = hoveredElements;
}
onMouseWheel(e){
if(!this.enabled) return;
if(this.logMessages) console.log(this.constructor.name + ": onMouseWheel");
e.preventDefault();
let delta = 0;
if (e.wheelDelta !== undefined) { // WebKit / Opera / Explorer 9
delta = e.wheelDelta;
} else if (e.detail !== undefined) { // Firefox
delta = -e.detail;
}
let ndelta = Math.sign(delta);
// this.wheelDelta += Math.sign(delta);
if (this.hoveredElement) {
this.hoveredElement.object.dispatchEvent({
type: 'mousewheel',
delta: ndelta,
object: this.hoveredElement.object
});
} else {
for (let inputListener of this.getSortedListeners()) {
inputListener.dispatchEvent({
type: 'mousewheel',
delta: ndelta,
object: null
});
}
}
}
startDragging (object, args = null) {
let name = object ? object.name : "no name";
if (this.logMessages) console.log(`${this.constructor.name}: startDragging: '${name}'`);
this.drag = {
start: this.mouse.clone(),
end: this.mouse.clone(),
lastDrag: new THREE.Vector2(0, 0),
startView: this.scene.view.clone(),
object: object
};
if (args) {
for (let key of Object.keys(args)) {
this.drag[key] = args[key];
}
}
}
getMousePointCloudIntersection (mouse) {
return Utils.getMousePointCloudIntersection(
this.mouse,
this.scene.getActiveCamera(),
this.viewer,
this.scene.pointclouds);
}
toggleSelection (object) {
let oldSelection = this.selection;
let index = this.selection.indexOf(object);
if (index === -1) {
this.selection.push(object);
object.dispatchEvent({
type: 'select'
});
} else {
this.selection.splice(index, 1);
object.dispatchEvent({
type: 'deselect'
});
}
this.dispatchEvent({
type: 'selection_changed',
oldSelection: oldSelection,
selection: this.selection
});
}
deselect(object){
let oldSelection = this.selection;
let index = this.selection.indexOf(object);
if(index >= 0){
this.selection.splice(index, 1);
object.dispatchEvent({
type: 'deselect'
});
this.dispatchEvent({
type: 'selection_changed',
oldSelection: oldSelection,
selection: this.selection
});
}
}
deselectAll () {
for (let object of this.selection) {
object.dispatchEvent({
type: 'deselect'
});
}
let oldSelection = this.selection;
if (this.selection.length > 0) {
this.selection = [];
this.dispatchEvent({
type: 'selection_changed',
oldSelection: oldSelection,
selection: this.selection
});
}
}
isSelected (object) {
let index = this.selection.indexOf(object);
return index !== -1;
}
registerInteractiveObject(object){
this.interactiveObjects.add(object);
}
removeInteractiveObject(object){
this.interactiveObjects.delete(object);
}
registerInteractiveScene (scene) {
let index = this.interactiveScenes.indexOf(scene);
if (index === -1) {
this.interactiveScenes.push(scene);
}
}
unregisterInteractiveScene (scene) {
let index = this.interactiveScenes.indexOf(scene);
if (index > -1) {
this.interactiveScenes.splice(index, 1);
}
}
getHoveredElement () {
let hoveredElements = this.getHoveredElements();
if (hoveredElements.length > 0) {
return hoveredElements[0];
} else {
return null;
}
}
getHoveredElements () {
let scenes = this.interactiveScenes.concat(this.scene.scene);
let interactableListeners = ['mouseup', 'mousemove', 'mouseover', 'mouseleave', 'drag', 'drop', 'click', 'select', 'deselect'];
let interactables = [];
for (let scene of scenes) {
scene.traverseVisible(node => {
if (node._listeners && node.visible && !this.blacklist.has(node)) {
let hasInteractableListener = interactableListeners.filter((e) => {
return node._listeners[e] !== undefined;
}).length > 0;
if (hasInteractableListener) {
interactables.push(node);
}
}
});
}
let camera = this.scene.getActiveCamera();
let ray = Utils.mouseToRay(this.mouse, camera, this.domElement.clientWidth, this.domElement.clientHeight);
let raycaster = new THREE.Raycaster();
raycaster.ray.set(ray.origin, ray.direction);
raycaster.params.Line.threshold = 0.2;
let intersections = raycaster.intersectObjects(interactables.filter(o => o.visible), false);
return intersections;
}
setScene (scene) {
this.deselectAll();
this.scene = scene;
}
update (delta) {
}
getNormalizedDrag () {
if (!this.drag) {
return new THREE.Vector2(0, 0);
}
let diff = new THREE.Vector2().subVectors(this.drag.end, this.drag.start);
diff.x = diff.x / this.domElement.clientWidth;
diff.y = diff.y / this.domElement.clientHeight;
return diff;
}
getNormalizedLastDrag () {
if (!this.drag) {
return new THREE.Vector2(0, 0);
}
let lastDrag = this.drag.lastDrag.clone();
lastDrag.x = lastDrag.x / this.domElement.clientWidth;
lastDrag.y = lastDrag.y / this.domElement.clientHeight;
return lastDrag;
}
}