feat(nds): 3d touch support
This commit is contained in:
@@ -274,15 +274,15 @@ const pressButton = (button: string) => {
|
||||
window.dispatchEvent(new KeyboardEvent("keydown", { key: `NDS_${button}` }));
|
||||
};
|
||||
|
||||
const raycast = (event: MouseEvent) => {
|
||||
const raycast = (clientX: number, clientY: number) => {
|
||||
const domElement = renderer.instance.domElement;
|
||||
const rect = domElement.getBoundingClientRect();
|
||||
|
||||
const raycaster = new THREE.Raycaster();
|
||||
raycaster.setFromCamera(
|
||||
new THREE.Vector2(
|
||||
((event.clientX - rect.left) / rect.width) * 2 - 1,
|
||||
-((event.clientY - rect.top) / rect.height) * 2 + 1,
|
||||
((clientX - rect.left) / rect.width) * 2 - 1,
|
||||
-((clientY - rect.top) / rect.height) * 2 + 1,
|
||||
),
|
||||
camera.activeCamera.value,
|
||||
);
|
||||
@@ -297,41 +297,80 @@ const getScreenCanvas = (name: string) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const dispatchScreenEvent = (
|
||||
type: string,
|
||||
const toScreenCoords = (
|
||||
canvas: HTMLCanvasElement,
|
||||
intersection: THREE.Intersection,
|
||||
) => {
|
||||
if (!intersection.uv) return;
|
||||
if (!intersection.uv) return null;
|
||||
|
||||
const logicalX = (1 - intersection.uv.x) * LOGICAL_WIDTH;
|
||||
const logicalY = (1 - intersection.uv.y) * LOGICAL_HEIGHT;
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
return {
|
||||
clientX: (logicalX / LOGICAL_WIDTH) * rect.width + rect.left,
|
||||
clientY: (logicalY / LOGICAL_HEIGHT) * rect.height + rect.top,
|
||||
};
|
||||
};
|
||||
|
||||
const dispatchScreenEvent = (
|
||||
type: string,
|
||||
canvas: HTMLCanvasElement,
|
||||
intersection: THREE.Intersection,
|
||||
) => {
|
||||
const coords = toScreenCoords(canvas, intersection);
|
||||
if (!coords) return;
|
||||
|
||||
canvas.dispatchEvent(
|
||||
new MouseEvent(type, {
|
||||
new MouseEvent(type, { bubbles: true, cancelable: true, ...coords }),
|
||||
);
|
||||
};
|
||||
|
||||
const dispatchScreenTouchEvent = (
|
||||
type: string,
|
||||
canvas: HTMLCanvasElement,
|
||||
intersection: THREE.Intersection,
|
||||
) => {
|
||||
const coords = toScreenCoords(canvas, intersection);
|
||||
if (!coords) return;
|
||||
|
||||
const touch = new Touch({ identifier: 0, target: canvas, ...coords });
|
||||
|
||||
canvas.dispatchEvent(
|
||||
new TouchEvent(type, {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
clientX: (logicalX / LOGICAL_WIDTH) * rect.width + rect.left,
|
||||
clientY: (logicalY / LOGICAL_HEIGHT) * rect.height + rect.top,
|
||||
touches: type === "touchend" ? [] : [touch],
|
||||
changedTouches: [touch],
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const handleMouseDown = (event: MouseEvent) => {
|
||||
if (!hasAnimated.value) {
|
||||
animateIntro();
|
||||
return;
|
||||
}
|
||||
const BUTTON_MAP = {
|
||||
[X_BUTTON]: "X",
|
||||
[A_BUTTON]: "A",
|
||||
[Y_BUTTON]: "Y",
|
||||
[B_BUTTON]: "B",
|
||||
[SELECT_BUTTON]: "SELECT",
|
||||
[START_BUTTON]: "START",
|
||||
} as const;
|
||||
|
||||
const intersection = raycast(event);
|
||||
const handleInteraction = (
|
||||
clientX: number,
|
||||
clientY: number,
|
||||
onScreen: (
|
||||
canvas: HTMLCanvasElement,
|
||||
intersection: THREE.Intersection,
|
||||
) => void,
|
||||
) => {
|
||||
const intersection = raycast(clientX, clientY);
|
||||
if (!intersection?.uv) return;
|
||||
|
||||
switch (intersection.object.name) {
|
||||
case TOP_SCREEN:
|
||||
case BOTTOM_SCREEN: {
|
||||
const canvas = getScreenCanvas(intersection.object.name);
|
||||
if (canvas) dispatchScreenEvent("mousedown", canvas, intersection);
|
||||
if (canvas) onScreen(canvas, intersection);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -352,47 +391,84 @@ const handleMouseDown = (event: MouseEvent) => {
|
||||
break;
|
||||
}
|
||||
|
||||
case X_BUTTON:
|
||||
case A_BUTTON:
|
||||
case Y_BUTTON:
|
||||
case B_BUTTON:
|
||||
case SELECT_BUTTON:
|
||||
case START_BUTTON: {
|
||||
const BUTTON_MAP = {
|
||||
[X_BUTTON]: "X",
|
||||
[A_BUTTON]: "A",
|
||||
[Y_BUTTON]: "Y",
|
||||
[B_BUTTON]: "B",
|
||||
[SELECT_BUTTON]: "SELECT",
|
||||
[START_BUTTON]: "START",
|
||||
} as const;
|
||||
|
||||
const button = BUTTON_MAP[intersection.object.name];
|
||||
default: {
|
||||
const button =
|
||||
BUTTON_MAP[intersection.object.name as keyof typeof BUTTON_MAP];
|
||||
if (button) pressButton(button);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const releaseButton = () => {
|
||||
if (!mousePressedButton) return;
|
||||
|
||||
physicalButtonsDown.delete(mousePressedButton);
|
||||
window.dispatchEvent(
|
||||
new KeyboardEvent("keyup", { key: `NDS_${mousePressedButton}` }),
|
||||
);
|
||||
mousePressedButton = null;
|
||||
};
|
||||
|
||||
const handleMouseDown = (event: MouseEvent) => {
|
||||
if (!hasAnimated.value) {
|
||||
animateIntro();
|
||||
return;
|
||||
}
|
||||
|
||||
handleInteraction(event.clientX, event.clientY, (canvas, intersection) => {
|
||||
dispatchScreenEvent("mousedown", canvas, intersection);
|
||||
});
|
||||
};
|
||||
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (!hasAnimated.value) return;
|
||||
|
||||
const intersection = raycast(event);
|
||||
const intersection = raycast(event.clientX, event.clientY);
|
||||
if (!intersection?.uv) return;
|
||||
|
||||
const canvas = getScreenCanvas(intersection.object.name);
|
||||
if (canvas) dispatchScreenEvent("click", canvas, intersection);
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
if (mousePressedButton) {
|
||||
physicalButtonsDown.delete(mousePressedButton);
|
||||
const handleMouseUp = (event: MouseEvent) => {
|
||||
releaseButton();
|
||||
|
||||
window.dispatchEvent(
|
||||
new KeyboardEvent("keyup", { key: `NDS_${mousePressedButton}` }),
|
||||
);
|
||||
const intersection = raycast(event.clientX, event.clientY);
|
||||
if (!intersection?.uv) return;
|
||||
|
||||
mousePressedButton = null;
|
||||
const canvas = getScreenCanvas(intersection.object.name);
|
||||
if (canvas) dispatchScreenEvent("mouseup", canvas, intersection);
|
||||
};
|
||||
|
||||
const handleTouchStart = (event: TouchEvent) => {
|
||||
const touch = event.touches[0];
|
||||
if (!touch) return;
|
||||
|
||||
if (!hasAnimated.value) {
|
||||
animateIntro();
|
||||
return;
|
||||
}
|
||||
|
||||
handleInteraction(touch.clientX, touch.clientY, (canvas, intersection) => {
|
||||
dispatchScreenEvent("mousedown", canvas, intersection);
|
||||
dispatchScreenTouchEvent("touchstart", canvas, intersection);
|
||||
});
|
||||
};
|
||||
|
||||
const handleTouchEnd = (event: TouchEvent) => {
|
||||
releaseButton();
|
||||
|
||||
const touch = event.changedTouches[0];
|
||||
if (!touch) return;
|
||||
|
||||
const intersection = raycast(touch.clientX, touch.clientY);
|
||||
if (!intersection?.uv) return;
|
||||
|
||||
const canvas = getScreenCanvas(intersection.object.name);
|
||||
if (canvas) {
|
||||
dispatchScreenEvent("click", canvas, intersection);
|
||||
dispatchScreenTouchEvent("touchend", canvas, intersection);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -403,6 +479,11 @@ onMounted(() => {
|
||||
renderer.instance.domElement.addEventListener("mousedown", handleMouseDown);
|
||||
renderer.instance.domElement.addEventListener("click", handleClick);
|
||||
renderer.instance.domElement.addEventListener("mouseup", handleMouseUp);
|
||||
renderer.instance.domElement.addEventListener(
|
||||
"touchstart",
|
||||
handleTouchStart,
|
||||
);
|
||||
renderer.instance.domElement.addEventListener("touchend", handleTouchEnd);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -414,6 +495,14 @@ onUnmounted(() => {
|
||||
);
|
||||
renderer.instance.domElement.removeEventListener("click", handleClick);
|
||||
renderer.instance.domElement.removeEventListener("mouseup", handleMouseUp);
|
||||
renderer.instance.domElement.removeEventListener(
|
||||
"touchstart",
|
||||
handleTouchStart,
|
||||
);
|
||||
renderer.instance.domElement.removeEventListener(
|
||||
"touchend",
|
||||
handleTouchEnd,
|
||||
);
|
||||
}
|
||||
topScreenTexture?.dispose();
|
||||
bottomScreenTexture?.dispose();
|
||||
|
||||
@@ -178,7 +178,7 @@ onMounted(() => {
|
||||
});
|
||||
canvas.value.addEventListener("touchend", handleTouchEnd, { passive: true });
|
||||
canvas.value.addEventListener("mousedown", handleSwipeMouseDown);
|
||||
document.addEventListener("mouseup", handleSwipeMouseUp);
|
||||
canvas.value.addEventListener("mouseup", handleSwipeMouseUp);
|
||||
|
||||
animationFrameId = requestAnimationFrame(renderFrame);
|
||||
});
|
||||
@@ -195,8 +195,8 @@ onUnmounted(() => {
|
||||
canvas.value.removeEventListener("touchstart", handleTouchStart);
|
||||
canvas.value.removeEventListener("touchend", handleTouchEnd);
|
||||
canvas.value.removeEventListener("mousedown", handleSwipeMouseDown);
|
||||
canvas.value.removeEventListener("mouseup", handleSwipeMouseUp);
|
||||
}
|
||||
document.removeEventListener("mouseup", handleSwipeMouseUp);
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
|
||||
Reference in New Issue
Block a user