1
0

FloorplanControls.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. import { Vector2, Vector3 } from "three";
  2. const STATE = {
  3. NONE: -1,
  4. ROTATE: 0,
  5. PAN: 1,
  6. ZOOM: 2,
  7. ZOOM_PAN: 3,
  8. ZOOM_ROTATE: 4,
  9. };
  10. const HANDLE = {
  11. ROTATE: 0,
  12. PAN: 1,
  13. ZOOM: 2,
  14. ZOOM_PAN: 3,
  15. ZOOM_ROTATE: 4,
  16. };
  17. const pointers = [];
  18. const pointerPositions = {};
  19. export default class FloorplanControls {
  20. constructor(camera, dom, player) {
  21. this.camera = camera;
  22. this.domElement = dom;
  23. this.domElement.style.touchAction = "none"; // disable touch scroll
  24. this.player = player;
  25. this.panSpeed = 1;
  26. this.zoomSpeed = 1;
  27. this.rotateSpeed = 1;
  28. this.maxDistance = 100;
  29. this.minDistance = 0.1;
  30. this.maxZoom = 500;
  31. this.minZoom = 5;
  32. this.target = new Vector3();
  33. this.state = STATE.NONE;
  34. this.rotateStart = new Vector2();
  35. this.rotateEnd = new Vector2();
  36. this.panStart = new Vector2();
  37. this.panEnd = new Vector2();
  38. this.zoomStart = new Vector2();
  39. this.locked = false; //禁止用户操作
  40. this.enabled = true; //禁止update
  41. this.enablePan = true;
  42. this.enableRotate = true;
  43. this.enableZoom = true;
  44. this.touchesEvent = {
  45. ONE: HANDLE.PAN,
  46. TWO: HANDLE.ZOOM,
  47. };
  48. this.mouseEvent = {
  49. LEFT: HANDLE.PAN,
  50. RIGHT: HANDLE.ROTATE,
  51. WHEEL: HANDLE.ZOOM,
  52. };
  53. this.onBindEvent();
  54. }
  55. onBindEvent = () => {
  56. this.domElement.addEventListener(
  57. "pointerdown",
  58. this.onPointerDown.bind(this)
  59. );
  60. this.domElement.addEventListener("pointerup", this.onPointerUp.bind(this));
  61. this.domElement.addEventListener(
  62. "pointermove",
  63. this.onPointerMove.bind(this)
  64. );
  65. this.domElement.addEventListener(
  66. "pointercancel",
  67. this.onPointerUp.bind(this)
  68. );
  69. this.domElement.addEventListener(
  70. "mousewheel",
  71. this.onMouseWheel.bind(this),
  72. { passive: false }
  73. );
  74. this.domElement.addEventListener("contextmenu", this.onPreventDefault);
  75. };
  76. addPointer = (event) => {
  77. pointers.push(event);
  78. };
  79. removePointer = (event) => {
  80. for (let i = 0; i < pointers.length; i++) {
  81. if (pointers[i].pointerId == event.pointerId) {
  82. pointers.splice(i, 1);
  83. return;
  84. }
  85. }
  86. };
  87. isTrackingPointer = (event) => {
  88. for (let i = 0; i < pointers.length; i++) {
  89. if (pointers[i] == event.pointerId) return true;
  90. }
  91. return false;
  92. };
  93. trackPointer = (event) => {
  94. let position = pointerPositions[event.pointerId];
  95. if (position === undefined) {
  96. position = new Vector2();
  97. pointerPositions[event.pointerId] = position;
  98. }
  99. position.set(event.pageX, event.pageY);
  100. };
  101. getSecondPointerPosition = (event) => {
  102. const pointerId =
  103. event.pointerId === pointers[0].pointerId
  104. ? pointers[1].pointerId
  105. : pointers[0].pointerId;
  106. return pointerPositions[pointerId];
  107. };
  108. // pointer event
  109. onPointerDown = (event) => {
  110. if (this.locked) return;
  111. if (pointers.length === 0) {
  112. this.domElement.setPointerCapture(event.pointerId);
  113. }
  114. if (this.isTrackingPointer(event)) return;
  115. this.addPointer(event);
  116. if (event.pointerType === "touch") {
  117. this.onTouchStart(event);
  118. } else {
  119. this.onMouseDown(event);
  120. }
  121. };
  122. onPointerUp = (event) => {
  123. if (this.locked) return;
  124. this.removePointer(event);
  125. if (pointers.length === 0) {
  126. this.domElement.releasePointerCapture(event.pointerId);
  127. this.state = STATE.NONE;
  128. } else if (pointers.length === 1) {
  129. const pointerId = pointers[0].pointerId;
  130. const position = pointerPositions[pointerId];
  131. this.onTouchStart({
  132. pointerId: pointerId,
  133. pageX: position.x,
  134. pageY: position.y,
  135. });
  136. }
  137. };
  138. onPointerMove = (event) => {
  139. if (this.locked) return;
  140. if (event.pointerType === "touch") {
  141. this.onTouchMove(event);
  142. } else {
  143. this.onMouseMove(event);
  144. }
  145. };
  146. //touch event
  147. onTouchStart = (event) => {
  148. this.trackPointer(event);
  149. switch (pointers.length) {
  150. case 1:
  151. switch (this.touchesEvent.ONE) {
  152. case HANDLE.ROTATE: //rotate
  153. if (this.enableRotate === false) return;
  154. this.handleTouchStartRotate();
  155. this.state = STATE.ROTATE;
  156. break;
  157. case HANDLE.PAN: //pan
  158. if (this.enablePan === false) return;
  159. this.handleTouchStartPan();
  160. this.state = STATE.PAN;
  161. break;
  162. default:
  163. state = STATE.NONE;
  164. }
  165. break;
  166. case 2:
  167. switch (this.touchesEvent.TWO) {
  168. case HANDLE.ZOOM: //zoom
  169. if (this.enableZoom === false) return;
  170. this.handleTouchStartZoom();
  171. this.state = STATE.ZOOM;
  172. break;
  173. case HANDLE.ZOOM_PAN: //zoom_pan
  174. if (this.enableZoom === false && this.enablePan === false) return;
  175. this.handleTouchStartZoom();
  176. this.handleTouchStartPan();
  177. this.state = STATE.ZOOM_PAN;
  178. break;
  179. //todo case HANDLE.ZOOM_ROTATE:
  180. default:
  181. state = STATE.NONE;
  182. }
  183. break;
  184. default:
  185. this.state = STATE.NONE;
  186. }
  187. };
  188. onTouchMove = (event) => {
  189. this.trackPointer(event);
  190. switch (this.state) {
  191. case STATE.ROTATE:
  192. if (this.enableRotate === false) return;
  193. this.handleTouchMoveRotate(event);
  194. break;
  195. case STATE.PAN:
  196. if (this.enablePan === false) return;
  197. this.handleTouchMovePan(event);
  198. break;
  199. case STATE.ZOOM:
  200. if (this.enableZoom === false) return;
  201. this.handleTouchMoveZoom(event);
  202. break;
  203. case STATE.ZOOM_PAN:
  204. if (this.enableZoom) this.handleTouchMoveZoom(event);
  205. if (this.enablePan) this.handleTouchMovePan(event);
  206. break;
  207. //todo case STATE.ZOOM_ROTATE:
  208. default:
  209. this.state = STATE.NONE;
  210. }
  211. };
  212. //mouse event
  213. onMouseDown = (event) => {
  214. if (this.locked) return;
  215. switch (event.button) {
  216. case 0: //left
  217. switch (this.mouseEvent.LEFT) {
  218. case HANDLE.PAN:
  219. if (this.enablePan === false) return;
  220. this.handleMouseDownPan(event);
  221. this.state = STATE.PAN;
  222. break;
  223. case HANDLE.ROTATE:
  224. if (this.enablePan === false) return;
  225. this.handleMouseDownRotate(event);
  226. this.state = STATE.ROTATE;
  227. break;
  228. default:
  229. this.state = STATE.NONE;
  230. }
  231. break;
  232. case 2: //right
  233. switch (this.mouseEvent.RIGHT) {
  234. case HANDLE.PAN:
  235. if (this.enablePan === false) return;
  236. this.handleMouseDownPan(event);
  237. this.state = STATE.PAN;
  238. break;
  239. case HANDLE.ROTATE:
  240. if (this.enablePan === false) return;
  241. this.handleMouseDownRotate(event);
  242. this.state = STATE.ROTATE;
  243. break;
  244. default:
  245. this.state = STATE.NONE;
  246. }
  247. break;
  248. default:
  249. this.state = STATE.NONE;
  250. }
  251. };
  252. onMouseMove = (event) => {
  253. if (this.locked) return;
  254. switch (this.state) {
  255. case STATE.PAN:
  256. if (this.enablePan === false) return;
  257. this.handleMouseMovePan(event);
  258. break;
  259. case STATE.ROTATE:
  260. if (this.enableRotate === false) return;
  261. this.handleMouseMoveRotate(event);
  262. break;
  263. default:
  264. this.state = STATE.NONE;
  265. }
  266. };
  267. onMouseWheel = (event) => {
  268. // console.log("this", this);
  269. if (this.locked) return;
  270. if (this.enableZoom === false) return;
  271. event.preventDefault();
  272. this.handleMouseWheelZoom(event);
  273. };
  274. onPreventDefault = (event) => {
  275. event.preventDefault();
  276. };
  277. //================================handle================================
  278. //-------------------------rotate-------------------------
  279. handleTouchStartRotate = () => {
  280. const position = pointerPositions[pointers[0].pointerId];
  281. this.rotateStart.set(position.x, position.y);
  282. };
  283. handleTouchMoveRotate = (event) => {
  284. this.rotateEnd.set(event.pageX, event.pageY);
  285. let rotateDelta = this.rotateEnd
  286. .clone()
  287. .sub(this.rotateStart)
  288. .multiplyScalar(this.rotateSpeed);
  289. let element = this.domElement;
  290. let rotateX = (2 * Math.PI * rotateDelta.x) / element.clientHeight;
  291. let rotateY = (2 * Math.PI * rotateDelta.y) / element.clientHeight;
  292. this.rotate(rotateX, rotateY);
  293. this.rotateStart.copy(this.rotateEnd);
  294. };
  295. handleMouseDownRotate = (event) => {
  296. this.rotateStart.set(event.pageX, event.pageY);
  297. };
  298. handleMouseMoveRotate = (event) => {
  299. this.rotateEnd.set(event.pageX, event.pageY);
  300. let rotateDelta = this.rotateEnd
  301. .clone()
  302. .sub(this.rotateStart)
  303. .multiplyScalar(this.rotateSpeed);
  304. let element = this.domElement;
  305. let rotateX = (2 * Math.PI * rotateDelta.x) / element.clientHeight;
  306. let rotateY = (2 * Math.PI * rotateDelta.y) / element.clientHeight;
  307. this.rotate(rotateX, rotateY);
  308. this.rotateStart.copy(this.rotateEnd);
  309. };
  310. //-------------------------zoom-------------------------
  311. handleTouchStartZoom = () => {
  312. const dx = pointers[0].pageX - pointers[1].pageX;
  313. const dy = pointers[0].pageY - pointers[1].pageY;
  314. const distance = Math.sqrt(dx * dx + dy * dy);
  315. this.zoomStart.set(0, distance);
  316. };
  317. handleTouchMoveZoom = (event) => {
  318. const position = this.getSecondPointerPosition(event);
  319. const dx = event.pageX - position.x;
  320. const dy = event.pageY - position.y;
  321. const distance = Math.sqrt(dx * dx + dy * dy);
  322. let delta = Math.pow(distance / this.zoomStart.y, this.zoomSpeed);
  323. this.zoom(1 / delta);
  324. this.zoomStart.set(0, distance);
  325. };
  326. handleMouseWheelZoom = (event) => {
  327. if (event.deltaY > 0) {
  328. //zoom out
  329. this.zoom(1.05 * this.zoomSpeed);
  330. } else {
  331. //zoom in
  332. this.zoom(0.95 * this.zoomSpeed);
  333. }
  334. };
  335. //-------------------------pan-------------------------
  336. handleTouchStartPan = () => {
  337. if (pointers.length === 1) {
  338. const position = pointerPositions[pointers[0].pointerId];
  339. this.panStart.set(position.x, position.y);
  340. } else {
  341. const x = 0.5 * (pointers[0].pageX + pointers[1].pageX);
  342. const y = 0.5 * (pointers[0].pageY + pointers[1].pageY);
  343. this.panStart.set(x, y);
  344. }
  345. };
  346. handleTouchMovePan = (event) => {
  347. if (pointers.length === 1) {
  348. this.panEnd.set(event.pageX, event.pageY);
  349. } else {
  350. const position = this.getSecondPointerPosition(event);
  351. const x = 0.5 * (event.pageX + position.x);
  352. const y = 0.5 * (event.pageY + position.y);
  353. this.panEnd.set(x, y);
  354. }
  355. let panDelta = this.panEnd.clone().sub(this.panStart);
  356. this.pan(panDelta);
  357. this.panStart.copy(this.panEnd);
  358. };
  359. handleMouseDownPan = (event) => {
  360. this.panStart.set(event.pageX, event.pageY);
  361. };
  362. handleMouseMovePan = (event) => {
  363. this.panEnd.set(event.pageX, event.pageY);
  364. let panDelta = this.panEnd.clone().sub(this.panStart);
  365. this.pan(panDelta);
  366. this.panStart.copy(this.panEnd);
  367. };
  368. rotate(x, y) {
  369. let r = y;
  370. if (Math.abs(x) > Math.abs(y)) r = x;
  371. let cameraRZ = this.camera.rotation.z;
  372. cameraRZ += r;
  373. if (Math.abs(cameraRZ) >= Math.PI * 2) {
  374. cameraRZ -= Math.sign(cameraRZ) * Math.PI * 2;
  375. }
  376. this.camera.rotation.z = cameraRZ;
  377. this.cameraUpdate();
  378. }
  379. zoom(delta) {
  380. // if(this.camera.isPerspectiveCamera) {
  381. // let cameraY = this.camera.position.y
  382. // cameraY *= delta
  383. // cameraY = Math.max(cameraY, this.minDistance)
  384. // cameraY = Math.min(cameraY, this.maxDistance)
  385. // this.camera.position.y = cameraY //handle
  386. // } else if(this.camera.isOrthographicCamera) {
  387. // let zoom = this.camera.zoom
  388. // zoom *= 1/delta
  389. // console.log(zoom)
  390. // this.camera.zoom = zoom
  391. // this.camera.updateProjectionMatrix()
  392. // }
  393. let cameraY = this.camera.position.y;
  394. cameraY *= delta;
  395. cameraY = Math.max(cameraY, this.minDistance);
  396. cameraY = Math.min(cameraY, this.maxDistance);
  397. this.camera.position.y = cameraY; //handle
  398. if (this.camera.isOrthographicCamera) {
  399. let zoom = this.camera.zoom;
  400. zoom *= 1 / delta;
  401. zoom = Math.max(zoom, this.minZoom);
  402. zoom = Math.min(zoom, this.maxZoom);
  403. this.camera.zoom = zoom;
  404. this.camera.updateProjectionMatrix();
  405. }
  406. this.cameraUpdate();
  407. }
  408. pan(delta) {
  409. const element = this.domElement;
  410. const matrix = this.camera.matrix.clone();
  411. const left = new Vector3();
  412. const up = new Vector3();
  413. let panDelta = delta.multiplyScalar(this.panSpeed);
  414. if (this.camera.isPerspectiveCamera) {
  415. let scalar =
  416. (2 *
  417. this.camera.position.y *
  418. Math.tan(((this.camera.fov / 2) * Math.PI) / 180.0)) /
  419. element.clientHeight;
  420. panDelta.multiplyScalar(scalar);
  421. left.setFromMatrixColumn(matrix, 0);
  422. left.multiplyScalar(-panDelta.x);
  423. up.setFromMatrixColumn(matrix, 1);
  424. up.multiplyScalar(panDelta.y);
  425. } else if (this.camera.isOrthographicCamera) {
  426. (panDelta.x =
  427. (panDelta.x * (this.camera.right - this.camera.left)) /
  428. this.camera.zoom /
  429. element.clientWidth),
  430. this.camera.matrix;
  431. (panDelta.y =
  432. (panDelta.y * (this.camera.top - this.camera.bottom)) /
  433. this.camera.zoom /
  434. element.clientHeight),
  435. this.camera.matrix;
  436. left.setFromMatrixColumn(matrix, 0);
  437. left.multiplyScalar(-panDelta.x);
  438. up.setFromMatrixColumn(matrix, 1);
  439. up.multiplyScalar(panDelta.y);
  440. } else {
  441. return;
  442. }
  443. this.camera.position.add(left).add(up);
  444. this.target.set(this.camera.position.x, 0, this.camera.position.z);
  445. this.cameraUpdate();
  446. }
  447. lookAt(target, height) {
  448. if (!target) return;
  449. height = height !== undefined ? height : this.camera.position.y;
  450. this.camera.position.set(target.x, height, target.z);
  451. this.target.set(target.x, 0, target.z);
  452. this.camera.lookAt(this.target);
  453. }
  454. cameraUpdate = () => {
  455. this.camera.updateMatrix();
  456. this.camera.updateProjectionMatrix();
  457. };
  458. update = () => {
  459. if (!this.enabled) return;
  460. };
  461. }