const VIRTUAL_JOYSTICK_HEIGHT = 0.8; const HORIZONTAL_LOOK_SPEED = 0.35; const VERTICAL_LOOK_SPEED = 0.18; export default class TouchEventsHandler { constructor(cursor, cameraController, pinchEmitter) { this.cursor = cursor; this.cameraController = cameraController; this.pinchEmitter = pinchEmitter; this.touches = []; this.touchReservedForCursor = null; this.touchesReservedForPinch = []; this.touchReservedForLookControls = null; this.needsPinch = false; this.pinchTouchId1 = -1; this.pinchTouchId2 = -1; this.handleTouchStart = this.handleTouchStart.bind(this); this.singleTouchStart = this.singleTouchStart.bind(this); this.handleTouchMove = this.handleTouchMove.bind(this); this.singleTouchMove = this.singleTouchMove.bind(this); this.handleTouchEnd = this.handleTouchEnd.bind(this); this.singleTouchEnd = this.singleTouchEnd.bind(this); this.addEventListeners(); } addEventListeners() { document.addEventListener("touchstart", this.handleTouchStart); document.addEventListener("touchmove", this.handleTouchMove); document.addEventListener("touchend", this.handleTouchEnd); document.addEventListener("touchcancel", this.handleTouchEnd); } tearDown() { document.removeEventListener("touchstart", this.handleTouchStart); document.removeEventListener("touchmove", this.handleTouchMove); document.removeEventListener("touchend", this.handleTouchEnd); document.removeEventListener("touchcancel", this.handleTouchEnd); } handleTouchStart(e) { for (let i = 0; i < e.changedTouches.length; i++) { this.singleTouchStart(e.changedTouches[i]); } } singleTouchStart(touch) { if (touch.clientY / window.innerHeight >= VIRTUAL_JOYSTICK_HEIGHT) { return; } if (!this.touchReservedForCursor) { const targetX = (touch.clientX / window.innerWidth) * 2 - 1; const targetY = -(touch.clientY / window.innerHeight) * 2 + 1; this.cursor.moveCursor(targetX, targetY); this.cursor.forceCursorUpdate(); if (this.cursor.startInteraction()) { this.touchReservedForCursor = touch; } } this.touches.push(touch); } handleTouchMove(e) { for (let i = 0; i < e.touches.length; i++) { this.singleTouchMove(e.touches[i]); } if (this.needsPinch) { this.pinch(); this.needsPinch = false; } } singleTouchMove(touch) { if (this.touchReservedForCursor && touch.identifier === this.touchReservedForCursor.identifier) { const targetX = (touch.clientX / window.innerWidth) * 2 - 1; const targetY = -(touch.clientY / window.innerHeight) * 2 + 1; this.cursor.moveCursor(targetX, targetY); return; } if (touch.clientY / window.innerHeight >= VIRTUAL_JOYSTICK_HEIGHT) return; if (!this.touches.some(t => touch.identifier === t.identifier)) { return; } let pinchIndex = this.touchesReservedForPinch.findIndex(t => touch.identifier === t.identifier); if (pinchIndex !== -1) { this.touchesReservedForPinch[pinchIndex] = touch; } else if (this.touchesReservedForPinch.length < 2) { this.touchesReservedForPinch.push(touch); pinchIndex = this.touchesReservedForPinch.length - 1; } if (this.touchesReservedForPinch.length == 2 && pinchIndex !== -1) { if (this.touchReservedForLookControls && touch.identifier === this.touchReservedForLookControls.identifier) { this.touchReservedForLookControls = null; } this.needsPinch = true; return; } if (!this.touchReservedForLookControls) { this.touchReservedForLookControls = touch; } if (touch.identifier === this.touchReservedForLookControls.identifier) { if (!this.touchReservedForCursor) { this.cursor.moveCursor( (touch.clientX / window.innerWidth) * 2 - 1, -(touch.clientY / window.innerHeight) * 2 + 1 ); } this.look(this.touchReservedForLookControls, touch); this.touchReservedForLookControls = touch; return; } } pinch() { const t1 = this.touchesReservedForPinch[0]; const t2 = this.touchesReservedForPinch[1]; const isNewPinch = t1.identifier !== this.pinchTouchId1 || t2.identifier !== this.pinchTouchId2; const pinchDistance = TouchEventsHandler.distance(t1.clientX, t1.clientY, t2.clientX, t2.clientY); this.pinchEmitter.emit("pinch", { isNewPinch: isNewPinch, distance: pinchDistance }); this.pinchTouchId1 = t1.identifier; this.pinchTouchId2 = t2.identifier; } look(prevTouch, touch) { const deltaPitch = (touch.clientY - prevTouch.clientY) * VERTICAL_LOOK_SPEED; const deltaYaw = (touch.clientX - prevTouch.clientX) * HORIZONTAL_LOOK_SPEED; this.cameraController.look(deltaPitch, deltaYaw); } handleTouchEnd(e) { for (let i = 0; i < e.changedTouches.length; i++) { this.singleTouchEnd(e.changedTouches[i]); } } singleTouchEnd(touch) { const touchIndex = this.touches.findIndex(t => touch.identifier === t.identifier); if (touchIndex === -1) { return; } this.touches.splice(touchIndex, 1); if (this.touchReservedForCursor && touch.identifier === this.touchReservedForCursor.identifier) { this.cursor.endInteraction(touch); this.touchReservedForCursor = null; return; } const pinchIndex = this.touchesReservedForPinch.findIndex(t => touch.identifier === t.identifier); if (pinchIndex !== -1) { this.touchesReservedForPinch.splice(pinchIndex, 1); this.pinchTouchId1 = -1; this.pinchTouchId2 = -1; } if (this.touchReservedForLookControls && touch.identifier === this.touchReservedForLookControls.identifier) { this.touchReservedForLookControls = null; } } static distance = (x1, y1, x2, y2) => { const x = x1 - x2; const y = y1 - y2; return Math.sqrt(x * x + y * y); }; }