InputHandler.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718
  1. /**
  2. * @author mschuetz / http://mschuetz.at
  3. *
  4. *
  5. */
  6. import * as THREE from "../../libs/three.js/build/three.module.js";
  7. import {KeyCodes} from "../KeyCodes.js";
  8. import {Utils} from "../utils.js";
  9. import {EventDispatcher} from "../EventDispatcher.js";
  10. export class InputHandler extends EventDispatcher {
  11. constructor (viewer) {
  12. super();
  13. this.viewer = viewer;
  14. this.renderer = viewer.renderer;
  15. this.domElement = this.renderer.domElement;
  16. this.enabled = true;
  17. this.scene = null;
  18. this.interactiveScenes = [];
  19. this.interactiveObjects = new Set();
  20. this.inputListeners = [];
  21. this.blacklist = new Set();
  22. this.drag = null;
  23. this.mouse = new THREE.Vector2(0, 0);
  24. this.selection = [];
  25. this.hoveredElements = [];
  26. this.pressedKeys = {};
  27. this.wheelDelta = 0;
  28. this.speed = 1;
  29. this.logMessages = false;
  30. if (this.domElement.tabIndex === -1) {
  31. this.domElement.tabIndex = 2222;
  32. }
  33. this.domElement.addEventListener('contextmenu', (event) => { event.preventDefault(); }, false);
  34. this.domElement.addEventListener('click', this.onMouseClick.bind(this), false);
  35. this.domElement.addEventListener('mousedown', this.onMouseDown.bind(this), false);
  36. this.domElement.addEventListener('mouseup', this.onMouseUp.bind(this), false);
  37. this.domElement.addEventListener('mousemove', this.onMouseMove.bind(this), false);
  38. this.domElement.addEventListener('mousewheel', this.onMouseWheel.bind(this), false);
  39. this.domElement.addEventListener('DOMMouseScroll', this.onMouseWheel.bind(this), false); // Firefox
  40. this.domElement.addEventListener('dblclick', this.onDoubleClick.bind(this));
  41. this.domElement.addEventListener('keydown', this.onKeyDown.bind(this));
  42. this.domElement.addEventListener('keyup', this.onKeyUp.bind(this));
  43. this.domElement.addEventListener('touchstart', this.onTouchStart.bind(this));
  44. this.domElement.addEventListener('touchend', this.onTouchEnd.bind(this));
  45. this.domElement.addEventListener('touchmove', this.onTouchMove.bind(this));
  46. }
  47. addInputListener (listener) {
  48. this.inputListeners.push(listener);
  49. }
  50. removeInputListener (listener) {
  51. this.inputListeners = this.inputListeners.filter(e => e !== listener);
  52. }
  53. getSortedListeners(){
  54. return this.inputListeners.sort( (a, b) => {
  55. let ia = (a.importance !== undefined) ? a.importance : 0;
  56. let ib = (b.importance !== undefined) ? b.importance : 0;
  57. return ib - ia;
  58. });
  59. }
  60. onTouchStart (e) {
  61. if (this.logMessages) console.log(this.constructor.name + ': onTouchStart');
  62. e.preventDefault();
  63. if (e.touches.length === 1) {
  64. let rect = this.domElement.getBoundingClientRect();
  65. let x = e.touches[0].pageX - rect.left;
  66. let y = e.touches[0].pageY - rect.top;
  67. this.mouse.set(x, y);
  68. this.startDragging(null);
  69. }
  70. for (let inputListener of this.getSortedListeners()) {
  71. inputListener.dispatchEvent({
  72. type: e.type,
  73. touches: e.touches,
  74. changedTouches: e.changedTouches
  75. });
  76. }
  77. }
  78. onTouchEnd (e) {
  79. if (this.logMessages) console.log(this.constructor.name + ': onTouchEnd');
  80. e.preventDefault();
  81. for (let inputListener of this.getSortedListeners()) {
  82. inputListener.dispatchEvent({
  83. type: 'drop',
  84. drag: this.drag,
  85. viewer: this.viewer
  86. });
  87. }
  88. this.drag = null;
  89. for (let inputListener of this.getSortedListeners()) {
  90. inputListener.dispatchEvent({
  91. type: e.type,
  92. touches: e.touches,
  93. changedTouches: e.changedTouches
  94. });
  95. }
  96. }
  97. onTouchMove (e) {
  98. if (this.logMessages) console.log(this.constructor.name + ': onTouchMove');
  99. e.preventDefault();
  100. if (e.touches.length === 1) {
  101. let rect = this.domElement.getBoundingClientRect();
  102. let x = e.touches[0].pageX - rect.left;
  103. let y = e.touches[0].pageY - rect.top;
  104. this.mouse.set(x, y);
  105. if (this.drag) {
  106. this.drag.mouse = 1;
  107. this.drag.lastDrag.x = x - this.drag.end.x;
  108. this.drag.lastDrag.y = y - this.drag.end.y;
  109. this.drag.end.set(x, y);
  110. if (this.logMessages) console.log(this.constructor.name + ': drag: ');
  111. for (let inputListener of this.getSortedListeners()) {
  112. inputListener.dispatchEvent({
  113. type: 'drag',
  114. drag: this.drag,
  115. viewer: this.viewer
  116. });
  117. }
  118. }
  119. }
  120. for (let inputListener of this.getSortedListeners()) {
  121. inputListener.dispatchEvent({
  122. type: e.type,
  123. touches: e.touches,
  124. changedTouches: e.changedTouches
  125. });
  126. }
  127. // DEBUG CODE
  128. // let debugTouches = [...e.touches, {
  129. // pageX: this.domElement.clientWidth / 2,
  130. // pageY: this.domElement.clientHeight / 2}];
  131. // for(let inputListener of this.getSortedListeners()){
  132. // inputListener.dispatchEvent({
  133. // type: e.type,
  134. // touches: debugTouches,
  135. // changedTouches: e.changedTouches
  136. // });
  137. // }
  138. }
  139. onKeyDown (e) {
  140. if (this.logMessages) console.log(this.constructor.name + ': onKeyDown');
  141. // DELETE
  142. if (e.keyCode === KeyCodes.DELETE && this.selection.length > 0) {
  143. this.dispatchEvent({
  144. type: 'delete',
  145. selection: this.selection
  146. });
  147. this.deselectAll();
  148. }
  149. this.dispatchEvent({
  150. type: 'keydown',
  151. keyCode: e.keyCode,
  152. event: e
  153. });
  154. // for(let l of this.getSortedListeners()){
  155. // l.dispatchEvent({
  156. // type: "keydown",
  157. // keyCode: e.keyCode,
  158. // event: e
  159. // });
  160. // }
  161. this.pressedKeys[e.keyCode] = true;
  162. // e.preventDefault();
  163. }
  164. onKeyUp (e) {
  165. if (this.logMessages) console.log(this.constructor.name + ': onKeyUp');
  166. delete this.pressedKeys[e.keyCode];
  167. e.preventDefault();
  168. }
  169. onDoubleClick (e) {
  170. if (this.logMessages) console.log(this.constructor.name + ': onDoubleClick');
  171. let consumed = false;
  172. for (let hovered of this.hoveredElements) {
  173. if (hovered._listeners && hovered._listeners['dblclick']) {
  174. hovered.object.dispatchEvent({
  175. type: 'dblclick',
  176. mouse: this.mouse,
  177. object: hovered.object
  178. });
  179. consumed = true;
  180. break;
  181. }
  182. }
  183. if (!consumed) {
  184. for (let inputListener of this.getSortedListeners()) {
  185. inputListener.dispatchEvent({
  186. type: 'dblclick',
  187. mouse: this.mouse,
  188. object: null
  189. });
  190. }
  191. }
  192. e.preventDefault();
  193. }
  194. onMouseClick (e) {
  195. if (this.logMessages) console.log(this.constructor.name + ': onMouseClick');
  196. e.preventDefault();
  197. }
  198. onMouseDown (e) {
  199. if (this.logMessages) console.log(this.constructor.name + ': onMouseDown');
  200. e.preventDefault();
  201. let consumed = false;
  202. let consume = () => { return consumed = true; };
  203. if (this.hoveredElements.length === 0) {
  204. for (let inputListener of this.getSortedListeners()) {
  205. inputListener.dispatchEvent({
  206. type: 'mousedown',
  207. viewer: this.viewer,
  208. mouse: this.mouse
  209. });
  210. }
  211. }else{
  212. for(let hovered of this.hoveredElements){
  213. let object = hovered.object;
  214. object.dispatchEvent({
  215. type: 'mousedown',
  216. viewer: this.viewer,
  217. consume: consume
  218. });
  219. if(consumed){
  220. break;
  221. }
  222. }
  223. }
  224. if (!this.drag) {
  225. let target = this.hoveredElements
  226. .find(el => (
  227. el.object._listeners &&
  228. el.object._listeners['drag'] &&
  229. el.object._listeners['drag'].length > 0));
  230. if (target) {
  231. this.startDragging(target.object, {location: target.point});
  232. } else {
  233. this.startDragging(null);
  234. }
  235. }
  236. if (this.scene) {
  237. this.viewStart = this.scene.view.clone();
  238. }
  239. }
  240. onMouseUp (e) {
  241. if (this.logMessages) console.log(this.constructor.name + ': onMouseUp');
  242. e.preventDefault();
  243. let noMovement = this.getNormalizedDrag().length() === 0;
  244. let consumed = false;
  245. let consume = () => { return consumed = true; };
  246. if (this.hoveredElements.length === 0) {
  247. for (let inputListener of this.getSortedListeners()) {
  248. inputListener.dispatchEvent({
  249. type: 'mouseup',
  250. viewer: this.viewer,
  251. mouse: this.mouse,
  252. consume: consume
  253. });
  254. if(consumed){
  255. break;
  256. }
  257. }
  258. }else{
  259. let hovered = this.hoveredElements
  260. .map(e => e.object)
  261. .find(e => (e._listeners && e._listeners['mouseup']));
  262. if(hovered){
  263. hovered.dispatchEvent({
  264. type: 'mouseup',
  265. viewer: this.viewer,
  266. consume: consume
  267. });
  268. }
  269. }
  270. if (this.drag) {
  271. if (this.drag.object) {
  272. if (this.logMessages) console.log(`${this.constructor.name}: drop ${this.drag.object.name}`);
  273. this.drag.object.dispatchEvent({
  274. type: 'drop',
  275. drag: this.drag,
  276. viewer: this.viewer
  277. });
  278. } else {
  279. for (let inputListener of this.getSortedListeners()) {
  280. inputListener.dispatchEvent({
  281. type: 'drop',
  282. drag: this.drag,
  283. viewer: this.viewer
  284. });
  285. }
  286. }
  287. // check for a click
  288. let clicked = this.hoveredElements.map(h => h.object).find(v => v === this.drag.object) !== undefined;
  289. if(clicked){
  290. if (this.logMessages) console.log(`${this.constructor.name}: click ${this.drag.object.name}`);
  291. this.drag.object.dispatchEvent({
  292. type: 'click',
  293. viewer: this.viewer,
  294. consume: consume,
  295. });
  296. }
  297. this.drag = null;
  298. }
  299. if(!consumed){
  300. if (e.button === THREE.MOUSE.LEFT) {
  301. if (noMovement) {
  302. let selectable = this.hoveredElements
  303. .find(el => el.object._listeners && el.object._listeners['select']);
  304. if (selectable) {
  305. selectable = selectable.object;
  306. if (this.isSelected(selectable)) {
  307. this.selection
  308. .filter(e => e !== selectable)
  309. .forEach(e => this.toggleSelection(e));
  310. } else {
  311. this.deselectAll();
  312. this.toggleSelection(selectable);
  313. }
  314. } else {
  315. this.deselectAll();
  316. }
  317. }
  318. } else if ((e.button === THREE.MOUSE.RIGHT) && noMovement) {
  319. this.deselectAll();
  320. }
  321. }
  322. }
  323. onMouseMove (e) {
  324. e.preventDefault();
  325. let rect = this.domElement.getBoundingClientRect();
  326. let x = e.clientX - rect.left;
  327. let y = e.clientY - rect.top;
  328. this.mouse.set(x, y);
  329. let hoveredElements = this.getHoveredElements();
  330. if(hoveredElements.length > 0){
  331. let names = hoveredElements.map(h => h.object.name).join(", ");
  332. if (this.logMessages) console.log(`${this.constructor.name}: onMouseMove; hovered: '${names}'`);
  333. }
  334. if (this.drag) {
  335. this.drag.mouse = e.buttons;
  336. this.drag.lastDrag.x = x - this.drag.end.x;
  337. this.drag.lastDrag.y = y - this.drag.end.y;
  338. this.drag.end.set(x, y);
  339. if (this.drag.object) {
  340. if (this.logMessages) console.log(this.constructor.name + ': drag: ' + this.drag.object.name);
  341. this.drag.object.dispatchEvent({
  342. type: 'drag',
  343. drag: this.drag,
  344. viewer: this.viewer
  345. });
  346. } else {
  347. if (this.logMessages) console.log(this.constructor.name + ': drag: ');
  348. let dragConsumed = false;
  349. for (let inputListener of this.getSortedListeners()) {
  350. inputListener.dispatchEvent({
  351. type: 'drag',
  352. drag: this.drag,
  353. viewer: this.viewer,
  354. consume: () => {dragConsumed = true;}
  355. });
  356. if(dragConsumed){
  357. break;
  358. }
  359. }
  360. }
  361. }else{
  362. let curr = hoveredElements.map(a => a.object).find(a => true);
  363. let prev = this.hoveredElements.map(a => a.object).find(a => true);
  364. if(curr !== prev){
  365. if(curr){
  366. if (this.logMessages) console.log(`${this.constructor.name}: mouseover: ${curr.name}`);
  367. curr.dispatchEvent({
  368. type: 'mouseover',
  369. object: curr,
  370. });
  371. }
  372. if(prev){
  373. if (this.logMessages) console.log(`${this.constructor.name}: mouseleave: ${prev.name}`);
  374. prev.dispatchEvent({
  375. type: 'mouseleave',
  376. object: prev,
  377. });
  378. }
  379. }
  380. if(hoveredElements.length > 0){
  381. let object = hoveredElements
  382. .map(e => e.object)
  383. .find(e => (e._listeners && e._listeners['mousemove']));
  384. if(object){
  385. object.dispatchEvent({
  386. type: 'mousemove',
  387. object: object
  388. });
  389. }
  390. }
  391. }
  392. // for (let inputListener of this.getSortedListeners()) {
  393. // inputListener.dispatchEvent({
  394. // type: 'mousemove',
  395. // object: null
  396. // });
  397. // }
  398. this.hoveredElements = hoveredElements;
  399. }
  400. onMouseWheel(e){
  401. if(!this.enabled) return;
  402. if(this.logMessages) console.log(this.constructor.name + ": onMouseWheel");
  403. e.preventDefault();
  404. let delta = 0;
  405. if (e.wheelDelta !== undefined) { // WebKit / Opera / Explorer 9
  406. delta = e.wheelDelta;
  407. } else if (e.detail !== undefined) { // Firefox
  408. delta = -e.detail;
  409. }
  410. let ndelta = Math.sign(delta);
  411. // this.wheelDelta += Math.sign(delta);
  412. if (this.hoveredElement) {
  413. this.hoveredElement.object.dispatchEvent({
  414. type: 'mousewheel',
  415. delta: ndelta,
  416. object: this.hoveredElement.object
  417. });
  418. } else {
  419. for (let inputListener of this.getSortedListeners()) {
  420. inputListener.dispatchEvent({
  421. type: 'mousewheel',
  422. delta: ndelta,
  423. object: null
  424. });
  425. }
  426. }
  427. }
  428. startDragging (object, args = null) {
  429. let name = object ? object.name : "no name";
  430. if (this.logMessages) console.log(`${this.constructor.name}: startDragging: '${name}'`);
  431. this.drag = {
  432. start: this.mouse.clone(),
  433. end: this.mouse.clone(),
  434. lastDrag: new THREE.Vector2(0, 0),
  435. startView: this.scene.view.clone(),
  436. object: object
  437. };
  438. if (args) {
  439. for (let key of Object.keys(args)) {
  440. this.drag[key] = args[key];
  441. }
  442. }
  443. }
  444. getMousePointCloudIntersection (mouse) {
  445. return Utils.getMousePointCloudIntersection(
  446. this.mouse,
  447. this.scene.getActiveCamera(),
  448. this.viewer,
  449. this.scene.pointclouds);
  450. }
  451. toggleSelection (object) {
  452. let oldSelection = this.selection;
  453. let index = this.selection.indexOf(object);
  454. if (index === -1) {
  455. this.selection.push(object);
  456. object.dispatchEvent({
  457. type: 'select'
  458. });
  459. } else {
  460. this.selection.splice(index, 1);
  461. object.dispatchEvent({
  462. type: 'deselect'
  463. });
  464. }
  465. this.dispatchEvent({
  466. type: 'selection_changed',
  467. oldSelection: oldSelection,
  468. selection: this.selection
  469. });
  470. }
  471. deselect(object){
  472. let oldSelection = this.selection;
  473. let index = this.selection.indexOf(object);
  474. if(index >= 0){
  475. this.selection.splice(index, 1);
  476. object.dispatchEvent({
  477. type: 'deselect'
  478. });
  479. this.dispatchEvent({
  480. type: 'selection_changed',
  481. oldSelection: oldSelection,
  482. selection: this.selection
  483. });
  484. }
  485. }
  486. deselectAll () {
  487. for (let object of this.selection) {
  488. object.dispatchEvent({
  489. type: 'deselect'
  490. });
  491. }
  492. let oldSelection = this.selection;
  493. if (this.selection.length > 0) {
  494. this.selection = [];
  495. this.dispatchEvent({
  496. type: 'selection_changed',
  497. oldSelection: oldSelection,
  498. selection: this.selection
  499. });
  500. }
  501. }
  502. isSelected (object) {
  503. let index = this.selection.indexOf(object);
  504. return index !== -1;
  505. }
  506. registerInteractiveObject(object){
  507. this.interactiveObjects.add(object);
  508. }
  509. removeInteractiveObject(object){
  510. this.interactiveObjects.delete(object);
  511. }
  512. registerInteractiveScene (scene) {
  513. let index = this.interactiveScenes.indexOf(scene);
  514. if (index === -1) {
  515. this.interactiveScenes.push(scene);
  516. }
  517. }
  518. unregisterInteractiveScene (scene) {
  519. let index = this.interactiveScenes.indexOf(scene);
  520. if (index > -1) {
  521. this.interactiveScenes.splice(index, 1);
  522. }
  523. }
  524. getHoveredElement () {
  525. let hoveredElements = this.getHoveredElements();
  526. if (hoveredElements.length > 0) {
  527. return hoveredElements[0];
  528. } else {
  529. return null;
  530. }
  531. }
  532. getHoveredElements () {
  533. let scenes = this.interactiveScenes.concat(this.scene.scene);
  534. let interactableListeners = ['mouseup', 'mousemove', 'mouseover', 'mouseleave', 'drag', 'drop', 'click', 'select', 'deselect'];
  535. let interactables = [];
  536. for (let scene of scenes) {
  537. scene.traverseVisible(node => {
  538. if (node._listeners && node.visible && !this.blacklist.has(node)) {
  539. let hasInteractableListener = interactableListeners.filter((e) => {
  540. return node._listeners[e] !== undefined;
  541. }).length > 0;
  542. if (hasInteractableListener) {
  543. interactables.push(node);
  544. }
  545. }
  546. });
  547. }
  548. let camera = this.scene.getActiveCamera();
  549. let ray = Utils.mouseToRay(this.mouse, camera, this.domElement.clientWidth, this.domElement.clientHeight);
  550. let raycaster = new THREE.Raycaster();
  551. raycaster.ray.set(ray.origin, ray.direction);
  552. raycaster.params.Line.threshold = 0.2;
  553. let intersections = raycaster.intersectObjects(interactables.filter(o => o.visible), false);
  554. return intersections;
  555. }
  556. setScene (scene) {
  557. this.deselectAll();
  558. this.scene = scene;
  559. }
  560. update (delta) {
  561. }
  562. getNormalizedDrag () {
  563. if (!this.drag) {
  564. return new THREE.Vector2(0, 0);
  565. }
  566. let diff = new THREE.Vector2().subVectors(this.drag.end, this.drag.start);
  567. diff.x = diff.x / this.domElement.clientWidth;
  568. diff.y = diff.y / this.domElement.clientHeight;
  569. return diff;
  570. }
  571. getNormalizedLastDrag () {
  572. if (!this.drag) {
  573. return new THREE.Vector2(0, 0);
  574. }
  575. let lastDrag = this.drag.lastDrag.clone();
  576. lastDrag.x = lastDrag.x / this.domElement.clientWidth;
  577. lastDrag.y = lastDrag.y / this.domElement.clientHeight;
  578. return lastDrag;
  579. }
  580. }