145 lines
4.0 KiB
Vue
145 lines
4.0 KiB
Vue
<script setup lang="ts">
|
|
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
import { useLoop, useTresContext } from "@tresjs/core";
|
|
import * as THREE from "three";
|
|
|
|
const props = defineProps<{
|
|
topScreenCanvas: HTMLCanvasElement | null;
|
|
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",
|
|
);
|
|
|
|
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],
|
|
() => {
|
|
if (!props.topScreenCanvas || !props.bottomScreenCanvas) return;
|
|
|
|
topScreenTexture = new THREE.CanvasTexture(props.topScreenCanvas);
|
|
topScreenTexture.minFilter = THREE.NearestFilter;
|
|
topScreenTexture.magFilter = THREE.NearestFilter;
|
|
topScreenTexture.flipY = false;
|
|
|
|
bottomScreenTexture = new THREE.CanvasTexture(props.bottomScreenCanvas);
|
|
bottomScreenTexture.minFilter = THREE.NearestFilter;
|
|
bottomScreenTexture.magFilter = THREE.NearestFilter;
|
|
bottomScreenTexture.flipY = false;
|
|
},
|
|
{ immediate: true },
|
|
);
|
|
|
|
watch(scene, () => {
|
|
if (!scene.value) return;
|
|
|
|
scene.value.traverse((child) => {
|
|
if (child instanceof THREE.Mesh) {
|
|
const material = child.material as THREE.Material;
|
|
|
|
if (material.name?.includes("screen_up") && topScreenTexture) {
|
|
topScreenTexture.repeat.set(1, 1024 / 404);
|
|
topScreenTexture.offset.set(0, -4 / 1024);
|
|
child.material = new THREE.MeshStandardMaterial({
|
|
map: topScreenTexture,
|
|
emissive: new THREE.Color(0x222222),
|
|
emissiveIntensity: 0.5,
|
|
});
|
|
topScreenMesh = child;
|
|
} else if (
|
|
material.name?.includes("screen_down") &&
|
|
bottomScreenTexture
|
|
) {
|
|
bottomScreenTexture.repeat.set(1, 1024 / 532);
|
|
bottomScreenTexture.offset.set(0, -1024 / 532 + 1);
|
|
child.material = new THREE.MeshStandardMaterial({
|
|
map: bottomScreenTexture,
|
|
emissive: new THREE.Color(0x222222),
|
|
emissiveIntensity: 0.5,
|
|
});
|
|
bottomScreenMesh = child;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
const { onRender } = useLoop();
|
|
|
|
onRender(() => {
|
|
if (topScreenTexture) topScreenTexture.needsUpdate = true;
|
|
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();
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<primitive v-if="scene" :object="scene" />
|
|
</template>
|