feat(nds): improve 3d model and texture display
@@ -2,6 +2,7 @@
|
||||
import { useLoop, useTresContext } from "@tresjs/core";
|
||||
import * as THREE from "three";
|
||||
import gsap from "gsap";
|
||||
import { LOGICAL_WIDTH, LOGICAL_HEIGHT } from "~/utils/screen";
|
||||
|
||||
const INTRO_ANIMATION = {
|
||||
MODEL_SPIN_DURATION: 3,
|
||||
@@ -26,25 +27,25 @@ const props = defineProps<{
|
||||
const { assets } = useAssets();
|
||||
const app = useAppStore();
|
||||
|
||||
const model = assets.nintendoDs.scene.clone(true);
|
||||
const model = assets.nitendoDs.model.clone(true);
|
||||
|
||||
let topScreenTexture: THREE.CanvasTexture | null = null;
|
||||
let bottomScreenTexture: THREE.CanvasTexture | null = null;
|
||||
|
||||
/// meshes ///
|
||||
// screens
|
||||
const TOP_SCREEN = "Object_9";
|
||||
const BOTTOM_SCREEN = "Object_28";
|
||||
const LID = "Object_8";
|
||||
const TOP_SCREEN = "top_screen";
|
||||
const BOTTOM_SCREEN = "bottom_screen";
|
||||
const LID = "lid";
|
||||
|
||||
// buttons
|
||||
const CROSS_BUTTON = "Object_21";
|
||||
const X_BUTTON = "Object_6";
|
||||
const A_BUTTON = "Object_32";
|
||||
const Y_BUTTON = "Object_4";
|
||||
const B_BUTTON = "Object_30";
|
||||
const SELECT_BUTTON = "Object_17";
|
||||
const START_BUTTON = "Object_11";
|
||||
const CROSS_BUTTON = "button_pad";
|
||||
const X_BUTTON = "button_x";
|
||||
const A_BUTTON = "button_a";
|
||||
const Y_BUTTON = "button_y";
|
||||
const B_BUTTON = "button_b";
|
||||
const SELECT_BUTTON = "button_select";
|
||||
const START_BUTTON = "button_start";
|
||||
|
||||
const meshes = new Map<string, THREE.Mesh>();
|
||||
|
||||
@@ -163,21 +164,25 @@ watch(
|
||||
() => {
|
||||
if (!props.topScreenCanvas || !props.bottomScreenCanvas) return;
|
||||
|
||||
topScreenTexture = new THREE.CanvasTexture(props.topScreenCanvas);
|
||||
topScreenTexture.minFilter = THREE.NearestFilter;
|
||||
topScreenTexture.magFilter = THREE.NearestFilter;
|
||||
topScreenTexture.colorSpace = THREE.SRGBColorSpace;
|
||||
topScreenTexture.flipY = false;
|
||||
topScreenTexture.repeat.set(1, 1024 / 404);
|
||||
topScreenTexture.offset.set(0, -4 / 1024);
|
||||
const webglRenderer = renderer.instance;
|
||||
if (!(webglRenderer instanceof THREE.WebGLRenderer)) return;
|
||||
|
||||
bottomScreenTexture = new THREE.CanvasTexture(props.bottomScreenCanvas);
|
||||
bottomScreenTexture.minFilter = THREE.NearestFilter;
|
||||
bottomScreenTexture.magFilter = THREE.NearestFilter;
|
||||
bottomScreenTexture.colorSpace = THREE.SRGBColorSpace;
|
||||
bottomScreenTexture.flipY = false;
|
||||
bottomScreenTexture.repeat.set(1, 1024 / 532);
|
||||
bottomScreenTexture.offset.set(0, -1024 / 532 + 1);
|
||||
const createScreenTexture = (
|
||||
canvas: HTMLCanvasElement,
|
||||
): THREE.CanvasTexture => {
|
||||
const texture = new THREE.CanvasTexture(canvas);
|
||||
texture.minFilter = THREE.LinearFilter;
|
||||
texture.magFilter = THREE.LinearFilter;
|
||||
texture.colorSpace = THREE.SRGBColorSpace;
|
||||
texture.wrapS = THREE.RepeatWrapping;
|
||||
texture.repeat.x = -1;
|
||||
texture.anisotropy = webglRenderer.capabilities.getMaxAnisotropy();
|
||||
texture.generateMipmaps = false;
|
||||
return texture;
|
||||
};
|
||||
|
||||
topScreenTexture = createScreenTexture(props.topScreenCanvas);
|
||||
bottomScreenTexture = createScreenTexture(props.bottomScreenCanvas);
|
||||
|
||||
requireMesh(TOP_SCREEN).material = new THREE.MeshStandardMaterial({
|
||||
map: topScreenTexture,
|
||||
@@ -314,7 +319,6 @@ const handleClick = (event: MouseEvent) => {
|
||||
switch (intersection.object.name) {
|
||||
case TOP_SCREEN:
|
||||
case BOTTOM_SCREEN: {
|
||||
console.log(intersection);
|
||||
const canvas =
|
||||
intersection.object.name === TOP_SCREEN
|
||||
? props.topScreenCanvas
|
||||
@@ -322,22 +326,16 @@ const handleClick = (event: MouseEvent) => {
|
||||
|
||||
if (!canvas) break;
|
||||
|
||||
const x = Math.floor(intersection.uv.x * 256);
|
||||
let y: number;
|
||||
|
||||
if (intersection.object.name === TOP_SCREEN) {
|
||||
y = Math.floor(intersection.uv.y * (1024 / 404) * 192);
|
||||
} else {
|
||||
y = Math.floor(192 - (1 - intersection.uv.y) * (1024 / 532) * 192);
|
||||
}
|
||||
const logicalX = (1 - intersection.uv.x) * LOGICAL_WIDTH;
|
||||
const logicalY = (1 - intersection.uv.y) * LOGICAL_HEIGHT;
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
canvas.dispatchEvent(
|
||||
new MouseEvent("click", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
clientX: (x / 256) * rect.width + rect.left,
|
||||
clientY: (y / 192) * rect.height + rect.top,
|
||||
clientX: (logicalX / LOGICAL_WIDTH) * rect.width + rect.left,
|
||||
clientY: (logicalY / LOGICAL_HEIGHT) * rect.height + rect.top,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { LOGICAL_WIDTH, LOGICAL_HEIGHT, SCREEN_SCALE } from "~/utils/screen";
|
||||
|
||||
const canvas = useTemplateRef("canvas");
|
||||
|
||||
const renderCallbacks = new Map<RenderCallback, number>();
|
||||
@@ -31,8 +33,8 @@ const handleCanvasClick = (event: MouseEvent) => {
|
||||
if (!canvas.value) return;
|
||||
|
||||
const rect = canvas.value.getBoundingClientRect();
|
||||
const scaleX = SCREEN_WIDTH / rect.width;
|
||||
const scaleY = SCREEN_HEIGHT / rect.height;
|
||||
const scaleX = LOGICAL_WIDTH / rect.width;
|
||||
const scaleY = LOGICAL_HEIGHT / rect.height;
|
||||
|
||||
const x = (event.clientX - rect.left) * scaleX;
|
||||
const y = (event.clientY - rect.top) * scaleY;
|
||||
@@ -59,6 +61,9 @@ const renderFrame = (timestamp: number) => {
|
||||
// render
|
||||
ctx.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
|
||||
ctx.save();
|
||||
ctx.scale(SCREEN_SCALE, SCREEN_SCALE);
|
||||
|
||||
const sortedCallbacks = Array.from(renderCallbacks.entries())
|
||||
.sort((a, b) => a[1] - b[1])
|
||||
.map(([callback]) => callback);
|
||||
@@ -71,6 +76,8 @@ const renderFrame = (timestamp: number) => {
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
||||
lastRealFrameTime = Date.now() - start;
|
||||
|
||||
animationFrameId = requestAnimationFrame(renderFrame);
|
||||
@@ -86,6 +93,8 @@ onMounted(() => {
|
||||
ctx = canvas.value.getContext("2d");
|
||||
if (!ctx) throw new Error("Missing 2d context");
|
||||
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
|
||||
canvas.value.addEventListener("click", handleCanvasClick);
|
||||
canvas.value.addEventListener("wheel", handleCanvasWheel, { passive: true });
|
||||
|
||||
|
||||
@@ -1,2 +1,7 @@
|
||||
export const SCREEN_WIDTH = 256;
|
||||
export const SCREEN_HEIGHT = 192;
|
||||
export const LOGICAL_WIDTH = 256;
|
||||
export const LOGICAL_HEIGHT = 192;
|
||||
|
||||
export const SCREEN_SCALE = 2;
|
||||
|
||||
export const SCREEN_WIDTH = LOGICAL_WIDTH * SCREEN_SCALE;
|
||||
export const SCREEN_HEIGHT = LOGICAL_HEIGHT * SCREEN_SCALE;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
Model Information:
|
||||
* title: Nintendo DS
|
||||
* source: https://sketchfab.com/3d-models/nintendo-ds-934fdde060874d5c99b40cdf1dbb37f2
|
||||
* author: solal.sblt (https://sketchfab.com/Solal.Sebillotte)
|
||||
|
||||
Model License:
|
||||
* license type: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
|
||||
* requirements: Author must be credited. Commercial use is allowed.
|
||||
|
||||
If you use this 3D model in your project be sure to copy paste this credit wherever you share it:
|
||||
This work is based on "Nintendo DS" (https://sketchfab.com/3d-models/nintendo-ds-934fdde060874d5c99b40cdf1dbb37f2) by solal.sblt (https://sketchfab.com/Solal.Sebillotte) licensed under CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
|
||||
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
BIN
public/nds/models/nitendo-ds/model.blend
Normal file
BIN
public/nds/models/nitendo-ds/model.blend1
Normal file
1975
public/nds/models/nitendo-ds/model.gltf
Normal file
BIN
public/nds/models/nitendo-ds/screen_down.002_baseColor.png
Normal file
|
After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 534 KiB After Width: | Height: | Size: 534 KiB |
BIN
public/nds/models/nitendo-ds/screen_up.002_baseColor.png
Normal file
|
After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 222 B After Width: | Height: | Size: 222 B |
|
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 1000 KiB After Width: | Height: | Size: 1000 KiB |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |