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}` }));
|
window.dispatchEvent(new KeyboardEvent("keydown", { key: `NDS_${button}` }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const raycast = (event: MouseEvent) => {
|
const raycast = (clientX: number, clientY: number) => {
|
||||||
const domElement = renderer.instance.domElement;
|
const domElement = renderer.instance.domElement;
|
||||||
const rect = domElement.getBoundingClientRect();
|
const rect = domElement.getBoundingClientRect();
|
||||||
|
|
||||||
const raycaster = new THREE.Raycaster();
|
const raycaster = new THREE.Raycaster();
|
||||||
raycaster.setFromCamera(
|
raycaster.setFromCamera(
|
||||||
new THREE.Vector2(
|
new THREE.Vector2(
|
||||||
((event.clientX - rect.left) / rect.width) * 2 - 1,
|
((clientX - rect.left) / rect.width) * 2 - 1,
|
||||||
-((event.clientY - rect.top) / rect.height) * 2 + 1,
|
-((clientY - rect.top) / rect.height) * 2 + 1,
|
||||||
),
|
),
|
||||||
camera.activeCamera.value,
|
camera.activeCamera.value,
|
||||||
);
|
);
|
||||||
@@ -297,41 +297,80 @@ const getScreenCanvas = (name: string) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const dispatchScreenEvent = (
|
const toScreenCoords = (
|
||||||
type: string,
|
|
||||||
canvas: HTMLCanvasElement,
|
canvas: HTMLCanvasElement,
|
||||||
intersection: THREE.Intersection,
|
intersection: THREE.Intersection,
|
||||||
) => {
|
) => {
|
||||||
if (!intersection.uv) return;
|
if (!intersection.uv) return null;
|
||||||
|
|
||||||
const logicalX = (1 - intersection.uv.x) * LOGICAL_WIDTH;
|
const logicalX = (1 - intersection.uv.x) * LOGICAL_WIDTH;
|
||||||
const logicalY = (1 - intersection.uv.y) * LOGICAL_HEIGHT;
|
const logicalY = (1 - intersection.uv.y) * LOGICAL_HEIGHT;
|
||||||
|
|
||||||
const rect = canvas.getBoundingClientRect();
|
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(
|
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,
|
bubbles: true,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
clientX: (logicalX / LOGICAL_WIDTH) * rect.width + rect.left,
|
touches: type === "touchend" ? [] : [touch],
|
||||||
clientY: (logicalY / LOGICAL_HEIGHT) * rect.height + rect.top,
|
changedTouches: [touch],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseDown = (event: MouseEvent) => {
|
const BUTTON_MAP = {
|
||||||
if (!hasAnimated.value) {
|
[X_BUTTON]: "X",
|
||||||
animateIntro();
|
[A_BUTTON]: "A",
|
||||||
return;
|
[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;
|
if (!intersection?.uv) return;
|
||||||
|
|
||||||
switch (intersection.object.name) {
|
switch (intersection.object.name) {
|
||||||
case TOP_SCREEN:
|
case TOP_SCREEN:
|
||||||
case BOTTOM_SCREEN: {
|
case BOTTOM_SCREEN: {
|
||||||
const canvas = getScreenCanvas(intersection.object.name);
|
const canvas = getScreenCanvas(intersection.object.name);
|
||||||
if (canvas) dispatchScreenEvent("mousedown", canvas, intersection);
|
if (canvas) onScreen(canvas, intersection);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,47 +391,84 @@ const handleMouseDown = (event: MouseEvent) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case X_BUTTON:
|
default: {
|
||||||
case A_BUTTON:
|
const button =
|
||||||
case Y_BUTTON:
|
BUTTON_MAP[intersection.object.name as keyof typeof BUTTON_MAP];
|
||||||
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];
|
|
||||||
if (button) pressButton(button);
|
if (button) pressButton(button);
|
||||||
break;
|
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) => {
|
const handleClick = (event: MouseEvent) => {
|
||||||
if (!hasAnimated.value) return;
|
if (!hasAnimated.value) return;
|
||||||
|
|
||||||
const intersection = raycast(event);
|
const intersection = raycast(event.clientX, event.clientY);
|
||||||
if (!intersection?.uv) return;
|
if (!intersection?.uv) return;
|
||||||
|
|
||||||
const canvas = getScreenCanvas(intersection.object.name);
|
const canvas = getScreenCanvas(intersection.object.name);
|
||||||
if (canvas) dispatchScreenEvent("click", canvas, intersection);
|
if (canvas) dispatchScreenEvent("click", canvas, intersection);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
const handleMouseUp = (event: MouseEvent) => {
|
||||||
if (mousePressedButton) {
|
releaseButton();
|
||||||
physicalButtonsDown.delete(mousePressedButton);
|
|
||||||
|
|
||||||
window.dispatchEvent(
|
const intersection = raycast(event.clientX, event.clientY);
|
||||||
new KeyboardEvent("keyup", { key: `NDS_${mousePressedButton}` }),
|
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("mousedown", handleMouseDown);
|
||||||
renderer.instance.domElement.addEventListener("click", handleClick);
|
renderer.instance.domElement.addEventListener("click", handleClick);
|
||||||
renderer.instance.domElement.addEventListener("mouseup", handleMouseUp);
|
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("click", handleClick);
|
||||||
renderer.instance.domElement.removeEventListener("mouseup", handleMouseUp);
|
renderer.instance.domElement.removeEventListener("mouseup", handleMouseUp);
|
||||||
|
renderer.instance.domElement.removeEventListener(
|
||||||
|
"touchstart",
|
||||||
|
handleTouchStart,
|
||||||
|
);
|
||||||
|
renderer.instance.domElement.removeEventListener(
|
||||||
|
"touchend",
|
||||||
|
handleTouchEnd,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
topScreenTexture?.dispose();
|
topScreenTexture?.dispose();
|
||||||
bottomScreenTexture?.dispose();
|
bottomScreenTexture?.dispose();
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
canvas.value.addEventListener("touchend", handleTouchEnd, { passive: true });
|
canvas.value.addEventListener("touchend", handleTouchEnd, { passive: true });
|
||||||
canvas.value.addEventListener("mousedown", handleSwipeMouseDown);
|
canvas.value.addEventListener("mousedown", handleSwipeMouseDown);
|
||||||
document.addEventListener("mouseup", handleSwipeMouseUp);
|
canvas.value.addEventListener("mouseup", handleSwipeMouseUp);
|
||||||
|
|
||||||
animationFrameId = requestAnimationFrame(renderFrame);
|
animationFrameId = requestAnimationFrame(renderFrame);
|
||||||
});
|
});
|
||||||
@@ -195,8 +195,8 @@ onUnmounted(() => {
|
|||||||
canvas.value.removeEventListener("touchstart", handleTouchStart);
|
canvas.value.removeEventListener("touchstart", handleTouchStart);
|
||||||
canvas.value.removeEventListener("touchend", handleTouchEnd);
|
canvas.value.removeEventListener("touchend", handleTouchEnd);
|
||||||
canvas.value.removeEventListener("mousedown", handleSwipeMouseDown);
|
canvas.value.removeEventListener("mousedown", handleSwipeMouseDown);
|
||||||
|
canvas.value.removeEventListener("mouseup", handleSwipeMouseUp);
|
||||||
}
|
}
|
||||||
document.removeEventListener("mouseup", handleSwipeMouseUp);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
|||||||
Reference in New Issue
Block a user