123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 |
- 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;
- };
- }
|