feat: add target model

This commit is contained in:
xfy
2025-05-21 15:19:26 +08:00
parent 3ac7d42cc1
commit fd5501e1d0
7 changed files with 468 additions and 61 deletions

View File

@ -18,23 +18,24 @@ export default function Page() {
<ComputerDesk />
</div>
<div className="text-6xl flex items-center">
<span
<div className="text-center pt-20">
<h1
className={clsx(
'font-semibold font-Lobster tracking-wide',
'pb-12 text-5xl sm:text-6xl',
styles.gradient,
)}
>
Hello World!
</span>
<span className="ml-3">
<Image
src="/images/img/hands.svg"
alt="hands"
width={36}
height={36}
/>
</span>
Hello World
</h1>
<h2
className={clsx(
'font-semibold font-Lobster tracking-wide',
'text-4xl sm:text-5xl',
)}
>
TECH OTAKUS SAVE THE WORLD
</h2>
</div>
</main>
);

View File

@ -1,60 +1,24 @@
'use client';
import { Canvas } from '@react-three/fiber';
import { lazy, Suspense } from 'react';
import { lazy, Suspense, useMemo } from 'react';
import Loading from './loading';
import { PerspectiveCamera } from '@react-three/drei/core/PerspectiveCamera';
import { Leva, useControls } from 'leva';
import { useMediaQuery } from 'react-responsive';
const ComputerModel = lazy(
() => import('components/models/home/computer-model'),
);
const Target = lazy(() => import('components/models/home/target'));
const ComputerDesk = () => {
const c = useControls('RUARoom', {
positionX: {
value: 0,
min: -10,
max: 10,
step: 0.1,
},
positionY: {
value: 0,
min: -10,
max: 10,
step: 0.1,
},
positionZ: {
value: 0,
min: -10,
max: 10,
step: 0.01,
},
rotationX: {
value: 0,
min: -Math.PI,
max: Math.PI,
step: 0.01,
},
rotationY: {
value: 0,
min: -Math.PI,
max: Math.PI,
step: 0.01,
},
rotationZ: {
value: 0,
min: -Math.PI,
max: Math.PI,
step: 0.001,
},
scale: {
value: 1,
min: 0.1,
max: 10,
step: 0.01,
},
});
const isMobile = useMediaQuery({ query: '(max-width: 768px)' });
const scale = useMemo(() => {
if (isMobile) {
return 0.08;
}
return 0.1;
}, [isMobile]);
return (
<>
@ -63,14 +27,15 @@ const ComputerDesk = () => {
<ambientLight intensity={1} />
<directionalLight position={[10, 10, 10]} intensity={0.5} />
<ComputerModel
scale={0.1}
position={[0.6, -6.0, 0]}
scale={scale}
position={[0.6, -7.2, 0]}
rotation={[0, -Math.PI, 0]}
/>
<Target />
<PerspectiveCamera makeDefault position={[0, 0, 30]} />
</Suspense>
</Canvas>
<Leva />
{/* <Leva /> */}
</>
);
};

View File

@ -0,0 +1,89 @@
/**
* Samll target item in home page
*/
import * as THREE from 'three';
import React, { JSX, useMemo, useRef } from 'react';
import { useGLTF } from '@react-three/drei';
import { DRACOLoader, GLTF, GLTFLoader } from 'three-stdlib';
import { useLoader } from '@react-three/fiber';
import gsap from 'gsap';
import { useGSAP } from '@gsap/react';
import { useMediaQuery } from 'react-responsive';
type GLTFResult = GLTF & {
nodes: {
Cylinder016: THREE.Mesh;
Cylinder016_1: THREE.Mesh;
Cylinder016_2: THREE.Mesh;
};
materials: {
['Red.025']: THREE.MeshStandardMaterial;
['White.025']: THREE.MeshStandardMaterial;
['BrownDark.018']: THREE.MeshStandardMaterial;
};
};
export function Target(props: JSX.IntrinsicElements['group']) {
const { nodes, materials } = useLoader(
GLTFLoader,
'/models/target-processed.gltf',
(loader) => {
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/libs/draco/');
loader.setDRACOLoader(dracoLoader);
},
) as unknown as GLTFResult;
// animation
const targetRef = useRef<THREE.Group>(null);
useGSAP(() => {
if (!targetRef.current) return;
gsap.to(targetRef.current.position, {
y: targetRef.current.position.y + 0.5,
duration: 1.5,
repeat: -1,
yoyo: true,
});
});
const isMobile = useMediaQuery({ query: '(max-width: 768px)' });
const position = useMemo<[number, number, number]>(() => {
if (isMobile) {
return [-5, -5, 3] as const;
}
return [-14, -8, 0] as const;
}, [isMobile]);
return (
<group {...props} dispose={null}>
<group
rotation={[Math.PI / 2, 0, -Math.PI / 8]}
position={position}
ref={targetRef}
>
<mesh
castShadow
receiveShadow
geometry={nodes.Cylinder016.geometry}
material={materials['Red.025']}
/>
<mesh
castShadow
receiveShadow
geometry={nodes.Cylinder016_1.geometry}
material={materials['White.025']}
/>
<mesh
castShadow
receiveShadow
geometry={nodes.Cylinder016_2.geometry}
material={materials['BrownDark.018']}
/>
</group>
</group>
);
}
useGLTF.preload('/models/target-processed.gltf');
export default Target;

View File

@ -22,6 +22,7 @@
"@docsearch/css": "^3.9.0",
"@docsearch/react": "^3.9.0",
"@giscus/react": "^3.1.0",
"@gsap/react": "^2.1.2",
"@octokit/core": "^6.1.5",
"@octokit/plugin-rest-endpoint-methods": "^14.0.0",
"@react-spring/three": "^10.0.0",
@ -30,12 +31,14 @@
"@tailwindcss/postcss": "^4.1.7",
"algoliasearch": "^5.25.0",
"dayjs": "^1.11.13",
"gsap": "^3.13.0",
"next": "15.3.2",
"next-mdx-remote": "^5.0.0",
"next-themes": "^0.4.6",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-icons": "^5.5.0",
"react-responsive": "^10.0.1",
"rehype-highlight": "^7.0.0",
"rehype-react": "^8.0.0",
"rehype-slug": "^6.0.0",

61
pnpm-lock.yaml generated
View File

@ -29,6 +29,9 @@ importers:
'@giscus/react':
specifier: ^3.1.0
version: 3.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@gsap/react':
specifier: ^2.1.2
version: 2.1.2(gsap@3.13.0)(react@19.1.0)
'@octokit/core':
specifier: ^6.1.5
version: 6.1.5
@ -53,6 +56,9 @@ importers:
dayjs:
specifier: ^1.11.13
version: 1.11.13
gsap:
specifier: ^3.13.0
version: 3.13.0
next:
specifier: 15.3.2
version: 15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.89.0)
@ -71,6 +77,9 @@ importers:
react-icons:
specifier: ^5.5.0
version: 5.5.0(react@19.1.0)
react-responsive:
specifier: ^10.0.1
version: 10.0.1(react@19.1.0)
rehype-highlight:
specifier: ^7.0.0
version: 7.0.1
@ -449,6 +458,12 @@ packages:
react: ^16 || ^17 || ^18 || ^19
react-dom: ^16 || ^17 || ^18 || ^19
'@gsap/react@2.1.2':
resolution: {integrity: sha512-JqliybO1837UcgH2hVOM4VO+38APk3ECNrsuSM4MuXp+rbf+/2IG2K1YJiqfTcXQHH7XlA0m3ykniFYstfq0Iw==}
peerDependencies:
gsap: ^3.12.5
react: '>=17'
'@humanwhocodes/config-array@0.13.0':
resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==}
engines: {node: '>=10.10.0'}
@ -1608,6 +1623,9 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
css-mediaquery@0.1.2:
resolution: {integrity: sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==}
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
@ -2133,6 +2151,9 @@ packages:
resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
engines: {node: '>=6.0'}
gsap@3.13.0:
resolution: {integrity: sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==}
gzip-size@6.0.0:
resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==}
engines: {node: '>=10'}
@ -2198,6 +2219,9 @@ packages:
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
hyphenate-style-name@1.1.0:
resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==}
iconv-lite@0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
@ -2608,6 +2632,9 @@ packages:
markdown-table@3.0.4:
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
matchmediaquery@0.4.2:
resolution: {integrity: sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==}
mdast-util-find-and-replace@3.0.1:
resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==}
@ -3072,6 +3099,12 @@ packages:
peerDependencies:
react: ^19.0.0
react-responsive@10.0.1:
resolution: {integrity: sha512-OM5/cRvbtUWEX8le8RCT8scA8y2OPtb0Q/IViEyCEM5FBN8lRrkUOZnu87I88A6njxDldvxG+rLBxWiA7/UM9g==}
engines: {node: '>=14'}
peerDependencies:
react: '>=16.8.0'
react-use-measure@2.1.7:
resolution: {integrity: sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==}
peerDependencies:
@ -3238,6 +3271,9 @@ packages:
resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==}
engines: {node: '>=0.10.0'}
shallow-equal@3.1.0:
resolution: {integrity: sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==}
sharp@0.34.1:
resolution: {integrity: sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@ -4116,6 +4152,11 @@ snapshots:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
'@gsap/react@2.1.2(gsap@3.13.0)(react@19.1.0)':
dependencies:
gsap: 3.13.0
react: 19.1.0
'@humanwhocodes/config-array@0.13.0':
dependencies:
'@humanwhocodes/object-schema': 2.0.3
@ -5323,6 +5364,8 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
css-mediaquery@0.1.2: {}
csstype@3.1.3: {}
d@1.0.2:
@ -6021,6 +6064,8 @@ snapshots:
section-matter: 1.0.0
strip-bom-string: 1.0.0
gsap@3.13.0: {}
gzip-size@6.0.0:
dependencies:
duplexer: 0.1.2
@ -6117,6 +6162,8 @@ snapshots:
html-escaper@2.0.2: {}
hyphenate-style-name@1.1.0: {}
iconv-lite@0.6.3:
dependencies:
safer-buffer: 2.1.2
@ -6492,6 +6539,10 @@ snapshots:
markdown-table@3.0.4: {}
matchmediaquery@0.4.2:
dependencies:
css-mediaquery: 0.1.2
mdast-util-find-and-replace@3.0.1:
dependencies:
'@types/mdast': 4.0.4
@ -7237,6 +7288,14 @@ snapshots:
react: 19.1.0
scheduler: 0.25.0
react-responsive@10.0.1(react@19.1.0):
dependencies:
hyphenate-style-name: 1.1.0
matchmediaquery: 0.4.2
prop-types: 15.8.1
react: 19.1.0
shallow-equal: 3.1.0
react-use-measure@2.1.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0
@ -7481,6 +7540,8 @@ snapshots:
is-plain-object: 2.0.4
split-string: 3.1.0
shallow-equal@3.1.0: {}
sharp@0.34.1:
dependencies:
color: 4.2.3

File diff suppressed because one or more lines are too long