Never give up

Three js - gauge example 본문

Three js

Three js - gauge example

대기만성 개발자 2023. 4. 8. 01:48
반응형

이번에는 계기판을 한번 만들어봤는데

 

계기판을 영어로 뭐라 표현하면 좋을까 고민하다가 그냥  게이지로 했습니다

 

index.tsx

import React, { useEffect } from 'react';
import util from 'src/util/common_util';
import CommonBtn from 'src/components/common_btn/common_btn';
import { useGauge } from 'src/custom_hooks';

const Gauge = () => {
    const { ref, setValue, resetCamera } = useGauge();

    useEffect(() => {
        const interval = setInterval(() => {
            setValue(util.generateNumber());
            console.log('/');
        }, 1000);

        return () => {
            clearInterval(interval);
        };
    }, []);

    return (
        <>
            <div className='div_three' ref={ref} />

            <CommonBtn
                position='absolute'
                right='30px'
                bottom='30px'
                onClick={resetCamera}>Reset camera</CommonBtn>
        </>
    );
};

export default Gauge;

이번에는 버튼 하나를 추가로 만들었는데

 

카메라를 움직일 수 있는 orbitcontrol을 추가해서 넣어봤습니다

 

그리고 1초마다 계기판을 움직일 수 있도록 1에서 9까지 난수를 발생시킵니다

(0부터 100까지 혹은 0.0부터 1.0까지 넣으려고 하다가 계산하기 귀찮아서 그런건 절대 아닙니다!)

 

useGauge.ts

import { useEffect, useRef } from "react";
import * as Three from "three";
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

const useGauge = () => {
  const ref = useRef<HTMLDivElement>(null);

  let renderer: Three.WebGLRenderer;

  let scene: Three.Scene;

  let camera: Three.PerspectiveCamera;

  let arrow: Three.Mesh;

  const minimumAngle = Math.PI * 0.25;

  const maximumAngle = Math.PI * -1.25;

  const division = Math.PI * 0.166667;

  let value = 0;

  let controls: OrbitControls;

  useEffect(() => {
    window.addEventListener('resize', handleResize);

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

  useEffect(() => {
    init();
  }, [ref]);

  const init = () => {
    // div에 ref 바인딩 된 이후에 작동
    if (ref.current !== null) {
      setCamera();

      setRenderer();

      controls = new OrbitControls(camera, ref.current);

      scene = new Three.Scene();

      renderer.render(scene, camera);

      makePanel();

      makeArrow();

      animate();
    }
  };

  // 카메라 셋팅
  const setCamera = () => {
    camera = new Three.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

    camera.position.z = 5;
  };

  // renderer 기본 셋팅
  const setRenderer = () => {
    renderer = new Three.WebGLRenderer({ antialias: true });

    renderer.setSize(window.innerWidth, window.innerHeight);

    ref.current!.appendChild(renderer.domElement);
  };

  // 배경 mesh
  const makePanel = () => {
    const geometry = new Three.CircleGeometry();

    const texture = new Three.TextureLoader().load('/assets/img/panel.jpg');

    const material = new Three.MeshBasicMaterial({ map: texture });
    const panel = new Three.Mesh(geometry, material);
    scene.add(panel);
  };

  // 지침 mesh
  const makeArrow = () => {
    const triangle = new Three.Shape();
    triangle.moveTo(0, 0);
    triangle.lineTo(0, 0.1);
    triangle.lineTo(-0.8, 0);
    triangle.lineTo(0, -0.1);
    triangle.lineTo(0, 0);

    const geometry = new Three.ShapeGeometry(triangle);

    const material = new Three.MeshBasicMaterial({ color: 0xD32F2F });

    arrow = new Three.Mesh(geometry, material);

    arrow.rotation.z = minimumAngle;

    scene.add(arrow);
  };

  // custom animation
  const moveTo = async () => {
    const zPosition = arrow.rotation.z;

    const destination = minimumAngle - (division * value);

    let velocity = Math.abs(Math.abs(zPosition) - Math.abs(destination)) * 2;

    if (velocity < 1) {
      velocity = 1;
    }

    // 시계 방향
    if (zPosition > destination) {
      arrow.rotation.z -= division * 0.05 * velocity;

      if (arrow.rotation.z < maximumAngle) {
        arrow.rotation.z = maximumAngle;
      }
    } else {
      // 반시계 방향
      arrow.rotation.z += division * 0.05 * velocity;

      if (arrow.rotation.z > minimumAngle) {
        arrow.rotation.z = minimumAngle;
      }
    }
  };

  // render screen
  const animate = () => {
    controls.update();

    moveTo();
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
  };

  // 브라우저 크기 변경 대응
  const handleResize = () => {
    if (ref !== null) {
      const width = ref.current!.clientWidth;
      const height = ref.current!.clientHeight;

      renderer.setSize(width, height);
      camera.aspect = width / height;

      camera.updateProjectionMatrix();
    };
  };

  return {
    ref,
    setValue: (num: number) => {
      value = num;
    },
    resetCamera: () => {
      controls.reset();
    }
  };
};

export default useGauge;

이번에는 mesh가 2개가 올라가는 구조인데

 

기본적으로 나중에 scene에 등록된 mesh가 위로 올라오게 됩니다

 

아마도 z index 설정하는 부분도 있을거 같아서 찾아보니

 

Mesh.translateZ() <- 이친구를 사용하면  설정할 수 있습니다

 

circlegeometry에 texture loader를 이용해서 이미지를 불러와 껍데기를 덮어줍니다(?)

 

그리고 지침은 shape geometry로 point to point로 그렸는데, 다른 방법도 있을테니 찾아보셔도 됩니다

(필자는 triangle geometry찾다가 안보여서 직접 그렸는데 이런건 좀 기본적으로 만들자..)

 

마지막으로 지침 움직임 부분은 (삽질에 삽질에 삽질을 더해서)  파이 * @로 돌리면서 포지션 잡고

 

현재 위치와 목적지 위치를 적당히(?) 계산해서 넣어줬습니다

 

< 완성된 계기판 >

속도 계산하는 부분도 개선이 필요한데 귀찮아서 따로 작업은 안했습니다...

 

반응형

'Three js' 카테고리의 다른 글

Three js - Fiber example  (0) 2023.06.03
Three js - GLTF example  (0) 2023.05.07
Three js - mouse event(feat. Raycaster)  (2) 2023.04.28
Three js - tutorial  (0) 2023.04.08
Comments