feat: 3d nds model
85
app/components/NDS.vue
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
||||||
|
import { useLoop } from "@tresjs/core";
|
||||||
|
import * as THREE from "three";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
topScreenCanvas: HTMLCanvasElement | null;
|
||||||
|
bottomScreenCanvas: HTMLCanvasElement | null;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
} 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const { onRender } = useLoop();
|
||||||
|
|
||||||
|
onRender(() => {
|
||||||
|
if (topScreenTexture) topScreenTexture.needsUpdate = true;
|
||||||
|
if (bottomScreenTexture) bottomScreenTexture.needsUpdate = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
topScreenTexture?.dispose();
|
||||||
|
bottomScreenTexture?.dispose();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<primitive v-if="scene" :object="scene" />
|
||||||
|
</template>
|
||||||
@@ -110,6 +110,10 @@ onUnmounted(() => {
|
|||||||
canvas.value.removeEventListener("wheel", handleCanvasWheel);
|
canvas.value.removeEventListener("wheel", handleCanvasWheel);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
canvas,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
const showStats = useState("showStats", () => false);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
:style="{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: '4px',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div :style="{ display: 'flex', alignItems: 'center', gap: '4px' }">
|
|
||||||
<input id="statsCheckbox" v-model="showStats" type="checkbox" />
|
|
||||||
<label for="statsCheckbox">Stats</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Screen>
|
|
||||||
<slot name="top">
|
|
||||||
<slot />
|
|
||||||
</slot>
|
|
||||||
<Stats v-if="showStats" />
|
|
||||||
</Screen>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Screen>
|
|
||||||
<slot name="bottom">
|
|
||||||
<slot />
|
|
||||||
</slot>
|
|
||||||
<Stats v-if="showStats" />
|
|
||||||
</Screen>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,26 +1,40 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
definePageMeta({
|
import { OrbitControls } from "@tresjs/cientos";
|
||||||
layout: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const screen = computed(() => route.query.screen as string | undefined);
|
const screen = computed(() => route.query.screen as string | undefined);
|
||||||
|
|
||||||
|
const topScreen = useTemplateRef("topScreen");
|
||||||
|
const bottomScreen = useTemplateRef("bottomScreen");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NuxtLayout name="default">
|
<div>
|
||||||
<template #top>
|
<TresCanvas window-size clear-color="#181818">
|
||||||
|
<TresPerspectiveCamera :args="[45, 1, 0.001, 1000]" />
|
||||||
|
<OrbitControls />
|
||||||
|
|
||||||
|
<TresAmbientLight />
|
||||||
|
<TresDirectionalLight />
|
||||||
|
|
||||||
|
<NDS
|
||||||
|
v-if="topScreen && bottomScreen"
|
||||||
|
:top-screen-canvas="topScreen.canvas"
|
||||||
|
:bottom-screen-canvas="bottomScreen.canvas"
|
||||||
|
/>
|
||||||
|
</TresCanvas>
|
||||||
|
|
||||||
|
<Screen ref="topScreen">
|
||||||
<HomeTopScreen v-if="!screen" />
|
<HomeTopScreen v-if="!screen" />
|
||||||
<ContactTopScreen v-else-if="screen === 'contact'" />
|
<ContactTopScreen v-else-if="screen === 'contact'" />
|
||||||
<ProjectsTopScreen v-else-if="screen === 'projects'" />
|
<ProjectsTopScreen v-else-if="screen === 'projects'" />
|
||||||
<SettingsTopScreen v-else-if="screen === 'settings'" />
|
<SettingsTopScreen v-else-if="screen === 'settings'" />
|
||||||
</template>
|
</Screen>
|
||||||
|
<Screen ref="bottomScreen">
|
||||||
<template #bottom>
|
|
||||||
<HomeBottomScreen v-if="!screen" />
|
<HomeBottomScreen v-if="!screen" />
|
||||||
<ContactBottomScreen v-else-if="screen === 'contact'" />
|
<ContactBottomScreen v-else-if="screen === 'contact'" />
|
||||||
<ProjectsBottomScreen v-else-if="screen === 'projects'" />
|
<ProjectsBottomScreen v-else-if="screen === 'projects'" />
|
||||||
<SettingsBottomScreen v-else-if="screen === 'settings'" />
|
<SettingsBottomScreen v-else-if="screen === 'settings'" />
|
||||||
</template>
|
</Screen>
|
||||||
</NuxtLayout>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export default defineNuxtConfig({
|
|||||||
"@pinia/nuxt",
|
"@pinia/nuxt",
|
||||||
"./modules/content-assets",
|
"./modules/content-assets",
|
||||||
"@nuxtjs/i18n",
|
"@nuxtjs/i18n",
|
||||||
|
"@tresjs/nuxt",
|
||||||
],
|
],
|
||||||
css: ["~/assets/app.css"],
|
css: ["~/assets/app.css"],
|
||||||
ssr: false,
|
ssr: false,
|
||||||
|
|||||||
@@ -12,8 +12,12 @@
|
|||||||
"lint": "eslint --fix --cache ."
|
"lint": "eslint --fix --cache ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tresjs/cientos": "^5.1.2",
|
||||||
|
"@tresjs/core": "^5.2.0",
|
||||||
|
"@tresjs/nuxt": "^5.1.2",
|
||||||
"gsap": "3.13.0",
|
"gsap": "3.13.0",
|
||||||
"pinia": "3.0.4",
|
"pinia": "3.0.4",
|
||||||
|
"three": "^0.182.0",
|
||||||
"vue": "3.5.25",
|
"vue": "3.5.25",
|
||||||
"vue-router": "4.6.3"
|
"vue-router": "4.6.3"
|
||||||
},
|
},
|
||||||
@@ -25,6 +29,7 @@
|
|||||||
"@nuxtjs/mdc": "0.18.4",
|
"@nuxtjs/mdc": "0.18.4",
|
||||||
"@pinia/nuxt": "0.11.3",
|
"@pinia/nuxt": "0.11.3",
|
||||||
"@types/node": "24.10.1",
|
"@types/node": "24.10.1",
|
||||||
|
"@types/three": "^0.182.0",
|
||||||
"better-sqlite3": "12.4.1",
|
"better-sqlite3": "12.4.1",
|
||||||
"eslint": "9.39.1",
|
"eslint": "9.39.1",
|
||||||
"nuxt": "4.2.1",
|
"nuxt": "4.2.1",
|
||||||
|
|||||||
12185
pnpm-lock.yaml
generated
@@ -3,3 +3,4 @@ onlyBuiltDependencies:
|
|||||||
- better-sqlite3
|
- better-sqlite3
|
||||||
- esbuild
|
- esbuild
|
||||||
- unrs-resolver
|
- unrs-resolver
|
||||||
|
- vue-demi
|
||||||
|
|||||||
11
public/models/nintendo-ds/license.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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/)
|
||||||
BIN
public/models/nintendo-ds/scene.bin
Normal file
1706
public/models/nintendo-ds/scene.gltf
Normal file
BIN
public/models/nintendo-ds/textures/base.002_baseColor.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
BIN
public/models/nintendo-ds/textures/base.002_normal.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 8.6 KiB |
BIN
public/models/nintendo-ds/textures/screen_down.002_baseColor.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 79 KiB |
BIN
public/models/nintendo-ds/textures/screen_down.002_normal.png
Normal file
|
After Width: | Height: | Size: 534 KiB |
BIN
public/models/nintendo-ds/textures/screen_up.002_baseColor.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 222 B |
BIN
public/models/nintendo-ds/textures/screen_up.002_normal.png
Normal file
|
After Width: | Height: | Size: 226 KiB |
BIN
public/models/nintendo-ds/textures/top.002_baseColor.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
public/models/nintendo-ds/textures/top.002_metallicRoughness.png
Normal file
|
After Width: | Height: | Size: 1000 KiB |
BIN
public/models/nintendo-ds/textures/top.002_normal.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |