You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

517 lines
16 KiB

1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
  1. import * as THREE from 'three';
  2. // 创建 TrackballControls 类并导出
  3. export class TrackballControls extends THREE.EventDispatcher {
  4. constructor(object, domElement) {
  5. super(); // 调用父类构造函数
  6. var _this = this;
  7. var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
  8. this.object = object;
  9. this.domElement = (domElement !== undefined) ? domElement : document;
  10. // API
  11. this.enabled = true;
  12. this.screen = { left: 0, top: 0, width: 0, height: 0 };
  13. this.rotateSpeed = 1.0;
  14. this.zoomSpeed = 1.2;
  15. this.panSpeed = 0.3;
  16. this.noRotate = false;
  17. this.noZoom = false;
  18. this.noPan = false;
  19. this.staticMoving = false;
  20. this.dynamicDampingFactor = 0.2;
  21. this.minDistance = 0;
  22. this.maxDistance = Infinity;
  23. this.keys = [65 /*A*/, 83 /*S*/, 68 /*D*/];
  24. // internals
  25. this.target = new THREE.Vector3();
  26. var EPS = 0.000001;
  27. var lastPosition = new THREE.Vector3();
  28. var _state = STATE.NONE,
  29. _prevState = STATE.NONE,
  30. _eye = new THREE.Vector3(),
  31. _movePrev = new THREE.Vector2(),
  32. _moveCurr = new THREE.Vector2(),
  33. _lastAxis = new THREE.Vector3(),
  34. _lastAngle = 0,
  35. _zoomStart = new THREE.Vector2(),
  36. _zoomEnd = new THREE.Vector2(),
  37. _touchZoomDistanceStart = 0,
  38. _touchZoomDistanceEnd = 0,
  39. _panStart = new THREE.Vector2(),
  40. _panEnd = new THREE.Vector2();
  41. // for reset
  42. this.target0 = this.target.clone();
  43. this.position0 = this.object.position.clone();
  44. this.up0 = this.object.up.clone();
  45. // events
  46. var changeEvent = { type: "change" };
  47. var startEvent = { type: "start" };
  48. var endEvent = { type: "end" };
  49. // methods
  50. this.handleResize = function () {
  51. if (this.domElement === document) {
  52. this.screen.left = 0;
  53. this.screen.top = 0;
  54. this.screen.width = window.innerWidth;
  55. this.screen.height = window.innerHeight;
  56. } else {
  57. var box = this.domElement.getBoundingClientRect();
  58. // adjustments come from similar code in the jquery offset() function
  59. var d = this.domElement.ownerDocument.documentElement;
  60. this.screen.left = box.left + window.pageXOffset - d.clientLeft;
  61. this.screen.top = box.top + window.pageYOffset - d.clientTop;
  62. this.screen.width = box.width;
  63. this.screen.height = box.height;
  64. }
  65. };
  66. var getMouseOnScreen = (function () {
  67. var vector = new THREE.Vector2();
  68. return function getMouseOnScreen(pageX, pageY) {
  69. vector.set(
  70. (pageX - _this.screen.left) / _this.screen.width,
  71. (pageY - _this.screen.top) / _this.screen.height
  72. );
  73. return vector;
  74. };
  75. })();
  76. var getMouseOnCircle = (function () {
  77. var vector = new THREE.Vector2();
  78. return function getMouseOnCircle(pageX, pageY) {
  79. vector.set(
  80. (pageX - _this.screen.width * 0.5 - _this.screen.left) /
  81. (_this.screen.width * 0.5),
  82. (_this.screen.height + 2 * (_this.screen.top - pageY)) /
  83. _this.screen.width // screen.width intentional
  84. );
  85. return vector;
  86. };
  87. })();
  88. this.rotateCamera = (function () {
  89. var axis = new THREE.Vector3(),
  90. quaternion = new THREE.Quaternion(),
  91. eyeDirection = new THREE.Vector3(),
  92. objectUpDirection = new THREE.Vector3(),
  93. objectSidewaysDirection = new THREE.Vector3(),
  94. moveDirection = new THREE.Vector3(),
  95. angle;
  96. return function rotateCamera() {
  97. moveDirection.set(
  98. _moveCurr.x - _movePrev.x,
  99. _moveCurr.y - _movePrev.y,
  100. 0
  101. );
  102. angle = moveDirection.length();
  103. if (angle) {
  104. _eye.copy(_this.object.position).sub(_this.target);
  105. eyeDirection.copy(_eye).normalize();
  106. objectUpDirection.copy(_this.object.up).normalize();
  107. objectSidewaysDirection
  108. .crossVectors(objectUpDirection, eyeDirection)
  109. .normalize();
  110. objectUpDirection.setLength(_moveCurr.y - _movePrev.y);
  111. objectSidewaysDirection.setLength(_moveCurr.x - _movePrev.x);
  112. moveDirection.copy(objectUpDirection.add(objectSidewaysDirection));
  113. axis.crossVectors(moveDirection, _eye).normalize();
  114. angle *= _this.rotateSpeed;
  115. quaternion.setFromAxisAngle(axis, angle);
  116. _eye.applyQuaternion(quaternion);
  117. _this.object.up.applyQuaternion(quaternion);
  118. _lastAxis.copy(axis);
  119. _lastAngle = angle;
  120. } else if (!_this.staticMoving && _lastAngle) {
  121. _lastAngle *= Math.sqrt(1.0 - _this.dynamicDampingFactor);
  122. _eye.copy(_this.object.position).sub(_this.target);
  123. quaternion.setFromAxisAngle(_lastAxis, _lastAngle);
  124. _eye.applyQuaternion(quaternion);
  125. _this.object.up.applyQuaternion(quaternion);
  126. }
  127. _movePrev.copy(_moveCurr);
  128. };
  129. })();
  130. this.zoomCamera = function () {
  131. var factor;
  132. if (_state === STATE.TOUCH_ZOOM_PAN) {
  133. factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
  134. _touchZoomDistanceStart = _touchZoomDistanceEnd;
  135. _eye.multiplyScalar(factor);
  136. } else {
  137. factor = 1.0 + (_zoomEnd.y - _zoomStart.y) * _this.zoomSpeed;
  138. if (factor !== 1.0 && factor > 0.0) {
  139. _eye.multiplyScalar(factor);
  140. }
  141. if (_this.staticMoving) {
  142. _zoomStart.copy(_zoomEnd);
  143. } else {
  144. _zoomStart.y +=
  145. (_zoomEnd.y - _zoomStart.y) * this.dynamicDampingFactor;
  146. }
  147. }
  148. };
  149. this.panCamera = (function () {
  150. var mouseChange = new THREE.Vector2(),
  151. objectUp = new THREE.Vector3(),
  152. pan = new THREE.Vector3();
  153. return function panCamera() {
  154. mouseChange.copy(_panEnd).sub(_panStart);
  155. if (mouseChange.lengthSq()) {
  156. mouseChange.multiplyScalar(_eye.length() * _this.panSpeed);
  157. pan.copy(_eye).cross(_this.object.up).setLength(mouseChange.x);
  158. pan.add(objectUp.copy(_this.object.up).setLength(mouseChange.y));
  159. _this.object.position.add(pan);
  160. _this.target.add(pan);
  161. if (_this.staticMoving) {
  162. _panStart.copy(_panEnd);
  163. } else {
  164. _panStart.add(
  165. mouseChange
  166. .subVectors(_panEnd, _panStart)
  167. .multiplyScalar(_this.dynamicDampingFactor)
  168. );
  169. }
  170. }
  171. };
  172. })();
  173. this.checkDistances = function () {
  174. if (!_this.noZoom || !_this.noPan) {
  175. if (_eye.lengthSq() > _this.maxDistance * _this.maxDistance) {
  176. _this.object.position.addVectors(
  177. _this.target,
  178. _eye.setLength(_this.maxDistance)
  179. );
  180. _zoomStart.copy(_zoomEnd);
  181. }
  182. if (_eye.lengthSq() < _this.minDistance * _this.minDistance) {
  183. _this.object.position.addVectors(
  184. _this.target,
  185. _eye.setLength(_this.minDistance)
  186. );
  187. _zoomStart.copy(_zoomEnd);
  188. }
  189. }
  190. };
  191. this.update = function () {
  192. _eye.subVectors(_this.object.position, _this.target);
  193. if (!_this.noRotate) {
  194. _this.rotateCamera();
  195. }
  196. if (!_this.noZoom) {
  197. _this.zoomCamera();
  198. }
  199. if (!_this.noPan) {
  200. _this.panCamera();
  201. }
  202. _this.object.position.addVectors(_this.target, _eye);
  203. _this.checkDistances();
  204. _this.object.lookAt(_this.target);
  205. if (lastPosition.distanceToSquared(_this.object.position) > EPS) {
  206. _this.dispatchEvent(changeEvent);
  207. lastPosition.copy(_this.object.position);
  208. }
  209. };
  210. this.reset = function () {
  211. _state = STATE.NONE;
  212. _prevState = STATE.NONE;
  213. _this.target.copy(_this.target0);
  214. _this.object.position.copy(_this.position0);
  215. _this.object.up.copy(_this.up0);
  216. _eye.subVectors(_this.object.position, _this.target);
  217. _this.object.lookAt(_this.target);
  218. _this.dispatchEvent(changeEvent);
  219. lastPosition.copy(_this.object.position);
  220. };
  221. // listeners
  222. function keydown(event) {
  223. if (_this.enabled === false) return;
  224. window.removeEventListener("keydown", keydown);
  225. _prevState = _state;
  226. if (_state !== STATE.NONE) {
  227. return;
  228. } else if (
  229. event.keyCode === _this.keys[STATE.ROTATE] &&
  230. !_this.noRotate
  231. ) {
  232. _state = STATE.ROTATE;
  233. } else if (event.keyCode === _this.keys[STATE.ZOOM] && !_this.noZoom) {
  234. _state = STATE.ZOOM;
  235. } else if (event.keyCode === _this.keys[STATE.PAN] && !_this.noPan) {
  236. _state = STATE.PAN;
  237. }
  238. }
  239. function keyup(event) {
  240. if (_this.enabled === false) return;
  241. _state = _prevState;
  242. window.addEventListener("keydown", keydown, false);
  243. }
  244. function mousedown(event) {
  245. if (_this.enabled === false) return;
  246. event.preventDefault();
  247. event.stopPropagation();
  248. if (_state === STATE.NONE) {
  249. _state = event.button;
  250. }
  251. // 阻止浏览器的默认行为
  252. return;
  253. if (_state === STATE.ROTATE && !_this.noRotate) {
  254. _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY));
  255. _movePrev.copy(_moveCurr);
  256. } else if (_state === STATE.ZOOM && !_this.noZoom) {
  257. _zoomStart.copy(getMouseOnScreen(event.pageX, event.pageY));
  258. _zoomEnd.copy(_zoomStart);
  259. } else if (_state === STATE.PAN && !_this.noPan) {
  260. _panStart.copy(getMouseOnScreen(event.pageX, event.pageY));
  261. _panEnd.copy(_panStart);
  262. }
  263. document.addEventListener("mousemove", mousemove, false);
  264. document.addEventListener("mouseup", mouseup, false);
  265. _this.dispatchEvent(startEvent);
  266. }
  267. function mousemove(event) {
  268. if (_this.enabled === false) return;
  269. event.preventDefault();
  270. event.stopPropagation();
  271. if (_state === STATE.ROTATE && !_this.noRotate) {
  272. _movePrev.copy(_moveCurr);
  273. _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY));
  274. } else if (_state === STATE.ZOOM && !_this.noZoom) {
  275. _zoomEnd.copy(getMouseOnScreen(event.pageX, event.pageY));
  276. } else if (_state === STATE.PAN && !_this.noPan) {
  277. _panEnd.copy(getMouseOnScreen(event.pageX, event.pageY));
  278. }
  279. }
  280. function mouseup(event) {
  281. if (_this.enabled === false) return;
  282. event.preventDefault();
  283. event.stopPropagation();
  284. _state = STATE.NONE;
  285. document.removeEventListener("mousemove", mousemove);
  286. document.removeEventListener("mouseup", mouseup);
  287. _this.dispatchEvent(endEvent);
  288. }
  289. function mousewheel(event) {
  290. if (_this.enabled === false) return;
  291. if (_this.noZoom === true) return;
  292. event.preventDefault();
  293. event.stopPropagation();
  294. // return;
  295. switch (event.deltaMode) {
  296. case 2:
  297. // Zoom in pages
  298. _zoomStart.y -= event.deltaY * 0.025;
  299. break;
  300. case 1:
  301. // Zoom in lines
  302. _zoomStart.y -= event.deltaY * 0.01;
  303. break;
  304. default:
  305. // undefined, 0, assume pixels
  306. _zoomStart.y -= event.deltaY * 0.00025;
  307. break;
  308. }
  309. _this.dispatchEvent(startEvent);
  310. _this.dispatchEvent(endEvent);
  311. }
  312. function touchstart(event) {
  313. if (_this.enabled === false) return;
  314. event.preventDefault();
  315. switch (event.touches.length) {
  316. case 1:
  317. _state = STATE.TOUCH_ROTATE;
  318. _moveCurr.copy(
  319. getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY)
  320. );
  321. _movePrev.copy(_moveCurr);
  322. break;
  323. default: // 2 or more
  324. _state = STATE.TOUCH_ZOOM_PAN;
  325. var dx = event.touches[0].pageX - event.touches[1].pageX;
  326. var dy = event.touches[0].pageY - event.touches[1].pageY;
  327. _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt(
  328. dx * dx + dy * dy
  329. );
  330. var x = (event.touches[0].pageX + event.touches[1].pageX) / 2;
  331. var y = (event.touches[0].pageY + event.touches[1].pageY) / 2;
  332. _panStart.copy(getMouseOnScreen(x, y));
  333. _panEnd.copy(_panStart);
  334. break;
  335. }
  336. _this.dispatchEvent(startEvent);
  337. }
  338. function touchmove(event) {
  339. if (_this.enabled === false) return;
  340. event.preventDefault();
  341. event.stopPropagation();
  342. switch (event.touches.length) {
  343. case 1:
  344. _movePrev.copy(_moveCurr);
  345. _moveCurr.copy(
  346. getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY)
  347. );
  348. break;
  349. default: // 2 or more
  350. var dx = event.touches[0].pageX - event.touches[1].pageX;
  351. var dy = event.touches[0].pageY - event.touches[1].pageY;
  352. _touchZoomDistanceEnd = Math.sqrt(dx * dx + dy * dy);
  353. var x = (event.touches[0].pageX + event.touches[1].pageX) / 2;
  354. var y = (event.touches[0].pageY + event.touches[1].pageY) / 2;
  355. _panEnd.copy(getMouseOnScreen(x, y));
  356. break;
  357. }
  358. }
  359. function touchend(event) {
  360. if (_this.enabled === false) return;
  361. switch (event.touches.length) {
  362. case 0:
  363. _state = STATE.NONE;
  364. break;
  365. case 1:
  366. _state = STATE.TOUCH_ROTATE;
  367. _moveCurr.copy(
  368. getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY)
  369. );
  370. _movePrev.copy(_moveCurr);
  371. break;
  372. }
  373. _this.dispatchEvent(endEvent);
  374. }
  375. function contextmenu(event) {
  376. if (_this.enabled === false) return;
  377. event.preventDefault();
  378. }
  379. this.dispose = function () {
  380. this.domElement.removeEventListener("contextmenu", contextmenu, false);
  381. this.domElement.removeEventListener("mousedown", mousedown, false);
  382. // this.domElement.removeEventListener("wheel", mousewheel, false);
  383. this.domElement.removeEventListener("touchstart", touchstart, false);
  384. this.domElement.removeEventListener("touchend", touchend, false);
  385. this.domElement.removeEventListener("touchmove", touchmove, false);
  386. document.removeEventListener("mousemove", mousemove, false);
  387. document.removeEventListener("mouseup", mouseup, false);
  388. window.removeEventListener("keydown", keydown, false);
  389. window.removeEventListener("keyup", keyup, false);
  390. };
  391. this.domElement.addEventListener("contextmenu", contextmenu, false);
  392. this.domElement.addEventListener("mousedown", mousedown, false);
  393. // this.domElement.addEventListener("wheel", mousewheel, false);
  394. this.domElement.addEventListener("touchstart", touchstart, false);
  395. this.domElement.addEventListener("touchend", touchend, false);
  396. this.domElement.addEventListener("touchmove", touchmove, false);
  397. window.addEventListener("keydown", keydown, false);
  398. window.addEventListener("keyup", keyup, false);
  399. this.handleResize();
  400. // force an update at start
  401. this.update();
  402. }
  403. }