상세 컨텐츠

본문 제목

이미지 매핑하기

three.js

by 폴리프레임 2024. 11. 24. 17:52

본문

반응형

이미지를 Box D 에만 texture 변수에 담어서 Mapping 하고, 나머지는 new THREE.Color() 처리해봅니다.

/* eslint-disable */
import React, { useRef, useMemo, useState, useEffect } from "react";
// import { useNavigate } from "react-router-dom";
import { Canvas, useFrame } from "@react-three/fiber";
import { Html } from "@react-three/drei";
import { gsap } from "gsap"; // 애니메이션을 위해 gsap 사용
import * as THREE from "three";
import { numberPlate } from "../../assets/images/index"; // 이미지 경로 임포트

function createMesh(geometry, material, position, name) {
  const mesh = new THREE.Mesh(geometry, material.clone());
  mesh.position.set(...position);
  mesh.name = name;
  mesh.castShadow = true;
  mesh.receiveShadow = true;
  return mesh;
}

function Box({ geometry, material, position, initialPosition, name, texture }) {
  // const navigate = useNavigate();
  // Random color generation
  const [color, setColor] = useState(
    () => new THREE.Color(Math.random(), Math.random(), Math.random())
  );
  const [showTooltip, setShowTooltip] = useState(false);

  const mesh = useRef();
  const materialWithTexture = useMemo(() => {
    if (texture) {
      console.log("Applying texture to material:", texture);
      return new THREE.MeshStandardMaterial({ map: texture });
    }
    return new THREE.MeshStandardMaterial({ color });
  }, [texture, color]);

  useEffect(() => {
    const animatePosition = () => {
      if (mesh.current) {
        gsap.fromTo(
          mesh.current.position,
          {
            x: initialPosition[0],
            y: initialPosition[1],
            z: initialPosition[2]
          },
          {
            x: position[0],
            y: position[1],
            z: position[2],
            duration: 1.5,
            ease: "power2.inOut"
          }
        );
      } else {
        requestAnimationFrame(animatePosition);
      }
    };
    requestAnimationFrame(animatePosition);
  }, [position, initialPosition]);
  useEffect(() => {
    if (mesh.current) {
      mesh.current.material = materialWithTexture;
      mesh.current.material.needsUpdate = true;
      console.log("Material applied to mesh:", mesh.current.material);
    }
  }, [materialWithTexture]);

  const handlePointerOver = (event) => {
    document.body.style.cursor = "pointer";
    setShowTooltip(true);
  };
  const handlePointerOut = (event) => {
    document.body.style.cursor = "default";
    setShowTooltip(false);
  };
  const handleClick = (event) => {
    const newColor = new THREE.Color(
      Math.random(),
      Math.random(),
      Math.random()
    );
    setColor(newColor);
    // navigate(`/${name}`)
    console.log("Clicked :", `/${name}`);
  };

  return (
    <mesh
      ref={mesh}
      position={initialPosition}
      castShadow
      receiveShadow
      onPointerOver={handlePointerOver}
      onPointerOut={handlePointerOut}
      onClick={handleClick}
    >
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial attach="material" {...materialWithTexture} />
     
      {showTooltip && (
        <Html position={[0.5, 0, 0]} center>
          <div
            style={{
              background: "rgba(0, 0, 0, 0.70)",
              color: "white",
              padding: "5px",
              borderRadius: "5px",
              whiteSpace: "nowrap", // 텍스트가 한 줄로 유지되도록 설정
            }}
          >
            {name}
          </div>
        </Html>
      )}
    </mesh>
  );
}

const Blocks = React.memo(function Blocks(props) {
  const modelRef = useRef();
  const isDraggingRef = useRef(false);
  const rotationRef = useRef({ x: 0, y: 0 });
  const [scale, setScale] = useState([2.5, 2.5, 2.5]);
  const [texture, setTexture] = useState(null);

  // Define geometry and material within Blocks
  const boxGeometry = useMemo(() => new THREE.BoxGeometry(1, 1, 1), []);
  const material = useMemo(() => new THREE.MeshStandardMaterial(), []);

  useEffect(() => {
    const loader = new THREE.TextureLoader();
    loader.load(
      numberPlate,
      (loadedTexture) => {
        loadedTexture.flipY = false; // 텍스처의 flipY 속성 설정
        setTexture(loadedTexture);
        console.log("Texture loaded:", loadedTexture); // 텍스처 로드 확인
      },
      undefined,
      (error) => {
        console.error("Error loading texture:", error); // 텍스처 로드 오류 확인
      }
    );
  }, []);

  useFrame((state) => {
    if (modelRef.current) {
      // 드래그 중일 때만 회전만 적용
      if (isDraggingRef.current) {
        modelRef.current.rotation.y += rotationRef.current.y;
        modelRef.current.rotation.x += rotationRef.current.x; // 회전 후에 값을 초기화하여 부드러운 회전 유지
        rotationRef.current.x = 0;
        rotationRef.current.y = 0;
      } else {
        // 드래그가 아닐 때 y축 위치 애니메이션 적용
        modelRef.current.position.y =
          -1.5 + Math.sin(state.clock.getElapsedTime()) * 0.3;
      }
    }
  });

  useEffect(() => {
    const handleMouseDown = () => {
      isDraggingRef.current = true;
    };

    const handleMouseUp = () => {
      isDraggingRef.current = false;
    };

    const handleMouseMove = (event) => {
      if (isDraggingRef.current) {
        rotationRef.current = {
          x: event.movementY * 0.005,
          y: event.movementX * 0.005,
        };
      }
    };

    // touch event for mobile
    const handleTouchStart = () => {
      isDraggingRef.current = true;
    };

    const handleTouchEnd = () => {
      isDraggingRef.current = false;
    };

    const handleTouchMove = (event) => {
      if (isDraggingRef.current && event.touches.length === 1) {
        const touch = event.touches[0];
        rotationRef.current = {
          x: touch.movementY * 0.005,
          y: touch.movementX * 0.005,
        };
      }
    };

    window.addEventListener("mousedown", handleMouseDown);
    window.addEventListener("mouseup", handleMouseUp);
    window.addEventListener("mousemove", handleMouseMove);
    window.addEventListener("touchstart", handleTouchStart);
    window.addEventListener("touchend", handleTouchEnd);
    window.addEventListener("touchmove", handleTouchMove);

    return () => {
      window.removeEventListener("mousedown", handleMouseDown);
      window.removeEventListener("mouseup", handleMouseUp);
      window.removeEventListener("mousemove", handleMouseMove);
      window.removeEventListener("touchstart", handleTouchStart);
      window.removeEventListener("touchend", handleTouchEnd);
      window.removeEventListener("touchmove", handleTouchMove);
    };
  }, []);

  useEffect(() => {
    const handleResize = () => {
      if (window.innerWidth < 600) {
        //768
        setScale([1, 1, 1]);
      } else {
        setScale([2.5, 2.5, 2.5]);
      }
    };

    window.addEventListener("resize", handleResize);

    // Initial check
    handleResize();

    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }, []);

  return (
    <group
      {...props}
      dispose={null}
      ref={modelRef}
      position={[0, 0, 0]}
      scale={scale}
      // rotation={[0.25, 0, 0]}
    >
      <Box
        geometry={boxGeometry}
        material={material}
        position={[1.5, 0, 0]}
        initialPosition={[3, 0, 0]} // 초기 위치를 오른쪽 멀리 설정
        name="Box A"
      />
      <Box
        geometry={boxGeometry}
        material={material}
        position={[-1.5, 0, 0]}
        initialPosition={[-3, 0, 0]} // 초기 위치를 왼쪽 멀리 설정
        name="Box B"
      />
      <Box
        geometry={boxGeometry}
        material={material}
        position={[0, 1.5, 0]}
        initialPosition={[0, 3, 0]} // 초기 위치를 위쪽 멀리 설정
        name="Box C"
      />
      <Box
        geometry={boxGeometry}
        material={material}
        position={[0, -1.5, 0]}
        initialPosition={[0, -3, 0]} // 초기 위치를 아래쪽 멀리 설정
        name="Box D"
        texture={texture} // 텍스처 추가
      />
    </group>
  );
});

export default Blocks;

'three.js' 카테고리의 다른 글

GLSL, WGSL 의 차이  (2) 2024.12.14
OrbitControls - target, maxPolarAngle  (0) 2024.12.06
touch event - @react-three/fiber  (2) 2024.11.23
video backgorund with @react-three/fibre  (1) 2024.11.22
resize - Three.js  (0) 2024.11.22

관련글 더보기