feat: dispatch clicks on the 3d models to the canvases

This commit is contained in:
2025-12-14 14:36:30 +01:00
parent fd8f0ea86a
commit e720f5a6c7
3 changed files with 92 additions and 9 deletions

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { useLoop } from "@tresjs/core";
import { useLoop, useTresContext } from "@tresjs/core";
import * as THREE from "three";
const props = defineProps<{
@@ -8,6 +8,11 @@ const props = defineProps<{
bottomScreenCanvas: HTMLCanvasElement | null;
}>();
const emit = defineEmits<{
topScreenClick: [x: number, y: number];
bottomScreenClick: [x: number, y: number];
}>();
const { state: model } = useLoader(
GLTFLoader,
"/models/nintendo-ds/scene.gltf",
@@ -17,6 +22,10 @@ const scene = computed(() => model.value?.scene);
let topScreenTexture: THREE.CanvasTexture | null = null;
let bottomScreenTexture: THREE.CanvasTexture | null = null;
let topScreenMesh: THREE.Mesh | null = null;
let bottomScreenMesh: THREE.Mesh | null = null;
const { camera, renderer } = useTresContext();
watch(
() => [props.topScreenCanvas, props.bottomScreenCanvas],
@@ -51,6 +60,7 @@ watch(scene, () => {
emissive: new THREE.Color(0x222222),
emissiveIntensity: 0.5,
});
topScreenMesh = child;
} else if (
material.name?.includes("screen_down") &&
bottomScreenTexture
@@ -62,6 +72,7 @@ watch(scene, () => {
emissive: new THREE.Color(0x222222),
emissiveIntensity: 0.5,
});
bottomScreenMesh = child;
}
}
});
@@ -74,7 +85,55 @@ onRender(() => {
if (bottomScreenTexture) bottomScreenTexture.needsUpdate = true;
});
const handleClick = (event: MouseEvent) => {
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,
),
camera.activeCamera.value,
);
if (topScreenMesh) {
const intersects = raycaster.intersectObject(topScreenMesh);
if (intersects[0]) {
const uv = intersects[0].uv;
if (uv) {
const x = Math.floor(uv.x * 256);
const y = Math.floor(uv.y * (1024 / 404) * 192);
emit("topScreenClick", x, y);
return;
}
}
}
if (bottomScreenMesh) {
const intersects = raycaster.intersectObject(bottomScreenMesh);
if (intersects[0]) {
const uv = intersects[0].uv;
if (uv) {
const x = Math.floor(uv.x * 256);
const y = Math.floor(192 - (1 - uv.y) * (1024 / 532) * 192);
emit("bottomScreenClick", x, y);
}
}
}
};
onMounted(() => {
if (renderer) {
renderer.instance.domElement.addEventListener("click", handleClick);
}
});
onUnmounted(() => {
if (renderer) {
renderer.instance.domElement.removeEventListener("click", handleClick);
}
topScreenTexture?.dispose();
bottomScreenTexture?.dispose();
});

View File

@@ -83,17 +83,17 @@ const renderFrame = (timestamp: number) => {
animationFrameId = requestAnimationFrame(renderFrame);
};
provide("registerUpdateCallback", registerUpdateCallback);
provide("registerRenderCallback", registerRenderCallback);
provide("registerScreenClickCallback", registerScreenClickCallback);
provide("registerScreenMouseWheelCallback", registerScreenMouseWheelCallback);
onMounted(() => {
if (!canvas.value) throw new Error("Missing canvas");
ctx = canvas.value.getContext("2d");
if (!ctx) throw new Error("Missing 2d context");
provide("registerUpdateCallback", registerUpdateCallback);
provide("registerRenderCallback", registerRenderCallback);
provide("registerScreenClickCallback", registerScreenClickCallback);
provide("registerScreenMouseWheelCallback", registerScreenMouseWheelCallback);
canvas.value.addEventListener("click", handleCanvasClick);
canvas.value.addEventListener("wheel", handleCanvasWheel, { passive: true });