diff --git a/src/nds.ts b/src/nds.ts index 7c97a6a..d2a5021 100644 --- a/src/nds.ts +++ b/src/nds.ts @@ -1,5 +1,7 @@ import * as THREE from "three"; import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"; +import { ScreenManager } from "./screen-manager"; +import { HomeScreen } from "./screens/home-screen"; const SCREEN_SOURCE_TEX_SIZE = 1024; const TOP_SCREEN_SOURCE_TEX_HEIGHT = SCREEN_SOURCE_TEX_SIZE / 404; @@ -21,19 +23,23 @@ const createScreenCanvas = () => { return canvas; }; -type Screen = { +type PhysicalScreen = { mesh: THREE.Mesh; canvas: HTMLCanvasElement; texture: THREE.CanvasTexture; }; export class NDS extends THREE.Object3D { - private botScreen: Screen | null = null; - private topScreen: Screen | null = null; + private botScreen: PhysicalScreen | null = null; + private topScreen: PhysicalScreen | null = null; + private screenManager: ScreenManager; public constructor(camera: THREE.Camera, domElement: HTMLCanvasElement) { super(); + // Initialize screen manager + this.screenManager = new ScreenManager(new HomeScreen()); + const loader = new GLTFLoader(); // load model loader.load("/nintendo-ds/scene.gltf", ({ scene: model }) => { @@ -101,10 +107,10 @@ export class NDS extends THREE.Object3D { emissiveIntensity: 0.5, }); - this.botScreen = { mesh: topScreenMesh, canvas, texture }; + this.botScreen = { mesh: botScreenMesh, canvas, texture }; } - domElement.addEventListener("mousemove", (event) => { + domElement.addEventListener("click", (event) => { const rect = domElement.getBoundingClientRect(); const raycaster = new THREE.Raycaster(); @@ -116,27 +122,17 @@ export class NDS extends THREE.Object3D { camera, ); - const intersects = raycaster.intersectObjects([ - topScreenMesh, - botScreenMesh, - ]); + const intersects = raycaster.intersectObject(botScreenMesh); if (intersects.length > 0) { - const intersection = intersects[0]; - const mesh = intersection.object as THREE.Mesh; - const uv = intersection.uv; - + const uv = intersects[0].uv; if (uv) { const x = Math.floor(uv.x * 256); const y = Math.floor( - mesh === topScreenMesh - ? uv.y * TOP_SCREEN_SOURCE_TEX_HEIGHT * 192 - : // invert coords only for bottom screen - 192 - (1 - uv.y) * BOT_SCREEN_SOURCE_TEX_HEIGHT * 192, + 192 - (1 - uv.y) * BOT_SCREEN_SOURCE_TEX_HEIGHT * 192, ); - x; - y; + this.screenManager.handleTouch(x, y); } } }); @@ -146,10 +142,17 @@ export class NDS extends THREE.Object3D { } public update(): void { - if (this.topScreen) { + if (!this.topScreen || !this.botScreen) return; + + { + const ctx = this.topScreen.canvas.getContext("2d")!; + this.screenManager.renderTop(ctx); this.topScreen.texture.needsUpdate = true; } - if (this.botScreen) { + + { + const ctx = this.botScreen.canvas.getContext("2d")!; + this.screenManager.renderBottom(ctx); this.botScreen.texture.needsUpdate = true; } } diff --git a/src/screen-manager.ts b/src/screen-manager.ts new file mode 100644 index 0000000..02fa253 --- /dev/null +++ b/src/screen-manager.ts @@ -0,0 +1,35 @@ +import type { Screen, ScreenContext } from "./screen"; + +export class ScreenManager { + private currentScreen: Screen; + private context: ScreenContext; + + constructor(initialScreen: Screen) { + this.currentScreen = initialScreen; + this.context = { + navigate: (screen: Screen) => { + this.currentScreen = screen; + }, + }; + } + + renderTop(ctx: CanvasRenderingContext2D) { + ctx.clearRect(0, 0, 256, 192); + ctx.save(); + this.currentScreen.renderTop(ctx); + ctx.restore(); + } + + renderBottom(ctx: CanvasRenderingContext2D) { + ctx.clearRect(0, 0, 256, 192); + ctx.save(); + this.currentScreen.renderBottom(ctx); + ctx.restore(); + } + + handleTouch(x: number, y: number) { + if (this.currentScreen.handleTouch) { + this.currentScreen.handleTouch(x, y, this.context); + } + } +} diff --git a/src/screen.ts b/src/screen.ts new file mode 100644 index 0000000..3cb6b9e --- /dev/null +++ b/src/screen.ts @@ -0,0 +1,9 @@ +export interface ScreenContext { + navigate: (screen: Screen) => void; +} + +export interface Screen { + renderTop(ctx: CanvasRenderingContext2D): void; + renderBottom(ctx: CanvasRenderingContext2D): void; + handleTouch?(x: number, y: number, context: ScreenContext): void; +} diff --git a/src/screens/contact-screen.ts b/src/screens/contact-screen.ts new file mode 100644 index 0000000..f21f834 --- /dev/null +++ b/src/screens/contact-screen.ts @@ -0,0 +1,25 @@ +import type { Screen, ScreenContext } from "../screen"; +import { HomeScreen } from "./home-screen"; + +export class ContactScreen implements Screen { + renderTop(ctx: CanvasRenderingContext2D) { + ctx.fillStyle = "#ffffff"; + ctx.font = "10px NDS10"; + ctx.textBaseline = "top"; + ctx.fillText("CONTACT", 1, 1); + } + + renderBottom(ctx: CanvasRenderingContext2D) { + ctx.fillStyle = "#ffffff"; + ctx.font = "10px NDS10"; + ctx.textAlign = "left"; + ctx.textBaseline = "bottom"; + ctx.fillText("< Back", 1, 191); + } + + handleTouch(x: number, y: number, context: ScreenContext): void { + if (x >= 0 && x <= 51 && y >= 178 && y <= 192) { + context.navigate(new HomeScreen()); + } + } +} diff --git a/src/screens/home-screen.ts b/src/screens/home-screen.ts new file mode 100644 index 0000000..c150de6 --- /dev/null +++ b/src/screens/home-screen.ts @@ -0,0 +1,25 @@ +import type { Screen, ScreenContext } from "../screen"; +import { ContactScreen } from "./contact-screen"; + +export class HomeScreen implements Screen { + renderTop(ctx: CanvasRenderingContext2D) { + ctx.fillStyle = "#ffffff"; + ctx.font = "10px NDS10"; + ctx.textBaseline = "top"; + ctx.fillText("HOME", 1, 1); + } + + renderBottom(ctx: CanvasRenderingContext2D) { + ctx.fillStyle = "#ffffff"; + ctx.font = "10px NDS10"; + ctx.textAlign = "right"; + ctx.textBaseline = "bottom"; + ctx.fillText("Contact >", 255, 191); + } + + handleTouch(x: number, y: number, context: ScreenContext): void { + if (x >= 205 && x <= 256 && y >= 178 && y <= 192) { + context.navigate(new ContactScreen()); + } + } +}