현재 프로젝트 구조
public 폴더에 텍스쳐나 glb 같은 에셋 파일들을 넣어놨다. Three.js 관련된 파일들은 따로 폴더에 모아놓을 생각이다.
Canvas에 있던 컴포넌트들은 따로 빼서 ObjectContainer라는 파일에 모아놨다.
카메라 컨트롤을 더 편하게 하기 위해 OrbitControl을 빼고 FlyControl과 PointerLockControls를 추가했다. axesHelper는 화면에 x,y, z 축을 표시해준다. FloorPlane이라는 컴포넌트로 바닥을 출력한다.
실행하면 이렇게 나온다.
저번에 glb 파일을 로딩하는 코드를 gltf로 생성했었다. gltf로 생성된 코드는 Cube_1이나 Cube_2같은 파일 고유의 속성이름들을 사용한다.
다른 glb파일들도 사용하려면 이걸 일반적인 표현으로 바꿔야 한다. 메쉬 오브젝트에 ref를 걸어서 console.log로 출력해보면 이 메쉬가 가진 속성들을 볼 수 있다.
여기서 타입 이름이 Mesh인 속성들만 컴포넌트로 출력하도록 해야 된다. 타입 이름이 Mesh인 것만 filter함수를 통해 걸러내고 이걸 map 함수로 출력한다.
이제 가구를 여러개 출력할 수 있도록 코드를 바꿔야 한다. 현재 가지고 있는 에셋 파일들을 전부 불러올 수 있도록 디버그용 파일을 만들었다.
가구 전체에 대한 데이터를 가지고 각각의 가구 컴포넌트로 데이터를 넘겨주는 FurnitureContainer라는 파일을 만들었다.
모든 가구 정보를 가지고 있는 allFurnitureInfo 변수에 useEffect로 디버그용 파일을 넣었다. 나중에 axios로 가구 정보를 서버에서 받아오도록 바꿀 것이다.
가구 하나의 컴포넌트는 가구의 정보를 props에서 가져오도록 수정했다.
furnitureInfo 타입은 이렇게 되어있다.
실행하면 이렇게 나온다.
프로젝트 구조

CanvasCaontainer.tsx
import { Canvas } from "@react-three/fiber";
import { useEffect } from "react";
import ObjectContainer from "./ObjectContainer";
const CanvasContainer = () => {
useEffect(() => {}, []);
return (
<div className="m-8 border-2 border-black w-[48rem] h-[32rem] flex">
<Canvas camera={{ fov: 75, near: 0.1, far: 1000, position: [0, 0, 5] }}>
<ObjectContainer />
</Canvas>
</div>
);
};
export default CanvasContainer;
ObjectContainer.tsx
import { useEffect } from "react";
import { FlyControls, PointerLockControls } from "@react-three/drei";
import FloorPlane from "./Floor";
import { useThree } from "@react-three/fiber";
import useKey from "../hooks/useKey";
import FurnitureContainer from "./FurnitureContainer";
const ObjectContainer = () => {
const three = useThree();
useKey(three.camera);
useEffect(() => {}, []);
return (
<group>
<FlyControls dragToLook={true} movementSpeed={10} />
<PointerLockControls />
<ambientLight intensity={1} />
<directionalLight position={[-1, 0, 1]} />
<axesHelper args={[10]} />
<FloorPlane />
<FurnitureContainer />
</group>
);
};
export default ObjectContainer;
FurnitureContainer.tsx
import React, { useEffect, useState } from "react";
import debugFurniture from "./DebugFurniture";
import * as THREE from "three";
import Furniture from "./Furniture";
import { furnitureInfo } from "./FurnitureInfo";
const FurnitureContainer = () => {
const [allFurnitureInfo, setAllFurnitureInfo] = useState<furnitureInfo[]>();
useEffect(() => {
setAllFurnitureInfo(debugFurniture);
});
return (
<group>
{allFurnitureInfo?.map((furnitureInfo) => (
<Furniture furnitureInfo={furnitureInfo}></Furniture>
))}
</group>
);
};
export default FurnitureContainer;
Furniture.tsx
/*
Auto-generated by: https://github.com/pmndrs/gltfjsx
Command: npx gltfjsx@6.2.16 BP_Martin_C.glb
*/
import React, { useEffect, useRef } from "react";
import { useGLTF } from "@react-three/drei";
import { furnitureInfo } from "./FurnitureInfo";
import { Vector3 } from "three";
type furnitureProps = { furnitureInfo: furnitureInfo };
export default function Furniture({ furnitureInfo }: furnitureProps) {
const obj = useRef<any>();
// add event listener to highlight dragged objects
const position = new Vector3(
furnitureInfo.position[0],
furnitureInfo.position[2],
furnitureInfo.position[1]
);
const { nodes, materials } = useGLTF(`./furnitures/${furnitureInfo.file}`);
const meshs = Object.values(nodes).filter((mesh) => mesh.type === "Mesh");
useEffect(() => {
console.log(nodes);
}, []);
return (
<group dispose={null} ref={obj} position={position}>
{meshs.map((mesh: any) => (
<mesh
key={mesh.uuid}
geometry={mesh.geometry}
material={mesh.material}
/>
))}
</group>
);
}
Floor.tsx
import React from "react";
import * as THREE from "three";
import { useLoader } from "@react-three/fiber";
import { DoubleSide } from "three";
const FloorPlane = () => {
const texture = useLoader(
THREE.TextureLoader,
"/floor/pine-wood-texture.jpg"
);
texture.wrapS = THREE.MirroredRepeatWrapping;
texture.wrapT = THREE.MirroredRepeatWrapping;
texture.repeat.set(4, 4);
return (
<mesh rotation-x={Math.PI * -0.5}>
<planeGeometry attach="geometry" args={[20, 20]} />
<meshBasicMaterial
attach="material"
map={texture}
side={DoubleSide}
color={"white"}
/>
</mesh>
);
};
export default FloorPlane;
furnitureInfo.ts
import * as THREE from "three";
export type furnitureInfo = {
file: string;
position: number[];
rotation: THREE.Euler;
};
debugFurniture.js
import * as THREE from "three";
const debugFurniture = [
{
file: "BP_이케아오드게르의자_C.glb",
position: [-1, 1, 0],
rotation: new THREE.Euler(Math.PI / 2, 0, 0),
},
{
file: "BP_Martin_C.glb",
position: [0, 1, 0],
rotation: new THREE.Euler(Math.PI / 2, 0, 0),
},
{
file: "BP_Kolonn_C.glb",
position: [-2, 1, 0],
rotation: new THREE.Euler(Math.PI / 2, 0, 0),
},
{
file: "BP_Leiter_600_4단_블루_C.glb",
position: [2, 1, 0],
rotation: new THREE.Euler(Math.PI / 2, 0, 0),
},
{
file: "BP_ikea_docsta_C.glb",
position: [1, 1, 0],
rotation: new THREE.Euler(Math.PI / 2, 0, 0),
},
{
file: "BP_애드온_베이직_책상_C.glb",
position: [0, 2, 0],
rotation: new THREE.Euler(Math.PI / 2, 0, 0),
},
{
file: "BP_dalshult_C.glb",
position: [-2, 2, 0],
rotation: new THREE.Euler(Math.PI / 2, 0, 0),
},
{
file: "BP_레브_책상_C.glb",
position: [2, 2, 0],
rotation: new THREE.Euler(Math.PI / 2, 0, 0),
},
{
file: "BP_토피옷장_C.glb",
position: [1, 3, 0],
rotation: new THREE.Euler(Math.PI / 2, 0, 0),
},
{
file: "BP_플랫_C.glb",
position: [-2, 3, 0],
rotation: new THREE.Euler(Math.PI / 2, 0, 0),
},
{
file: "BP_베이직_5단_C.glb",
position: [2, 3, 0],
rotation: new THREE.Euler(Math.PI / 2, 0, 0),
},
{
file: "BP_한샘_샘베딩_옷장_C.glb",
position: [-1, 3, 0],
rotation: new THREE.Euler(Math.PI / 2, 0, 0),
},
{
file: "BP_ACE침대_BRA1339_C.glb",
position: [-1, 5, 0],
rotation: new THREE.Euler(Math.PI / 2, 0, 0),
},
{
file: "BP_에이스_LUNATO_C.glb",
position: [1, 5, 0],
rotation: new THREE.Euler(Math.PI / 2, 0, 0),
},
];
export default debugFurniture;
'프로젝트' 카테고리의 다른 글
[가구 프로젝트] 웹페이지 만들기 (0) | 2024.04.26 |
---|---|
[가구 프로젝트] three.js 오브젝트 이동&회전 기능 만들기 (0) | 2024.03.27 |
[가구 프로젝트] three.js 라이브러리 코드 수정하기 (0) | 2024.03.26 |
[가구 프로젝트] canvas에 크로스헤어 추가하기 (0) | 2024.03.19 |
[리액트 가구 프로젝트] 리액트로 3D모델 사용하기 (0) | 2024.03.13 |