feat(nds): improve 3d model and texture display
@@ -2,6 +2,7 @@
|
|||||||
import { useLoop, useTresContext } from "@tresjs/core";
|
import { useLoop, useTresContext } from "@tresjs/core";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import gsap from "gsap";
|
import gsap from "gsap";
|
||||||
|
import { LOGICAL_WIDTH, LOGICAL_HEIGHT } from "~/utils/screen";
|
||||||
|
|
||||||
const INTRO_ANIMATION = {
|
const INTRO_ANIMATION = {
|
||||||
MODEL_SPIN_DURATION: 3,
|
MODEL_SPIN_DURATION: 3,
|
||||||
@@ -26,25 +27,25 @@ const props = defineProps<{
|
|||||||
const { assets } = useAssets();
|
const { assets } = useAssets();
|
||||||
const app = useAppStore();
|
const app = useAppStore();
|
||||||
|
|
||||||
const model = assets.nintendoDs.scene.clone(true);
|
const model = assets.nitendoDs.model.clone(true);
|
||||||
|
|
||||||
let topScreenTexture: THREE.CanvasTexture | null = null;
|
let topScreenTexture: THREE.CanvasTexture | null = null;
|
||||||
let bottomScreenTexture: THREE.CanvasTexture | null = null;
|
let bottomScreenTexture: THREE.CanvasTexture | null = null;
|
||||||
|
|
||||||
/// meshes ///
|
/// meshes ///
|
||||||
// screens
|
// screens
|
||||||
const TOP_SCREEN = "Object_9";
|
const TOP_SCREEN = "top_screen";
|
||||||
const BOTTOM_SCREEN = "Object_28";
|
const BOTTOM_SCREEN = "bottom_screen";
|
||||||
const LID = "Object_8";
|
const LID = "lid";
|
||||||
|
|
||||||
// buttons
|
// buttons
|
||||||
const CROSS_BUTTON = "Object_21";
|
const CROSS_BUTTON = "button_pad";
|
||||||
const X_BUTTON = "Object_6";
|
const X_BUTTON = "button_x";
|
||||||
const A_BUTTON = "Object_32";
|
const A_BUTTON = "button_a";
|
||||||
const Y_BUTTON = "Object_4";
|
const Y_BUTTON = "button_y";
|
||||||
const B_BUTTON = "Object_30";
|
const B_BUTTON = "button_b";
|
||||||
const SELECT_BUTTON = "Object_17";
|
const SELECT_BUTTON = "button_select";
|
||||||
const START_BUTTON = "Object_11";
|
const START_BUTTON = "button_start";
|
||||||
|
|
||||||
const meshes = new Map<string, THREE.Mesh>();
|
const meshes = new Map<string, THREE.Mesh>();
|
||||||
|
|
||||||
@@ -163,21 +164,25 @@ watch(
|
|||||||
() => {
|
() => {
|
||||||
if (!props.topScreenCanvas || !props.bottomScreenCanvas) return;
|
if (!props.topScreenCanvas || !props.bottomScreenCanvas) return;
|
||||||
|
|
||||||
topScreenTexture = new THREE.CanvasTexture(props.topScreenCanvas);
|
const webglRenderer = renderer.instance;
|
||||||
topScreenTexture.minFilter = THREE.NearestFilter;
|
if (!(webglRenderer instanceof THREE.WebGLRenderer)) return;
|
||||||
topScreenTexture.magFilter = THREE.NearestFilter;
|
|
||||||
topScreenTexture.colorSpace = THREE.SRGBColorSpace;
|
|
||||||
topScreenTexture.flipY = false;
|
|
||||||
topScreenTexture.repeat.set(1, 1024 / 404);
|
|
||||||
topScreenTexture.offset.set(0, -4 / 1024);
|
|
||||||
|
|
||||||
bottomScreenTexture = new THREE.CanvasTexture(props.bottomScreenCanvas);
|
const createScreenTexture = (
|
||||||
bottomScreenTexture.minFilter = THREE.NearestFilter;
|
canvas: HTMLCanvasElement,
|
||||||
bottomScreenTexture.magFilter = THREE.NearestFilter;
|
): THREE.CanvasTexture => {
|
||||||
bottomScreenTexture.colorSpace = THREE.SRGBColorSpace;
|
const texture = new THREE.CanvasTexture(canvas);
|
||||||
bottomScreenTexture.flipY = false;
|
texture.minFilter = THREE.LinearFilter;
|
||||||
bottomScreenTexture.repeat.set(1, 1024 / 532);
|
texture.magFilter = THREE.LinearFilter;
|
||||||
bottomScreenTexture.offset.set(0, -1024 / 532 + 1);
|
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({
|
requireMesh(TOP_SCREEN).material = new THREE.MeshStandardMaterial({
|
||||||
map: topScreenTexture,
|
map: topScreenTexture,
|
||||||
@@ -314,7 +319,6 @@ const handleClick = (event: MouseEvent) => {
|
|||||||
switch (intersection.object.name) {
|
switch (intersection.object.name) {
|
||||||
case TOP_SCREEN:
|
case TOP_SCREEN:
|
||||||
case BOTTOM_SCREEN: {
|
case BOTTOM_SCREEN: {
|
||||||
console.log(intersection);
|
|
||||||
const canvas =
|
const canvas =
|
||||||
intersection.object.name === TOP_SCREEN
|
intersection.object.name === TOP_SCREEN
|
||||||
? props.topScreenCanvas
|
? props.topScreenCanvas
|
||||||
@@ -322,22 +326,16 @@ const handleClick = (event: MouseEvent) => {
|
|||||||
|
|
||||||
if (!canvas) break;
|
if (!canvas) break;
|
||||||
|
|
||||||
const x = Math.floor(intersection.uv.x * 256);
|
const logicalX = (1 - intersection.uv.x) * LOGICAL_WIDTH;
|
||||||
let y: number;
|
const logicalY = (1 - intersection.uv.y) * LOGICAL_HEIGHT;
|
||||||
|
|
||||||
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 rect = canvas.getBoundingClientRect();
|
const rect = canvas.getBoundingClientRect();
|
||||||
canvas.dispatchEvent(
|
canvas.dispatchEvent(
|
||||||
new MouseEvent("click", {
|
new MouseEvent("click", {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
clientX: (x / 256) * rect.width + rect.left,
|
clientX: (logicalX / LOGICAL_WIDTH) * rect.width + rect.left,
|
||||||
clientY: (y / 192) * rect.height + rect.top,
|
clientY: (logicalY / LOGICAL_HEIGHT) * rect.height + rect.top,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { LOGICAL_WIDTH, LOGICAL_HEIGHT, SCREEN_SCALE } from "~/utils/screen";
|
||||||
|
|
||||||
const canvas = useTemplateRef("canvas");
|
const canvas = useTemplateRef("canvas");
|
||||||
|
|
||||||
const renderCallbacks = new Map<RenderCallback, number>();
|
const renderCallbacks = new Map<RenderCallback, number>();
|
||||||
@@ -31,8 +33,8 @@ const handleCanvasClick = (event: MouseEvent) => {
|
|||||||
if (!canvas.value) return;
|
if (!canvas.value) return;
|
||||||
|
|
||||||
const rect = canvas.value.getBoundingClientRect();
|
const rect = canvas.value.getBoundingClientRect();
|
||||||
const scaleX = SCREEN_WIDTH / rect.width;
|
const scaleX = LOGICAL_WIDTH / rect.width;
|
||||||
const scaleY = SCREEN_HEIGHT / rect.height;
|
const scaleY = LOGICAL_HEIGHT / rect.height;
|
||||||
|
|
||||||
const x = (event.clientX - rect.left) * scaleX;
|
const x = (event.clientX - rect.left) * scaleX;
|
||||||
const y = (event.clientY - rect.top) * scaleY;
|
const y = (event.clientY - rect.top) * scaleY;
|
||||||
@@ -59,6 +61,9 @@ const renderFrame = (timestamp: number) => {
|
|||||||
// render
|
// render
|
||||||
ctx.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
ctx.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.scale(SCREEN_SCALE, SCREEN_SCALE);
|
||||||
|
|
||||||
const sortedCallbacks = Array.from(renderCallbacks.entries())
|
const sortedCallbacks = Array.from(renderCallbacks.entries())
|
||||||
.sort((a, b) => a[1] - b[1])
|
.sort((a, b) => a[1] - b[1])
|
||||||
.map(([callback]) => callback);
|
.map(([callback]) => callback);
|
||||||
@@ -71,6 +76,8 @@ const renderFrame = (timestamp: number) => {
|
|||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
lastRealFrameTime = Date.now() - start;
|
lastRealFrameTime = Date.now() - start;
|
||||||
|
|
||||||
animationFrameId = requestAnimationFrame(renderFrame);
|
animationFrameId = requestAnimationFrame(renderFrame);
|
||||||
@@ -86,6 +93,8 @@ onMounted(() => {
|
|||||||
ctx = canvas.value.getContext("2d");
|
ctx = canvas.value.getContext("2d");
|
||||||
if (!ctx) throw new Error("Missing 2d context");
|
if (!ctx) throw new Error("Missing 2d context");
|
||||||
|
|
||||||
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
canvas.value.addEventListener("click", handleCanvasClick);
|
canvas.value.addEventListener("click", handleCanvasClick);
|
||||||
canvas.value.addEventListener("wheel", handleCanvasWheel, { passive: true });
|
canvas.value.addEventListener("wheel", handleCanvasWheel, { passive: true });
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,7 @@
|
|||||||
export const SCREEN_WIDTH = 256;
|
export const LOGICAL_WIDTH = 256;
|
||||||
export const SCREEN_HEIGHT = 192;
|
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 |