반응형
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- Game js
- Completer
- KakaoMap
- Raycasting
- Three js
- uint8array
- react
- Excel
- methodChannel
- FirebaseAnalytics
- identifierForVender
- Redux
- uint16array
- swagger-typescript-api
- RouteObserver
- Flutter
- Babel standalone
- jszip
- node
- Prism.js
- Image Resize typescript
- babel
- Three-fiber
- typescript
- androidId
- code editor
- userevent_tracker
- REST API
- web track
- webrtc
Archives
- Today
- Total
Never give up
Three js - gauge example 본문
반응형
이번에는 계기판을 한번 만들어봤는데
계기판을 영어로 뭐라 표현하면 좋을까 고민하다가 그냥 게이지로 했습니다
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