diff --git a/src/components/Obj3d.tsx b/src/components/Obj3d.tsx new file mode 100644 index 0000000..1d30b3d --- /dev/null +++ b/src/components/Obj3d.tsx @@ -0,0 +1,142 @@ +import React, { useEffect, useRef, useState } from "react"; +import * as THREE from "three"; + +export const Obj3d = () => { + const mountRef = useRef(null); + const [mouseX, setMouseX] = useState(0); + const [mouseY, setMouseY] = useState(0); + const [isDragging, setIsDragging] = useState(false); + const [previousMouseX, setPreviousMouseX] = useState(0); + const [previousMouseY, setPreviousMouseY] = useState(0); + const [initialRotation, setInitialRotation] = useState( + null + ); + const [initialZoom, setInitialZoom] = useState(5); // 초기 줌 값을 상태로 관리 + + useEffect(() => { + const mount = mountRef.current; + + // Scene, Camera, Renderer 생성 + const scene = new THREE.Scene(); + scene.background = null; // 배경을 투명하게 설정 + + const camera = new THREE.PerspectiveCamera( + 70, + mount!.clientWidth / mount!.clientHeight, + 0.1, + 1000 + ); + const renderer = new THREE.WebGLRenderer({ alpha: true }); // alpha 속성을 true로 설정 + renderer.setSize(mount!.clientWidth, mount!.clientHeight); + mount!.appendChild(renderer.domElement); + + // 조명 + const directionLight = new THREE.DirectionalLight(0xffffff, 1.5); + directionLight.position.set(-0.7, 1, 5); + scene.add(directionLight); + + // 큐브 생성 (색상 변경) + const geometry = new THREE.IcosahedronGeometry(1.8, 0); + const material = new THREE.MeshPhysicalMaterial({ + color: 0xff8bcb, + metalness: 1, + roughness: 0.5, + transparent: true, + opacity: 0.9, + reflectivity: 0.9, + }); + const cube = new THREE.Mesh(geometry, material); + scene.add(cube); + + camera.position.z = initialZoom; // 초기 줌 값 설정 + + // 마우스 이벤트 리스너 + const onMouseDown = (event: MouseEvent) => { + setIsDragging(true); + setPreviousMouseX(event.clientX); + setPreviousMouseY(event.clientY); + setInitialRotation(cube.rotation.clone()); // 클릭 시점의 회전 상태 저장 + setInitialZoom(camera.position.z); // 클릭 시점의 줌 값 저장 + }; + + const onMouseMove = (event: MouseEvent) => { + if (isDragging) { + const deltaX = event.clientX - previousMouseX; + const deltaY = event.clientY - previousMouseY; + + if (initialRotation) { + cube.rotation.y = initialRotation.y + deltaX * 0.005; + cube.rotation.x = initialRotation.x + deltaY * 0.005; + } + + setPreviousMouseX(event.clientX); + setPreviousMouseY(event.clientY); + } + }; + + const onMouseUp = () => { + setIsDragging(false); + }; + + const onWheel = (event: WheelEvent) => { + const delta = event.deltaY * 0.01; + + camera.position.z -= delta; + + // Limiting camera zoom + if (camera.position.z < 4) { + camera.position.z = 4; + } else if (camera.position.z > 15) { + camera.position.z = 15; + } + }; + + window.addEventListener("mousedown", onMouseDown); + window.addEventListener("mousemove", onMouseMove); + window.addEventListener("mouseup", onMouseUp); + window.addEventListener("wheel", onWheel); + + if (previousMouseX > 0 || previousMouseY > 0) { + cube.rotation.x -= previousMouseX; + cube.rotation.y -= previousMouseY; + } + + // 반응형 처리 + const onWindowResize = () => { + if (mount) { + camera.aspect = mount.clientWidth / mount.clientHeight; + camera.updateProjectionMatrix(); + renderer.setSize(mount.clientWidth, mount.clientHeight); + } + }; + window.addEventListener("resize", onWindowResize); + + // 애니메이션 루프 + const animate = () => { + requestAnimationFrame(animate); + + // 기본 회전 + if (!isDragging) { + cube.rotation.x += 0.005; + cube.rotation.y += 0.005; + } + + renderer.render(scene, camera); + }; + animate(); + + // 클린업 함수 + return () => { + mount!.removeChild(renderer.domElement); + window.removeEventListener("mousedown", onMouseDown); + window.removeEventListener("mousemove", onMouseMove); + window.removeEventListener("mouseup", onMouseUp); + window.removeEventListener("wheel", onWheel); + window.removeEventListener("resize", onWindowResize); + }; + }, [isDragging, initialRotation, initialZoom]); + + return
; +}; + +export default Obj3d; diff --git a/src/components/Obj3d2.tsx b/src/components/Obj3d2.tsx new file mode 100644 index 0000000..2ecbc34 --- /dev/null +++ b/src/components/Obj3d2.tsx @@ -0,0 +1,143 @@ +import React, { useEffect, useRef, useState } from "react"; +import * as THREE from "three"; + +export const Obj3d2 = () => { + const mountRef = useRef(null); + const [mouseX, setMouseX] = useState(0); + const [mouseY, setMouseY] = useState(0); + const [isDragging, setIsDragging] = useState(false); + const [previousMouseX, setPreviousMouseX] = useState(0); + const [previousMouseY, setPreviousMouseY] = useState(0); + const [initialRotation, setInitialRotation] = useState( + null + ); + const [initialZoom, setInitialZoom] = useState(5); // 초기 줌 값을 상태로 관리 + + useEffect(() => { + const mount = mountRef.current; + + // Scene, Camera, Renderer 생성 + const scene = new THREE.Scene(); + scene.background = null; // 배경을 투명하게 설정 + + const camera = new THREE.PerspectiveCamera( + 70, + mount!.clientWidth / mount!.clientHeight, + 0.1, + 1000 + ); + const renderer = new THREE.WebGLRenderer({ alpha: true }); // alpha 속성을 true로 설정 + renderer.setSize(mount!.clientWidth, mount!.clientHeight); + mount!.appendChild(renderer.domElement); + + // 조명 + const directionLight = new THREE.DirectionalLight(0xffffff, 1.5); + directionLight.position.set(-0.7, 1, 5); + scene.add(directionLight); + + // 큐브 생성 (색상 변경) + const geometry = new THREE.ConeGeometry(2, 2.5, 6); + + const material = new THREE.MeshPhysicalMaterial({ + color: 0x02bbfa, + metalness: 1, + roughness: 0.5, + transparent: true, + opacity: 0.9, + reflectivity: 0.9, + }); + const cube = new THREE.Mesh(geometry, material); + scene.add(cube); + + camera.position.z = initialZoom; // 초기 줌 값 설정 + + // 마우스 이벤트 리스너 + const onMouseDown = (event: MouseEvent) => { + setIsDragging(true); + setPreviousMouseX(event.clientX); + setPreviousMouseY(event.clientY); + setInitialRotation(cube.rotation.clone()); // 클릭 시점의 회전 상태 저장 + setInitialZoom(camera.position.z); // 클릭 시점의 줌 값 저장 + }; + + const onMouseMove = (event: MouseEvent) => { + if (isDragging) { + const deltaX = event.clientX - previousMouseX; + const deltaY = event.clientY - previousMouseY; + + if (initialRotation) { + cube.rotation.y = initialRotation.y + deltaX * 0.005; + cube.rotation.x = initialRotation.x + deltaY * 0.005; + } + + setPreviousMouseX(event.clientX); + setPreviousMouseY(event.clientY); + } + }; + + const onMouseUp = () => { + setIsDragging(false); + }; + + const onWheel = (event: WheelEvent) => { + const delta = event.deltaY * 0.01; + + camera.position.z -= delta; + + // Limiting camera zoom + if (camera.position.z < 4) { + camera.position.z = 4; + } else if (camera.position.z > 15) { + camera.position.z = 15; + } + }; + + window.addEventListener("mousedown", onMouseDown); + window.addEventListener("mousemove", onMouseMove); + window.addEventListener("mouseup", onMouseUp); + window.addEventListener("wheel", onWheel); + + if (previousMouseX > 0 || previousMouseY > 0) { + cube.rotation.x -= previousMouseX; + cube.rotation.y -= previousMouseY; + } + + // 반응형 처리 + const onWindowResize = () => { + if (mount) { + camera.aspect = mount.clientWidth / mount.clientHeight; + camera.updateProjectionMatrix(); + renderer.setSize(mount.clientWidth, mount.clientHeight); + } + }; + window.addEventListener("resize", onWindowResize); + + // 애니메이션 루프 + const animate = () => { + requestAnimationFrame(animate); + + // 기본 회전 + if (!isDragging) { + cube.rotation.x += 0.005; + cube.rotation.y += 0.005; + } + + renderer.render(scene, camera); + }; + animate(); + + // 클린업 함수 + return () => { + mount!.removeChild(renderer.domElement); + window.removeEventListener("mousedown", onMouseDown); + window.removeEventListener("mousemove", onMouseMove); + window.removeEventListener("mouseup", onMouseUp); + window.removeEventListener("wheel", onWheel); + window.removeEventListener("resize", onWindowResize); + }; + }, [isDragging, initialRotation, initialZoom]); + + return
; +}; + +export default Obj3d2; diff --git a/src/components/Obj3d3.tsx b/src/components/Obj3d3.tsx new file mode 100644 index 0000000..98ff9f8 --- /dev/null +++ b/src/components/Obj3d3.tsx @@ -0,0 +1,143 @@ +import React, { useEffect, useRef, useState } from "react"; +import * as THREE from "three"; + +export const Obj3d3 = () => { + const mountRef = useRef(null); + const [mouseX, setMouseX] = useState(0); + const [mouseY, setMouseY] = useState(0); + const [isDragging, setIsDragging] = useState(false); + const [previousMouseX, setPreviousMouseX] = useState(0); + const [previousMouseY, setPreviousMouseY] = useState(0); + const [initialRotation, setInitialRotation] = useState( + null + ); + const [initialZoom, setInitialZoom] = useState(5); // 초기 줌 값을 상태로 관리 + + useEffect(() => { + const mount = mountRef.current; + + // Scene, Camera, Renderer 생성 + const scene = new THREE.Scene(); + scene.background = null; // 배경을 투명하게 설정 + + const camera = new THREE.PerspectiveCamera( + 70, + mount!.clientWidth / mount!.clientHeight, + 0.1, + 1000 + ); + const renderer = new THREE.WebGLRenderer({ alpha: true }); // alpha 속성을 true로 설정 + renderer.setSize(mount!.clientWidth, mount!.clientHeight); + mount!.appendChild(renderer.domElement); + + // 조명 + const directionLight = new THREE.DirectionalLight(0xffffff, 1.5); + directionLight.position.set(-0.7, 1, 5); + scene.add(directionLight); + + // 큐브 생성 (색상 변경) + const geometry = new THREE.TorusGeometry(1.3, 0.55, 16); + + const material = new THREE.MeshPhysicalMaterial({ + color: 0x03ff8d, + metalness: 1, + roughness: 0.5, + transparent: true, + opacity: 0.9, + reflectivity: 0.9, + }); + const cube = new THREE.Mesh(geometry, material); + scene.add(cube); + + camera.position.z = initialZoom; // 초기 줌 값 설정 + + // 마우스 이벤트 리스너 + const onMouseDown = (event: MouseEvent) => { + setIsDragging(true); + setPreviousMouseX(event.clientX); + setPreviousMouseY(event.clientY); + setInitialRotation(cube.rotation.clone()); // 클릭 시점의 회전 상태 저장 + setInitialZoom(camera.position.z); // 클릭 시점의 줌 값 저장 + }; + + const onMouseMove = (event: MouseEvent) => { + if (isDragging) { + const deltaX = event.clientX - previousMouseX; + const deltaY = event.clientY - previousMouseY; + + if (initialRotation) { + cube.rotation.y = initialRotation.y + deltaX * 0.005; + cube.rotation.x = initialRotation.x + deltaY * 0.005; + } + + setPreviousMouseX(event.clientX); + setPreviousMouseY(event.clientY); + } + }; + + const onMouseUp = () => { + setIsDragging(false); + }; + + const onWheel = (event: WheelEvent) => { + const delta = event.deltaY * 0.01; + + camera.position.z -= delta; + + // Limiting camera zoom + if (camera.position.z < 4) { + camera.position.z = 4; + } else if (camera.position.z > 15) { + camera.position.z = 15; + } + }; + + window.addEventListener("mousedown", onMouseDown); + window.addEventListener("mousemove", onMouseMove); + window.addEventListener("mouseup", onMouseUp); + window.addEventListener("wheel", onWheel); + + if (previousMouseX > 0 || previousMouseY > 0) { + cube.rotation.x -= previousMouseX; + cube.rotation.y -= previousMouseY; + } + + // 반응형 처리 + const onWindowResize = () => { + if (mount) { + camera.aspect = mount.clientWidth / mount.clientHeight; + camera.updateProjectionMatrix(); + renderer.setSize(mount.clientWidth, mount.clientHeight); + } + }; + window.addEventListener("resize", onWindowResize); + + // 애니메이션 루프 + const animate = () => { + requestAnimationFrame(animate); + + // 기본 회전 + if (!isDragging) { + cube.rotation.x += 0.005; + cube.rotation.y += 0.005; + } + + renderer.render(scene, camera); + }; + animate(); + + // 클린업 함수 + return () => { + mount!.removeChild(renderer.domElement); + window.removeEventListener("mousedown", onMouseDown); + window.removeEventListener("mousemove", onMouseMove); + window.removeEventListener("mouseup", onMouseUp); + window.removeEventListener("wheel", onWheel); + window.removeEventListener("resize", onWindowResize); + }; + }, [isDragging, initialRotation, initialZoom]); + + return
; +}; + +export default Obj3d3; diff --git a/src/pages/ThemeRecsQuestions.tsx b/src/pages/ThemeRecsQuestions.tsx index e4413ea..b6f3a6c 100644 --- a/src/pages/ThemeRecsQuestions.tsx +++ b/src/pages/ThemeRecsQuestions.tsx @@ -1,5 +1,8 @@ import React, { useEffect, useState } from "react"; import Cube from "./Cube"; +import Obj3d from "../components/Obj3d"; +import Obj3d2 from "../components/Obj3d2"; +import Obj3d3 from "../components/Obj3d3"; import { Container, StartBtn, @@ -95,12 +98,12 @@ const ThemeRecsQuestions = () => {
- +
- +
당신을 위한 방,
@@ -140,7 +143,7 @@ const ThemeRecsQuestions = () => {
- +