move 3d clicking and touching for placing from renderer into controls

This commit is contained in:
HF 2024-01-25 03:41:20 +01:00
parent e74b3f923b
commit 1768dd88bf
4 changed files with 171 additions and 225 deletions

View File

@ -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.

View File

@ -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

View File

@ -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) {

View File

@ -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;