From 1768dd88bfc762497d0dfd8185484e5447821d37 Mon Sep 17 00:00:00 2001 From: HF Date: Thu, 25 Jan 2024 03:41:20 +0100 Subject: [PATCH] move 3d clicking and touching for placing from renderer into controls --- src/controls/VoxelPainterControls.js | 143 +++++++++++++++- src/core/utils.js | 14 ++ src/ui/Renderer2D.js | 1 + src/ui/Renderer3D.js | 238 +++------------------------ 4 files changed, 171 insertions(+), 225 deletions(-) diff --git a/src/controls/VoxelPainterControls.js b/src/controls/VoxelPainterControls.js index 1811b61..a0ca2cc 100644 --- a/src/controls/VoxelPainterControls.js +++ b/src/controls/VoxelPainterControls.js @@ -27,6 +27,13 @@ import { THREE_CANVAS_HEIGHT, VIEW_UPDATE_DELAY, } from '../core/constants'; +import { + getDiff, + getTapOrClickCenter, +} from '../core/utils'; +import { + selectHoverColor, +} from '../store/actions'; const STORE_UPDATE_DELAY = VIEW_UPDATE_DELAY / 2; // Mouse buttons @@ -126,6 +133,14 @@ class VoxelPainterControls { vec = new Vector3(); // forcing next update forceNextUpdate = false; + // start of touch or click + clickTapStartTime = 0; + // on touch: true if current tab was ever more than one figher at any time + wasEverMultiTap = false; + // screen coords of where a tap/click started + clickTapStartCoords = [0, 0]; + // on touch: timeout to detect long-press + tapTimeout = null; constructor(renderer, camera, target, domElement, store) { this.renderer = renderer; @@ -276,9 +291,6 @@ class VoxelPainterControls { panStart.copy(panEnd); } - // eslint-disable-next-line class-methods-use-this - handleMouseUp() {} - handleMouseWheel(event) { const scaleDelta = 0.95 ** zoomSpeed; if (event.deltaY < 0) { @@ -394,8 +406,102 @@ class VoxelPainterControls { if (enableRotate) this.handleTouchMoveRotate(event); } - // eslint-disable-next-line class-methods-use-this - handleTouchEnd() {} + placeVoxelOnScreen(screenCoords, allowedDistance) { + const intersect = this.renderer.castRay(screenCoords); + if (!intersect) { + return; + } + const target = intersect.point.clone() + .add(intersect.face.normal.multiplyScalar(0.5)) + .floor() + .addScalar(0.5) + .floor(); + if (target.clone().sub(this.camera.position).length() < allowedDistance) { + const [x, y, z] = target.toArray(); + this.renderer.placeVoxel(x, y, z); + } + } + + deleteVoxelOnScreen(screenCoords, allowedDistance) { + const intersect = this.renderer.castRay(screenCoords); + if (!intersect) { + return; + } + const target = intersect.point.clone() + .add(intersect.face.normal.multiplyScalar(-0.5)) + .floor() + .addScalar(0.5) + .floor(); + if (target.y < 0) { + return; + } + if (target.clone().sub(this.camera.position).length() < allowedDistance) { + const [x, y, z] = target.toArray(); + this.renderer.placeVoxel(x, y, z, 0); + } + } + + selectColorOnScreen(screenCoords) { + // selectHoverColor doesn't actually do anything with the coords + this.store.dispatch(selectHoverColor(screenCoords)); + } + + handleMouseUp(event) { + if (!this.clickTapStartTime + || Date.now() - this.clickTapStartTime > 300 + || this.store.getState().fetching.fetchingPixel + ) { + return; + } + const screenCoords = getTapOrClickCenter(event); + if (getDiff(screenCoords, this.clickTapStartCoords) > 6) { + return; + } + + switch (event.button) { + case 0: + // left + this.placeVoxelOnScreen(screenCoords, 120); + break; + case 1: + // middle + this.selectColorOnScreen(screenCoords); + break; + case 2: + // right + this.deleteVoxelOnScreen(screenCoords, 120); + break; + default: + } + } + + handleTouchEnd(event) { + if (event.touches.length + || !this.clickTapStartTime + || Date.now() - this.clickTapStartTime > 300 + || this.store.getState().fetching.fetchingPixel + ) { + return; + } + const screenCoords = getTapOrClickCenter(event); + if (getDiff(screenCoords, this.clickTapStartCoords) > 6) { + return; + } + this.placeVoxelOnScreen(screenCoords, 90); + } + + onLongTap(event) { + if (!this.clickTapStartTime + || this.store.getState().fetching.fetchingPixel + ) { + return; + } + const screenCoords = getTapOrClickCenter(event); + if (getDiff(screenCoords, this.clickTapStartCoords) > 6) { + return; + } + this.deleteVoxelOnScreen(screenCoords, 90); + } onMouseMove(event) { event.preventDefault(); @@ -417,6 +523,7 @@ class VoxelPainterControls { onMouseUp(event) { this.handleMouseUp(event); + this.clickTapStartTime = 0; document.removeEventListener('mousemove', this.onMouseMove, false); document.removeEventListener('mouseup', this.onMouseUp, false); this.state = STATE.NONE; @@ -439,6 +546,17 @@ class VoxelPainterControls { onTouchStart(event) { event.preventDefault(); + if (event.touches.length === 1) { + this.clickTapStartTime = Date.now(); + this.clickTapStartCoords = getTapOrClickCenter(event); + this.tapTimeout = setTimeout(() => { + this.onLongTap(event); + }, 600); + } else { + this.clickTapStartTime = 0; + clearTimeout(this.tapTimeout); + } + switch (event.touches.length) { case 1: switch (TOUCHES.ONE) { @@ -489,6 +607,12 @@ class VoxelPainterControls { event.preventDefault(); event.stopPropagation(); + const screenCoords = getTapOrClickCenter(event); + if (getDiff(screenCoords, this.clickTapStartCoords) > 6) { + clearTimeout(this.tapTimeout); + this.clickTapStartTime = 0; + } + switch (this.state) { case STATE.TOUCH_ROTATE: if (!enableRotate) { @@ -520,7 +644,12 @@ class VoxelPainterControls { } onTouchEnd(event) { - this.handleTouchEnd(event); + event.preventDefault(); + if (!event.touches.length) { + clearTimeout(this.tapTimeout); + this.handleTouchEnd(event); + this.clickTapStartTime = 0; + } this.state = STATE.NONE; } @@ -532,6 +661,8 @@ class VoxelPainterControls { onMouseDown(event) { // Prevent the browser from scrolling. event.preventDefault(); + this.clickTapStartTime = Date.now(); + this.clickTapStartCoords = getTapOrClickCenter(event); // Manually set the focus since calling preventDefault above // prevents the browser from setting it automatically. diff --git a/src/core/utils.js b/src/core/utils.js index cf3f8c0..e357853 100644 --- a/src/core/utils.js +++ b/src/core/utils.js @@ -636,6 +636,20 @@ export function getDateKeyOfTs(ts) { return `${year}${month}${day}`; } +/* + * get largest distance between axes of array + */ +export function getDiff(arr1, arr2) { + let largest = 0; + for (let i = 0; i < arr1.length; i += 1) { + const length = Math.abs(arr1[i] - arr2[i]); + if (length > largest) { + largest = length; + } + } + return largest; +} + /* * get screen coords of touch / mouse event * @param event MouseEvent or TouchEvent diff --git a/src/ui/Renderer2D.js b/src/ui/Renderer2D.js index 71ff57c..ddbef38 100644 --- a/src/ui/Renderer2D.js +++ b/src/ui/Renderer2D.js @@ -409,6 +409,7 @@ class Renderer2D extends Renderer { context.fillRect(x, y, TILE_SIZE, TILE_SIZE); } else { chunk = this.chunkLoader.getChunk(tiledZoom, cx, cy); + // TODO is chunk nnow an object or image? it doesnt make any sense if (chunk) { context.drawImage(chunk, x, y); if (touch) { diff --git a/src/ui/Renderer3D.js b/src/ui/Renderer3D.js index f5ace11..97f83a0 100644 --- a/src/ui/Renderer3D.js +++ b/src/ui/Renderer3D.js @@ -28,7 +28,6 @@ import ChunkLoader from './ChunkLoader3D'; import { getChunkOfPixel, getOffsetOfPixel, - getTapOrClickCenter, } from '../core/utils'; import { THREE_TILE_SIZE, @@ -36,7 +35,6 @@ import { import { setHover, unsetHover, - selectHoverColor, } from '../store/actions'; import pixelTransferController from './PixelTransferController'; @@ -64,14 +62,7 @@ class Renderer3D extends Renderer { // temp variables for mouse events mouse = new Vector2(); raycaster = new Raycaster(); - pressTime; - pressCdTime; - multitap = 0; lastIntersect = 0; - // on touch: true if current tab was ever more than one figher at any time - wasEverMultiTap = false; - // screen coords of where a tap/click started - clickTapStartCoords = [0, 0]; constructor(store) { super(store); @@ -194,26 +185,9 @@ class Renderer3D extends Renderer { this.onDocumentMouseMove = this.onDocumentMouseMove.bind(this); this.onDocumentTouchMove = this.onDocumentTouchMove.bind(this); - // eslint-disable-next-line max-len - this.onDocumentMouseDownOrTouchStart = this.onDocumentMouseDownOrTouchStart.bind(this); - this.onDocumentMouseUp = this.onDocumentMouseUp.bind(this); this.onWindowResize = this.onWindowResize.bind(this); - this.onDocumentTouchEnd = this.onDocumentTouchEnd.bind(this); - this.multiTapEnd = this.multiTapEnd.bind(this); domElement.addEventListener('mousemove', this.onDocumentMouseMove, false); domElement.addEventListener('touchmove', this.onDocumentTouchMove, false); - domElement.addEventListener( - 'mousedown', - this.onDocumentMouseDownOrTouchStart, - false, - ); - domElement.addEventListener( - 'touchstart', - this.onDocumentMouseDownOrTouchStart, - false, - ); - domElement.addEventListener('touchend', this.onDocumentTouchEnd, false); - domElement.addEventListener('mouseup', this.onDocumentMouseUp, false); window.addEventListener('resize', this.onWindowResize, false); this.updateCanvasData(state); @@ -511,25 +485,34 @@ class Renderer3D extends Renderer { } onDocumentMouseMove(event) { - event.preventDefault(); - this.updateRollOverMesh( (event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, ); } - onDocumentMouseDownOrTouchStart(event) { - this.pressTime = Date.now(); - this.clickTapStartCoords = getTapOrClickCenter(event); - this.wasEverMultiTap = (event.touches?.length > 1); + onDocumentTouchMove(event) { + if (this.rollOverMesh.position.y !== -10) { + console.log('unset hover'); + this.store.dispatch(unsetHover()); + this.rollOverMesh.position.y = -10; + } } - onDocumentTouchMove(event) { - if (event.touches?.length > 1) { - this.wasEverMultiTap = true; + castRay([clientX, clientY]) { + const { + mouse, camera, raycaster, objects, + } = this; + mouse.set( + (clientX / window.innerWidth) * 2 - 1, + -(clientY / window.innerHeight) * 2 + 1, + ); + raycaster.setFromCamera(mouse, camera); + const intersects = raycaster.intersectObjects(objects); + if (intersects.length > 0) { + return intersects[0]; } - this.updateRollOverMesh(0, 0); + return null; } getPointedColor() { @@ -540,7 +523,6 @@ class Renderer3D extends Renderer { camera, } = this; raycaster.setFromCamera(mouse, camera); - const intersects = raycaster.intersectObjects(objects); if (intersects.length <= 0) { return null; @@ -586,188 +568,6 @@ class Renderer3D extends Renderer { curColor, ); } - - multiTapEnd(event) { - const { - store, - multitap, - } = this; - this.multitap = 0; - const state = store.getState(); - - if (!state.canvas.hover || this.wasEverMultiTap) { - return; - } - const [clientX, clientY] = getTapOrClickCenter(event); - const { clickTapStartCoords } = this; - const coordsDiff = [ - clickTapStartCoords[0] - clientX, - clickTapStartCoords[1] - clientY, - ].map(Math.abs); - if (coordsDiff[0] > 5 || coordsDiff[1] > 5) { - return; - } - - switch (multitap) { - case 1: { - // single tap - // Place Voxel - if (this.rollOverMesh.position.y < 0) { - return; - } - this.placeVoxel(...state.canvas.hover); - break; - } - case 2: { - // double tap - // Remove Voxel - const { - mouse, - raycaster, - camera, - objects, - } = this; - mouse.set(0, 0); - raycaster.setFromCamera(mouse, camera); - const intersects = raycaster.intersectObjects(objects); - if (intersects.length > 0) { - const intersect = intersects[0]; - const target = intersect.point.clone() - .add(intersect.face.normal.multiplyScalar(-0.5)) - .floor() - .addScalar(0.5) - .floor(); - if (target.y < 0) { - return; - } - if (target.clone().sub(camera.position).length() <= 50) { - const [x, y, z] = target.toArray(); - this.placeVoxel(x, y, z, 0); - } - } - break; - } - default: - break; - } - } - - onDocumentTouchEnd(event) { - event.preventDefault(); - if (event.touches.length) { - return; - } - - const curTime = Date.now(); - if (curTime - this.pressTime > 600) { - this.multitap = 0; - return; - } - // if we want to do something with triple tap, - // we should reset on every tap - // but we don't need that right now... - if (this.multitap === 0) { - setTimeout(() => this.multiTapEnd(event), 500); - } - this.multitap += 1; - } - - onDocumentMouseUp(event) { - const curTime = Date.now(); - if (curTime - this.pressCdTime < 200) { - return; - } - if (curTime - this.pressTime > 500) { - this.pressCdTime = curTime; - return; - } - - const state = this.store.getState(); - const { - isOnMobile, - } = state.user; - const { - fetchingPixel, - } = state.fetching; - if (fetchingPixel || isOnMobile) { - return; - } - - if (!state.canvas.hover) { - return; - } - const [clientX, clientY] = getTapOrClickCenter(event); - const { clickTapStartCoords } = this; - const coordsDiff = [ - clickTapStartCoords[0] - clientX, - clickTapStartCoords[1] - clientY, - ].map(Math.abs); - if (coordsDiff[0] > 5 || coordsDiff[1] > 5) { - return; - } - - event.preventDefault(); - const { button } = event; - const { - innerWidth, - innerHeight, - } = window; - const { - store, - mouse, - } = this; - - mouse.set( - (clientX / innerWidth) * 2 - 1, - -(clientY / innerHeight) * 2 + 1, - ); - - if (button === 1) { - // middle mouse button - store.dispatch(selectHoverColor()); - return; - } - - const { - camera, - objects, - raycaster, - } = this; - - raycaster.setFromCamera(mouse, camera); - - const intersects = raycaster.intersectObjects(objects); - if (intersects.length > 0) { - const intersect = intersects[0]; - - if (button === 0) { - // left mouse button - const target = intersect.point.clone() - .add(intersect.face.normal.multiplyScalar(0.5)) - .floor() - .addScalar(0.5) - .floor(); - if (target.clone().sub(camera.position).length() < 120) { - const [x, y, z] = target.toArray(); - this.placeVoxel(x, y, z); - } - } else if (button === 2) { - // right mouse button - const target = intersect.point.clone() - .add(intersect.face.normal.multiplyScalar(-0.5)) - .floor() - .addScalar(0.5) - .floor(); - if (target.y < 0) { - return; - } - if (target.clone().sub(camera.position).length() < 120) { - const [x, y, z] = target.toArray(); - this.placeVoxel(x, y, z, 0); - } - } - } - } } export default Renderer3D;