[React/Three.js] 특정 GLTF 모델 제거, GLTF에 type 넣기, 특정 data 조회 후 삭제

cherry_·2023년 8월 15일
0

대망의 아바타 옷 꾸미기 기능!

	const groupRef = useRef(props);
    
  //deco 추가하는 함수
  const putDecoGltf = (gltfPath, setScale, positionX, positionY, positionZ, type) => {
    const gltfLoader = new GLTFLoader();
    gltfLoader.load(gltfPath, (childGltf) => {
    const model = childGltf.scene;
    model.scale.set(setScale, setScale, setScale); // 모델 크기 조정
    model.position.set(positionX, positionY, positionZ); // 모델 위치 설정
    groupRef.current.add(model);
    }); 
  } 

    gltfLoader.load(mouthGltfPaht, (childGltf) => {
      const model = childGltf.scene;
      model.scale.set(1.1, 1.1, 1.1); // 자식 모델 크기 조정
      model.position.set(0,-0.04,0); // 자식 모델 위치 설정
      groupRef.current.add(model);
    });

나는 이렇게 생긴 gltfLoader.load 함수를 만들어서 gltf 파일을 불러와 Canvas에 넣어주고 있다.
나는 아바타 꾸미기 기능을 만들고 있기 때문에 불러와야 하는 gltf 파일이 많았다,, 그래서 함수로 만들어서 로드하고 있었음.
그리고 아바타 사이즈 조절을 하는데 아바타+옷+악세서리 등이 함께 사이즈 조절, 위치 조절이 되어야 하기 때문에 groupRef로 그룹에 추가해서 묶어주는데,, 이건 나중에 포스팅 하겠음. 오늘 목적은 그게 아님.

오늘의 목적

슈 옷입히기 게임이라도 해보신 분들은 알겠지만 '상의'를 누르면 상의가 바뀌고 '하의'를 누르면 하의가 바뀌여야 한다.

그래, 바뀌어야 한다.

gltfLoader.load로 불러오기만 하면 불러온 옷 위에 옷이 덧씌워지고, 덧씌워지고, 덧씌워지고...
처음에는 아무것도 입히지 않은 아바타를 매번 다시 불러오면 되지 않나? 했는데 그럼 로딩도 길어지고, 무엇보다 load도 똑같이 다시 불러와지기 때문에 소용이 없었다.
이 길로 계속 밀고 나가기엔 '상의' 파트에서는 상의만 사라지고 새로운 상의가 올라와야 하는데 저렇게 하면 다른 하의, 악세서리 등도 사라질 것 같아서.
역시 특정 gltf를 없애는 코드를 작성해야겠다고 생각했다.

내가 할 일

그룹에 추가하는 형식으로 gltf를 load하기 때문에 그룹에서 빼면 된다고 생각했다.
내가 할일은 다음과 같다.

  1. GLTF remove & 그룹에서 빼기
  2. 눈, 입, 상의, 하의 등 특정 type을 가진 GLTF 조회하기 (이것만 삭제해야 하므로)

생각보다 적지?~??!?

1. groupRef.current.remove(model);

groupRef.current.remove(model);

위 코드는 model 객체를 groupRef에서 제거한다.
일단 내가 삭제할 객체들은 groupRef의 자식들이므로 자식 객체를 지우면 되지 않나...

groupRef.current.remove(groupRef.current.children[0]);

ㅋㅋ

이러면 올라간 gltf가 랜덤으로 지워짐
이때 glft에 type을 넣어야겠다고 생각했다. 나는 삭제할 model 객체가 딱 정해진 게 아니었다.
매 순간마다 그저 '상의' 혹은 '하의' type을 가진 객체를 조회해서 해당 객체만 삭제해야 했다.

2. 특정 GLTF 'type' 조회 후 삭제

  //deco 초기화하는 함수
  const removeDecoGltf = (type) => {
    const childToRemove = groupRef.current.children.find(
      (child) => child.userData.type === type
    );
  
    if (childToRemove) {
      groupRef.current.remove(childToRemove);
  }

childToRemove 변수에 userData.typetype과 일치하는 gltf를 저장한다.
이 gltf는 groupRef 그룹에 속해 있는 자식들 중 하나임
그리고 childToRemove가 존재하면 지운다!

3. 그럼 GLTF가 type을 가지고 있어야겠지?

type과 일치하는지 살펴보려면 gltf가 type을 가지고 있어야 한다. (당연함)
참고로 typevar itemTypeArray = ['eye', 'mouth', '상의', '하의', '한벌의상']; 이거임.
이 배열을 돌면서 일치하는지 검사한다... 내가 클릭한 의상의 type이 뭔지는 서버에서 넘어온다.

프론트에서 할일은,

  • 모델을 로드할 때 해당 모델에 type값 넣어주기
  • 동일한 type 값을 가진 모델이 Canvas에 올라가 있는지
  • 올라가 있다면 지우고 현재 모델을 올려주기
    • 없다면 현재 모델을 올리기
  //deco 추가하는 함수
  const putDecoGltf = (gltfPath, setScale, positionX, positionY, positionZ, type) => {
    const gltfLoader = new GLTFLoader();
    gltfLoader.load(gltfPath, (childGltf) => {
    const model = childGltf.scene;
    model.scale.set(setScale, setScale, setScale); // 모델 크기 조정
    model.position.set(positionX, positionY, positionZ); // 모델 위치 설정
    
    model.userData.type = type; // 원하는 타입 값을 설정
    
    itemTypeArray.map((n)=>{
      if(type === n){
        //기존에 있고, 현재 모델의 타입과 일치하는 타입의 모델 지우기
        removeDecoGltf(type)
      }
    })
    
    groupRef.current.add(model);
    }); 
  } 

userData 객체에 type 속성을 추가한 후, 원하는 타입 값을 설정했다.

userData는 Three.js의 3D 객체에 사용자 정의 데이터를 저장할 수 있는 속성입니다. 이를 이용하여 특정 자식 객체의 속성을 저장하고 사용할 수 있습니다.
여기서 중요한 점은, Three.js의 userData 속성은 단순한 JavaScript 객체이므로, 어떤 속성이든 저장할 수 있습니다. 따라서 type 외에도 다양한 사용자 정의 데이터를 userData에 저장할 수 있습니다.

라고 한다.

잘 돌아가서 만족스럽군.........

기타

  • ItemBox.js에서 썸네일 box 클릭 -> AvatarDeco.jsisClick 함수 호출
    이런 식으로 진행했다.

문제는 itemBox와 AvatarDeco 사이에 Item.js가 하나 더 있음. 원래는 자식 -> 부모 간의 데이터 전송으로 하려고 했지만 자식 부모간은 괜찮지만 손자와 할아버지? 이건 복잡한 문제가 생기는 거거든요.
그냥 외부함수 호출 (할아버지 외부인 취급) 로 갔더니 간편하고 좋았다. (정말?)

사실 힘들엇음...
주요한 방법은 아래와 같다.

  • 전역 함수가 호출되었을 때 해당 호출을 리액트 컴포넌트 내부에서 감지
  • Custom Event 사용:
export function isClick(index, itemtype){ //의상 index 받아오기
  type = itemtype;
  addGltfPath = gltfArray[index];
  const event = new CustomEvent('globalFunctionCalled');
  window.dispatchEvent(event);
}

const GltfGroupModels = (props) => {
  useEffect(() => {
    function handleGlobalFunctionCall() {
      console.log(type);
      // 원하는 작업 수행
      //removeDecoGltf();
      putDecoGltf(addGltfPath, 1.1, 0,-0.04,0, type);
      console.log(addGltfPath);
    }

    window.addEventListener('globalFunctionCalled', handleGlobalFunctionCall);

    return () => {
      window.removeEventListener('globalFunctionCalled', handleGlobalFunctionCall);
    };
  }, []);
}

급한 사람을 위해 남겨놓고, 나는 다음에 작성해야지... 마감이 얼마 안 남앗다고.... 다시 기능개발로 돌아가야겟다,,

0개의 댓글