import { Vector2, Vector3 } from "three"; const STATE = { NONE: -1, ROTATE: 0, PAN: 1, ZOOM: 2, ZOOM_PAN: 3, ZOOM_ROTATE: 4, }; const HANDLE = { ROTATE: 0, PAN: 1, ZOOM: 2, ZOOM_PAN: 3, ZOOM_ROTATE: 4, }; const pointers = []; const pointerPositions = {}; export default class FloorplanControls { constructor(camera, dom, player) { this.camera = camera; this.domElement = dom; this.domElement.style.touchAction = "none"; // disable touch scroll this.player = player; this.panSpeed = 1; this.zoomSpeed = 1; this.rotateSpeed = 1; this.maxDistance = 100; this.minDistance = 0.1; this.maxZoom = 500; this.minZoom = 5; this.target = new Vector3(); this.state = STATE.NONE; this.rotateStart = new Vector2(); this.rotateEnd = new Vector2(); this.panStart = new Vector2(); this.panEnd = new Vector2(); this.zoomStart = new Vector2(); this.locked = false; //禁止用户操作 this.enabled = true; //禁止update this.enablePan = true; this.enableRotate = true; this.enableZoom = true; this.touchesEvent = { ONE: HANDLE.PAN, TWO: HANDLE.ZOOM, }; this.mouseEvent = { LEFT: HANDLE.PAN, RIGHT: HANDLE.ROTATE, WHEEL: HANDLE.ZOOM, }; this.onBindEvent(); } onBindEvent = () => { this.domElement.addEventListener( "pointerdown", this.onPointerDown.bind(this) ); this.domElement.addEventListener("pointerup", this.onPointerUp.bind(this)); this.domElement.addEventListener( "pointermove", this.onPointerMove.bind(this) ); this.domElement.addEventListener( "pointercancel", this.onPointerUp.bind(this) ); this.domElement.addEventListener( "mousewheel", this.onMouseWheel.bind(this), { passive: false } ); this.domElement.addEventListener("contextmenu", this.onPreventDefault); }; addPointer = (event) => { pointers.push(event); }; removePointer = (event) => { for (let i = 0; i < pointers.length; i++) { if (pointers[i].pointerId == event.pointerId) { pointers.splice(i, 1); return; } } }; isTrackingPointer = (event) => { for (let i = 0; i < pointers.length; i++) { if (pointers[i] == event.pointerId) return true; } return false; }; trackPointer = (event) => { let position = pointerPositions[event.pointerId]; if (position === undefined) { position = new Vector2(); pointerPositions[event.pointerId] = position; } position.set(event.pageX, event.pageY); }; getSecondPointerPosition = (event) => { const pointerId = event.pointerId === pointers[0].pointerId ? pointers[1].pointerId : pointers[0].pointerId; return pointerPositions[pointerId]; }; // pointer event onPointerDown = (event) => { if (this.locked) return; if (pointers.length === 0) { this.domElement.setPointerCapture(event.pointerId); } if (this.isTrackingPointer(event)) return; this.addPointer(event); if (event.pointerType === "touch") { this.onTouchStart(event); } else { this.onMouseDown(event); } }; onPointerUp = (event) => { if (this.locked) return; this.removePointer(event); if (pointers.length === 0) { this.domElement.releasePointerCapture(event.pointerId); this.state = STATE.NONE; } else if (pointers.length === 1) { const pointerId = pointers[0].pointerId; const position = pointerPositions[pointerId]; this.onTouchStart({ pointerId: pointerId, pageX: position.x, pageY: position.y, }); } }; onPointerMove = (event) => { if (this.locked) return; if (event.pointerType === "touch") { this.onTouchMove(event); } else { this.onMouseMove(event); } }; //touch event onTouchStart = (event) => { this.trackPointer(event); switch (pointers.length) { case 1: switch (this.touchesEvent.ONE) { case HANDLE.ROTATE: //rotate if (this.enableRotate === false) return; this.handleTouchStartRotate(); this.state = STATE.ROTATE; break; case HANDLE.PAN: //pan if (this.enablePan === false) return; this.handleTouchStartPan(); this.state = STATE.PAN; break; default: state = STATE.NONE; } break; case 2: switch (this.touchesEvent.TWO) { case HANDLE.ZOOM: //zoom if (this.enableZoom === false) return; this.handleTouchStartZoom(); this.state = STATE.ZOOM; break; case HANDLE.ZOOM_PAN: //zoom_pan if (this.enableZoom === false && this.enablePan === false) return; this.handleTouchStartZoom(); this.handleTouchStartPan(); this.state = STATE.ZOOM_PAN; break; //todo case HANDLE.ZOOM_ROTATE: default: state = STATE.NONE; } break; default: this.state = STATE.NONE; } }; onTouchMove = (event) => { this.trackPointer(event); switch (this.state) { case STATE.ROTATE: if (this.enableRotate === false) return; this.handleTouchMoveRotate(event); break; case STATE.PAN: if (this.enablePan === false) return; this.handleTouchMovePan(event); break; case STATE.ZOOM: if (this.enableZoom === false) return; this.handleTouchMoveZoom(event); break; case STATE.ZOOM_PAN: if (this.enableZoom) this.handleTouchMoveZoom(event); if (this.enablePan) this.handleTouchMovePan(event); break; //todo case STATE.ZOOM_ROTATE: default: this.state = STATE.NONE; } }; //mouse event onMouseDown = (event) => { if (this.locked) return; switch (event.button) { case 0: //left switch (this.mouseEvent.LEFT) { case HANDLE.PAN: if (this.enablePan === false) return; this.handleMouseDownPan(event); this.state = STATE.PAN; break; case HANDLE.ROTATE: if (this.enablePan === false) return; this.handleMouseDownRotate(event); this.state = STATE.ROTATE; break; default: this.state = STATE.NONE; } break; case 2: //right switch (this.mouseEvent.RIGHT) { case HANDLE.PAN: if (this.enablePan === false) return; this.handleMouseDownPan(event); this.state = STATE.PAN; break; case HANDLE.ROTATE: if (this.enablePan === false) return; this.handleMouseDownRotate(event); this.state = STATE.ROTATE; break; default: this.state = STATE.NONE; } break; default: this.state = STATE.NONE; } }; onMouseMove = (event) => { if (this.locked) return; switch (this.state) { case STATE.PAN: if (this.enablePan === false) return; this.handleMouseMovePan(event); break; case STATE.ROTATE: if (this.enableRotate === false) return; this.handleMouseMoveRotate(event); break; default: this.state = STATE.NONE; } }; onMouseWheel = (event) => { // console.log("this", this); if (this.locked) return; if (this.enableZoom === false) return; event.preventDefault(); this.handleMouseWheelZoom(event); }; onPreventDefault = (event) => { event.preventDefault(); }; //================================handle================================ //-------------------------rotate------------------------- handleTouchStartRotate = () => { const position = pointerPositions[pointers[0].pointerId]; this.rotateStart.set(position.x, position.y); }; handleTouchMoveRotate = (event) => { this.rotateEnd.set(event.pageX, event.pageY); let rotateDelta = this.rotateEnd .clone() .sub(this.rotateStart) .multiplyScalar(this.rotateSpeed); let element = this.domElement; let rotateX = (2 * Math.PI * rotateDelta.x) / element.clientHeight; let rotateY = (2 * Math.PI * rotateDelta.y) / element.clientHeight; this.rotate(rotateX, rotateY); this.rotateStart.copy(this.rotateEnd); }; handleMouseDownRotate = (event) => { this.rotateStart.set(event.pageX, event.pageY); }; handleMouseMoveRotate = (event) => { this.rotateEnd.set(event.pageX, event.pageY); let rotateDelta = this.rotateEnd .clone() .sub(this.rotateStart) .multiplyScalar(this.rotateSpeed); let element = this.domElement; let rotateX = (2 * Math.PI * rotateDelta.x) / element.clientHeight; let rotateY = (2 * Math.PI * rotateDelta.y) / element.clientHeight; this.rotate(rotateX, rotateY); this.rotateStart.copy(this.rotateEnd); }; //-------------------------zoom------------------------- handleTouchStartZoom = () => { const dx = pointers[0].pageX - pointers[1].pageX; const dy = pointers[0].pageY - pointers[1].pageY; const distance = Math.sqrt(dx * dx + dy * dy); this.zoomStart.set(0, distance); }; handleTouchMoveZoom = (event) => { const position = this.getSecondPointerPosition(event); const dx = event.pageX - position.x; const dy = event.pageY - position.y; const distance = Math.sqrt(dx * dx + dy * dy); let delta = Math.pow(distance / this.zoomStart.y, this.zoomSpeed); this.zoom(1 / delta); this.zoomStart.set(0, distance); }; handleMouseWheelZoom = (event) => { if (event.deltaY > 0) { //zoom out this.zoom(1.05 * this.zoomSpeed); } else { //zoom in this.zoom(0.95 * this.zoomSpeed); } }; //-------------------------pan------------------------- handleTouchStartPan = () => { if (pointers.length === 1) { const position = pointerPositions[pointers[0].pointerId]; this.panStart.set(position.x, position.y); } else { const x = 0.5 * (pointers[0].pageX + pointers[1].pageX); const y = 0.5 * (pointers[0].pageY + pointers[1].pageY); this.panStart.set(x, y); } }; handleTouchMovePan = (event) => { if (pointers.length === 1) { this.panEnd.set(event.pageX, event.pageY); } else { const position = this.getSecondPointerPosition(event); const x = 0.5 * (event.pageX + position.x); const y = 0.5 * (event.pageY + position.y); this.panEnd.set(x, y); } let panDelta = this.panEnd.clone().sub(this.panStart); this.pan(panDelta); this.panStart.copy(this.panEnd); }; handleMouseDownPan = (event) => { this.panStart.set(event.pageX, event.pageY); }; handleMouseMovePan = (event) => { this.panEnd.set(event.pageX, event.pageY); let panDelta = this.panEnd.clone().sub(this.panStart); this.pan(panDelta); this.panStart.copy(this.panEnd); }; rotate(x, y) { let r = y; if (Math.abs(x) > Math.abs(y)) r = x; let cameraRZ = this.camera.rotation.z; cameraRZ += r; if (Math.abs(cameraRZ) >= Math.PI * 2) { cameraRZ -= Math.sign(cameraRZ) * Math.PI * 2; } this.camera.rotation.z = cameraRZ; this.cameraUpdate(); } zoom(delta) { // if(this.camera.isPerspectiveCamera) { // let cameraY = this.camera.position.y // cameraY *= delta // cameraY = Math.max(cameraY, this.minDistance) // cameraY = Math.min(cameraY, this.maxDistance) // this.camera.position.y = cameraY //handle // } else if(this.camera.isOrthographicCamera) { // let zoom = this.camera.zoom // zoom *= 1/delta // console.log(zoom) // this.camera.zoom = zoom // this.camera.updateProjectionMatrix() // } let cameraY = this.camera.position.y; cameraY *= delta; cameraY = Math.max(cameraY, this.minDistance); cameraY = Math.min(cameraY, this.maxDistance); this.camera.position.y = cameraY; //handle if (this.camera.isOrthographicCamera) { let zoom = this.camera.zoom; zoom *= 1 / delta; zoom = Math.max(zoom, this.minZoom); zoom = Math.min(zoom, this.maxZoom); this.camera.zoom = zoom; this.camera.updateProjectionMatrix(); } this.cameraUpdate(); } pan(delta) { const element = this.domElement; const matrix = this.camera.matrix.clone(); const left = new Vector3(); const up = new Vector3(); let panDelta = delta.multiplyScalar(this.panSpeed); if (this.camera.isPerspectiveCamera) { let scalar = (2 * this.camera.position.y * Math.tan(((this.camera.fov / 2) * Math.PI) / 180.0)) / element.clientHeight; panDelta.multiplyScalar(scalar); left.setFromMatrixColumn(matrix, 0); left.multiplyScalar(-panDelta.x); up.setFromMatrixColumn(matrix, 1); up.multiplyScalar(panDelta.y); } else if (this.camera.isOrthographicCamera) { (panDelta.x = (panDelta.x * (this.camera.right - this.camera.left)) / this.camera.zoom / element.clientWidth), this.camera.matrix; (panDelta.y = (panDelta.y * (this.camera.top - this.camera.bottom)) / this.camera.zoom / element.clientHeight), this.camera.matrix; left.setFromMatrixColumn(matrix, 0); left.multiplyScalar(-panDelta.x); up.setFromMatrixColumn(matrix, 1); up.multiplyScalar(panDelta.y); } else { return; } this.camera.position.add(left).add(up); this.target.set(this.camera.position.x, 0, this.camera.position.z); this.cameraUpdate(); } lookAt(target, height) { if (!target) return; height = height !== undefined ? height : this.camera.position.y; this.camera.position.set(target.x, height, target.z); this.target.set(target.x, 0, target.z); this.camera.lookAt(this.target); } cameraUpdate = () => { this.camera.updateMatrix(); this.camera.updateProjectionMatrix(); }; update = () => { if (!this.enabled) return; }; }