diff --git a/index.html b/index.html index cd65f8a..9357f1c 100644 --- a/index.html +++ b/index.html @@ -7,7 +7,6 @@
-
diff --git a/src/main.ts b/src/main.ts index 9555a12..898ccc9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,8 @@ import "./style.css"; import * as THREE from "three"; -import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; import { OrbitControls } from "three/addons/controls/OrbitControls.js"; +import { NDS } from "./nds"; // initialize scene const scene = new THREE.Scene(); @@ -27,28 +27,8 @@ const handleResize = () => { window.addEventListener("resize", handleResize, false); handleResize(); -// load nds model -const loader = new GLTFLoader(); -const nds = await loader.loadAsync("/nintendo-ds/scene.gltf"); -nds.scene.scale.set(50, 50, 50); -scene.add(nds.scene); - -let topScreenMesh: THREE.Mesh | undefined = undefined; -let bottomScreenMesh: THREE.Mesh | undefined = undefined; - -nds.scene.traverse((child) => { - if (child instanceof THREE.Mesh) { - const material = child.material as THREE.Material; - if (material.name?.includes("screen_up")) { - topScreenMesh = child; - } else if (material.name?.includes("screen_down")) { - bottomScreenMesh = child; - } - } -}); - -if (!topScreenMesh) throw new Error(`Top screen mesh not found`); -if (!bottomScreenMesh) throw new Error(`Bottom screen mesh not found`); +const nds = new NDS(camera, renderer.domElement); +scene.add(nds); // create light const color = 0xffffff; @@ -57,54 +37,10 @@ const ambient = new THREE.AmbientLight(color, intensity); scene.add(ambient); const directional = new THREE.DirectionalLight(color, intensity); -directional.target = nds.scene; +directional.target = nds; directional.position.set(0, 100, 0); scene.add(directional); -const coordDisplay = document.getElementById("coord-display")!; - -renderer.domElement.addEventListener("mousemove", (event) => { - const rect = renderer.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, - ); - - const intersects = raycaster.intersectObjects([ - topScreenMesh!, - bottomScreenMesh!, - ]); - - if (intersects.length > 0) { - const intersection = intersects[0]; - const mesh = intersection.object as THREE.Mesh; - const uv = intersection.uv; - - if (uv) { - coordDisplay.hidden = false; - - const x = Math.floor(uv.x * 256); - const y = - mesh === topScreenMesh - ? Math.floor(uv.y * (1024 / 404) * 192) - : // invert coords only for bottom screen - Math.floor(192 - (1 - uv.y) * (1024 / 532) * 192); - - const screenName = mesh === topScreenMesh ? "top" : "bottom"; - coordDisplay.textContent = `${screenName} | pos: (${x}, ${y})`; - } else { - coordDisplay.hidden = true; - } - } else { - coordDisplay.hidden = true; - } -}); - // main loop renderer.setAnimationLoop(() => { controls.update(); diff --git a/src/nds.ts b/src/nds.ts new file mode 100644 index 0000000..37aa251 --- /dev/null +++ b/src/nds.ts @@ -0,0 +1,72 @@ +import * as THREE from "three"; +import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; + +export class NDS extends THREE.Object3D { + topScreenMesh: THREE.Mesh | undefined; + bottomScreenMesh: THREE.Mesh | undefined; + + public constructor(camera: THREE.Camera, domElement: HTMLCanvasElement) { + super(); + + const loader = new GLTFLoader(); + // load model + loader.load("/nintendo-ds/scene.gltf", ({ scene: model }) => { + model.scale.set(50, 50, 50); + + // find top and bottom screens + model.traverse((child) => { + if (child instanceof THREE.Mesh) { + const material = child.material as THREE.Material; + if (material.name?.includes("screen_up")) { + this.topScreenMesh = child; + } else if (material.name?.includes("screen_down")) { + this.bottomScreenMesh = child; + } + } + }); + + if (!this.topScreenMesh) throw new Error(`Missing top screen`); + if (!this.bottomScreenMesh) throw new Error(`Missing bottom screen`); + + const { topScreenMesh, bottomScreenMesh } = this; + + domElement.addEventListener("mousemove", (event) => { + 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, + ); + + const intersects = raycaster.intersectObjects([ + topScreenMesh, + bottomScreenMesh, + ]); + + if (intersects.length > 0) { + const intersection = intersects[0]; + const mesh = intersection.object as THREE.Mesh; + const uv = intersection.uv; + + if (uv) { + const x = Math.floor(uv.x * 256); + const y = + mesh === topScreenMesh + ? Math.floor(uv.y * (1024 / 404) * 192) + : // invert coords only for bottom screen + Math.floor(192 - (1 - uv.y) * (1024 / 532) * 192); + + x; + y; + } + } + }); + + super.add(model); + }); + } +} diff --git a/src/style.css b/src/style.css index 5d88312..293d3b1 100644 --- a/src/style.css +++ b/src/style.css @@ -1,16 +1,3 @@ body { margin: 0; } - -#coord-display { - position: fixed; - top: 5px; - left: 5px; - color: white; - font-family: monospace; - font-size: 16px; - background-color: #000000; - padding: 10px; - pointer-events: none; - z-index: 1000; -}