UNPKG

cione-comp-lib

Version:

`$ yarn add cione-comp-lib` 或者 `$ npm i cione-comp-lib -S`

444 lines (443 loc) 17.4 kB
import { EventDispatcher, MOUSE, Vector3, Vector2, Quaternion } from "../../../build/three.module.mjs"; const _changeEvent = { type: "change" }; const _startEvent = { type: "start" }; const _endEvent = { type: "end" }; class TrackballControls extends EventDispatcher { constructor(object, domElement) { super(); const scope = this; const STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 }; this.object = object; this.domElement = domElement; this.domElement.style.touchAction = "none"; this.enabled = true; this.screen = { left: 0, top: 0, width: 0, height: 0 }; this.rotateSpeed = 1; this.zoomSpeed = 1.2; this.panSpeed = 0.3; this.noRotate = false; this.noZoom = false; this.noPan = false; this.staticMoving = false; this.dynamicDampingFactor = 0.2; this.minDistance = 0; this.maxDistance = Infinity; this.keys = ["KeyA", "KeyS", "KeyD"]; this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; this.target = new Vector3(); const EPS = 1e-6; const lastPosition = new Vector3(); let lastZoom = 1; let _state = STATE.NONE, _keyState = STATE.NONE, _touchZoomDistanceStart = 0, _touchZoomDistanceEnd = 0, _lastAngle = 0; const _eye = new Vector3(), _movePrev = new Vector2(), _moveCurr = new Vector2(), _lastAxis = new Vector3(), _zoomStart = new Vector2(), _zoomEnd = new Vector2(), _panStart = new Vector2(), _panEnd = new Vector2(), _pointers = [], _pointerPositions = {}; this.target0 = this.target.clone(); this.position0 = this.object.position.clone(); this.up0 = this.object.up.clone(); this.zoom0 = this.object.zoom; this.handleResize = function() { const box = scope.domElement.getBoundingClientRect(); const d = scope.domElement.ownerDocument.documentElement; scope.screen.left = box.left + window.pageXOffset - d.clientLeft; scope.screen.top = box.top + window.pageYOffset - d.clientTop; scope.screen.width = box.width; scope.screen.height = box.height; }; const getMouseOnScreen = function() { const vector = new Vector2(); return function getMouseOnScreen2(pageX, pageY) { vector.set( (pageX - scope.screen.left) / scope.screen.width, (pageY - scope.screen.top) / scope.screen.height ); return vector; }; }(); const getMouseOnCircle = function() { const vector = new Vector2(); return function getMouseOnCircle2(pageX, pageY) { vector.set( (pageX - scope.screen.width * 0.5 - scope.screen.left) / (scope.screen.width * 0.5), (scope.screen.height + 2 * (scope.screen.top - pageY)) / scope.screen.width ); return vector; }; }(); this.rotateCamera = function() { const axis = new Vector3(), quaternion = new Quaternion(), eyeDirection = new Vector3(), objectUpDirection = new Vector3(), objectSidewaysDirection = new Vector3(), moveDirection = new Vector3(); return function rotateCamera() { moveDirection.set(_moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0); let angle = moveDirection.length(); if (angle) { _eye.copy(scope.object.position).sub(scope.target); eyeDirection.copy(_eye).normalize(); objectUpDirection.copy(scope.object.up).normalize(); objectSidewaysDirection.crossVectors(objectUpDirection, eyeDirection).normalize(); objectUpDirection.setLength(_moveCurr.y - _movePrev.y); objectSidewaysDirection.setLength(_moveCurr.x - _movePrev.x); moveDirection.copy(objectUpDirection.add(objectSidewaysDirection)); axis.crossVectors(moveDirection, _eye).normalize(); angle *= scope.rotateSpeed; quaternion.setFromAxisAngle(axis, angle); _eye.applyQuaternion(quaternion); scope.object.up.applyQuaternion(quaternion); _lastAxis.copy(axis); _lastAngle = angle; } else if (!scope.staticMoving && _lastAngle) { _lastAngle *= Math.sqrt(1 - scope.dynamicDampingFactor); _eye.copy(scope.object.position).sub(scope.target); quaternion.setFromAxisAngle(_lastAxis, _lastAngle); _eye.applyQuaternion(quaternion); scope.object.up.applyQuaternion(quaternion); } _movePrev.copy(_moveCurr); }; }(); this.zoomCamera = function() { let factor; if (_state === STATE.TOUCH_ZOOM_PAN) { factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; _touchZoomDistanceStart = _touchZoomDistanceEnd; if (scope.object.isPerspectiveCamera) { _eye.multiplyScalar(factor); } else if (scope.object.isOrthographicCamera) { scope.object.zoom /= factor; scope.object.updateProjectionMatrix(); } else { console.warn("THREE.TrackballControls: Unsupported camera type"); } } else { factor = 1 + (_zoomEnd.y - _zoomStart.y) * scope.zoomSpeed; if (factor !== 1 && factor > 0) { if (scope.object.isPerspectiveCamera) { _eye.multiplyScalar(factor); } else if (scope.object.isOrthographicCamera) { scope.object.zoom /= factor; scope.object.updateProjectionMatrix(); } else { console.warn("THREE.TrackballControls: Unsupported camera type"); } } if (scope.staticMoving) { _zoomStart.copy(_zoomEnd); } else { _zoomStart.y += (_zoomEnd.y - _zoomStart.y) * this.dynamicDampingFactor; } } }; this.panCamera = function() { const mouseChange = new Vector2(), objectUp = new Vector3(), pan = new Vector3(); return function panCamera() { mouseChange.copy(_panEnd).sub(_panStart); if (mouseChange.lengthSq()) { if (scope.object.isOrthographicCamera) { const scale_x = (scope.object.right - scope.object.left) / scope.object.zoom / scope.domElement.clientWidth; const scale_y = (scope.object.top - scope.object.bottom) / scope.object.zoom / scope.domElement.clientWidth; mouseChange.x *= scale_x; mouseChange.y *= scale_y; } mouseChange.multiplyScalar(_eye.length() * scope.panSpeed); pan.copy(_eye).cross(scope.object.up).setLength(mouseChange.x); pan.add(objectUp.copy(scope.object.up).setLength(mouseChange.y)); scope.object.position.add(pan); scope.target.add(pan); if (scope.staticMoving) { _panStart.copy(_panEnd); } else { _panStart.add(mouseChange.subVectors(_panEnd, _panStart).multiplyScalar(scope.dynamicDampingFactor)); } } }; }(); this.checkDistances = function() { if (!scope.noZoom || !scope.noPan) { if (_eye.lengthSq() > scope.maxDistance * scope.maxDistance) { scope.object.position.addVectors(scope.target, _eye.setLength(scope.maxDistance)); _zoomStart.copy(_zoomEnd); } if (_eye.lengthSq() < scope.minDistance * scope.minDistance) { scope.object.position.addVectors(scope.target, _eye.setLength(scope.minDistance)); _zoomStart.copy(_zoomEnd); } } }; this.update = function() { _eye.subVectors(scope.object.position, scope.target); if (!scope.noRotate) { scope.rotateCamera(); } if (!scope.noZoom) { scope.zoomCamera(); } if (!scope.noPan) { scope.panCamera(); } scope.object.position.addVectors(scope.target, _eye); if (scope.object.isPerspectiveCamera) { scope.checkDistances(); scope.object.lookAt(scope.target); if (lastPosition.distanceToSquared(scope.object.position) > EPS) { scope.dispatchEvent(_changeEvent); lastPosition.copy(scope.object.position); } } else if (scope.object.isOrthographicCamera) { scope.object.lookAt(scope.target); if (lastPosition.distanceToSquared(scope.object.position) > EPS || lastZoom !== scope.object.zoom) { scope.dispatchEvent(_changeEvent); lastPosition.copy(scope.object.position); lastZoom = scope.object.zoom; } } else { console.warn("THREE.TrackballControls: Unsupported camera type"); } }; this.reset = function() { _state = STATE.NONE; _keyState = STATE.NONE; scope.target.copy(scope.target0); scope.object.position.copy(scope.position0); scope.object.up.copy(scope.up0); scope.object.zoom = scope.zoom0; scope.object.updateProjectionMatrix(); _eye.subVectors(scope.object.position, scope.target); scope.object.lookAt(scope.target); scope.dispatchEvent(_changeEvent); lastPosition.copy(scope.object.position); lastZoom = scope.object.zoom; }; function onPointerDown(event) { if (scope.enabled === false) return; if (_pointers.length === 0) { scope.domElement.setPointerCapture(event.pointerId); scope.domElement.addEventListener("pointermove", onPointerMove); scope.domElement.addEventListener("pointerup", onPointerUp); } addPointer(event); if (event.pointerType === "touch") { onTouchStart(event); } else { onMouseDown(event); } } function onPointerMove(event) { if (scope.enabled === false) return; if (event.pointerType === "touch") { onTouchMove(event); } else { onMouseMove(event); } } function onPointerUp(event) { if (scope.enabled === false) return; if (event.pointerType === "touch") { onTouchEnd(event); } else { onMouseUp(); } removePointer(event); if (_pointers.length === 0) { scope.domElement.releasePointerCapture(event.pointerId); scope.domElement.removeEventListener("pointermove", onPointerMove); scope.domElement.removeEventListener("pointerup", onPointerUp); } } function onPointerCancel(event) { removePointer(event); } function keydown(event) { if (scope.enabled === false) return; window.removeEventListener("keydown", keydown); if (_keyState !== STATE.NONE) { return; } else if (event.code === scope.keys[STATE.ROTATE] && !scope.noRotate) { _keyState = STATE.ROTATE; } else if (event.code === scope.keys[STATE.ZOOM] && !scope.noZoom) { _keyState = STATE.ZOOM; } else if (event.code === scope.keys[STATE.PAN] && !scope.noPan) { _keyState = STATE.PAN; } } function keyup() { if (scope.enabled === false) return; _keyState = STATE.NONE; window.addEventListener("keydown", keydown); } function onMouseDown(event) { if (_state === STATE.NONE) { switch (event.button) { case scope.mouseButtons.LEFT: _state = STATE.ROTATE; break; case scope.mouseButtons.MIDDLE: _state = STATE.ZOOM; break; case scope.mouseButtons.RIGHT: _state = STATE.PAN; break; } } const state = _keyState !== STATE.NONE ? _keyState : _state; if (state === STATE.ROTATE && !scope.noRotate) { _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY)); _movePrev.copy(_moveCurr); } else if (state === STATE.ZOOM && !scope.noZoom) { _zoomStart.copy(getMouseOnScreen(event.pageX, event.pageY)); _zoomEnd.copy(_zoomStart); } else if (state === STATE.PAN && !scope.noPan) { _panStart.copy(getMouseOnScreen(event.pageX, event.pageY)); _panEnd.copy(_panStart); } scope.dispatchEvent(_startEvent); } function onMouseMove(event) { const state = _keyState !== STATE.NONE ? _keyState : _state; if (state === STATE.ROTATE && !scope.noRotate) { _movePrev.copy(_moveCurr); _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY)); } else if (state === STATE.ZOOM && !scope.noZoom) { _zoomEnd.copy(getMouseOnScreen(event.pageX, event.pageY)); } else if (state === STATE.PAN && !scope.noPan) { _panEnd.copy(getMouseOnScreen(event.pageX, event.pageY)); } } function onMouseUp() { _state = STATE.NONE; scope.dispatchEvent(_endEvent); } function onMouseWheel(event) { if (scope.enabled === false) return; if (scope.noZoom === true) return; event.preventDefault(); switch (event.deltaMode) { case 2: _zoomStart.y -= event.deltaY * 0.025; break; case 1: _zoomStart.y -= event.deltaY * 0.01; break; default: _zoomStart.y -= event.deltaY * 25e-5; break; } scope.dispatchEvent(_startEvent); scope.dispatchEvent(_endEvent); } function onTouchStart(event) { trackPointer(event); switch (_pointers.length) { case 1: _state = STATE.TOUCH_ROTATE; _moveCurr.copy(getMouseOnCircle(_pointers[0].pageX, _pointers[0].pageY)); _movePrev.copy(_moveCurr); break; default: _state = STATE.TOUCH_ZOOM_PAN; const dx = _pointers[0].pageX - _pointers[1].pageX; const dy = _pointers[0].pageY - _pointers[1].pageY; _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt(dx * dx + dy * dy); const x = (_pointers[0].pageX + _pointers[1].pageX) / 2; const y = (_pointers[0].pageY + _pointers[1].pageY) / 2; _panStart.copy(getMouseOnScreen(x, y)); _panEnd.copy(_panStart); break; } scope.dispatchEvent(_startEvent); } function onTouchMove(event) { trackPointer(event); switch (_pointers.length) { case 1: _movePrev.copy(_moveCurr); _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY)); break; default: const position = getSecondPointerPosition(event); const dx = event.pageX - position.x; const dy = event.pageY - position.y; _touchZoomDistanceEnd = Math.sqrt(dx * dx + dy * dy); const x = (event.pageX + position.x) / 2; const y = (event.pageY + position.y) / 2; _panEnd.copy(getMouseOnScreen(x, y)); break; } } function onTouchEnd(event) { switch (_pointers.length) { case 0: _state = STATE.NONE; break; case 1: _state = STATE.TOUCH_ROTATE; _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY)); _movePrev.copy(_moveCurr); break; case 2: _state = STATE.TOUCH_ZOOM_PAN; for (let i = 0; i < _pointers.length; i++) { if (_pointers[i].pointerId !== event.pointerId) { const position = _pointerPositions[_pointers[i].pointerId]; _moveCurr.copy(getMouseOnCircle(position.x, position.y)); _movePrev.copy(_moveCurr); break; } } break; } scope.dispatchEvent(_endEvent); } function contextmenu(event) { if (scope.enabled === false) return; event.preventDefault(); } function addPointer(event) { _pointers.push(event); } function removePointer(event) { delete _pointerPositions[event.pointerId]; for (let i = 0; i < _pointers.length; i++) { if (_pointers[i].pointerId == event.pointerId) { _pointers.splice(i, 1); return; } } } function trackPointer(event) { let position = _pointerPositions[event.pointerId]; if (position === void 0) { position = new Vector2(); _pointerPositions[event.pointerId] = position; } position.set(event.pageX, event.pageY); } function getSecondPointerPosition(event) { const pointer = event.pointerId === _pointers[0].pointerId ? _pointers[1] : _pointers[0]; return _pointerPositions[pointer.pointerId]; } this.dispose = function() { scope.domElement.removeEventListener("contextmenu", contextmenu); scope.domElement.removeEventListener("pointerdown", onPointerDown); scope.domElement.removeEventListener("pointercancel", onPointerCancel); scope.domElement.removeEventListener("wheel", onMouseWheel); scope.domElement.removeEventListener("pointermove", onPointerMove); scope.domElement.removeEventListener("pointerup", onPointerUp); window.removeEventListener("keydown", keydown); window.removeEventListener("keyup", keyup); }; this.domElement.addEventListener("contextmenu", contextmenu); this.domElement.addEventListener("pointerdown", onPointerDown); this.domElement.addEventListener("pointercancel", onPointerCancel); this.domElement.addEventListener("wheel", onMouseWheel, { passive: false }); window.addEventListener("keydown", keydown); window.addEventListener("keyup", keyup); this.handleResize(); this.update(); } } export { TrackballControls };