GIS Developer 김형준 님의 유튜브 영상 을 보며 만들어본 three.js
의 particle 를 해 보았습니다.
우선 html 코드 입니다.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="basic.css">
<script type="importmap">
{
"imports": {
"three": "../../build/three.module.js"
}
}
</script>
</head>
<body>
<div id="webgl-container"></div>
<script type="module" src="basic.js" defer></script>
</body>
</html>
영상을 보면 시간이 지난 영상이라 three.js 에서 지원 하지 않는 것도 있고 버전 도 맞추기 위한 코트를 추가로 작성하였습니다.
html 에선
<script type="importmap">
{
"imports": {
"three": "../../build/three.module.js"
}
}
</script>
위에 코드를 추가해 최신 three.js 를 사용 할 수 있게 해 주었습니다.
다음은 css 파일 입니다.
* {
outline: none;
margin: 0;
}
body {
overflow: hidden;
}
#webgl-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
css 파일은 간단한 새팅 이라고 보면 됨니다.
다음은 patcle 을 표현 하기 위한 three.js 코드 입니다.
//three.js를 사용 하게 하는 import 작업(three.js 라이브러리를 설치했다는 가정하에 사용 가능)
import * as THREE from '../build/three.module.js';
import { OrbitControls } from "../examples/jsm/controls/OrbitControls.js"
class Particle {
constructor(scene, geometry, material, x, y) {
//box 들의 좌표를 가져오는 함수
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(x, y, 0);
scene.add(mesh);
mesh.wrapper = this;
this.awakenTime = undefined;
this._mesh = mesh;
}
awake(time) {
if(!this.awakenTime) {
this.awakenTime = time;
}
}
update(time) { //particle 이 마우스 의 좌표와 겹쳐 진 후에 box 들의 움직임 을 정의한 함수
if(this.awakenTime) {
const period = 6.0;
const t = time - this.awakenTime;
if(t >= period) this.awakenTime = undefined;
this._mesh.rotation.x = THREE.MathUtils.lerp(0, Math.PI*2*period, t/period);
let h_s, l;
if(t < period/2) {
h_s = THREE.MathUtils.lerp(0.0, 1.0, t/(period/2));
l = THREE.MathUtils.lerp(0.1, 1.0, t/(period/2));
} else {
h_s = THREE.MathUtils.lerp(1.0, 0.0, t/(period/2.0) - 1);
l = THREE.Math.lerp(1.0, 0.1, t/(period/2.0) - 1);
}
this._mesh.material.color.setHSL(h_s, h_s, l);
this._mesh.position.z = h_s * 15.0;
}
}
}
class App {
constructor() {
const divContainer = document.querySelector("#webgl-container");
this._divContainer = divContainer;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
divContainer.appendChild(renderer.domElement);
this._renderer = renderer;
const scene = new THREE.Scene();
this._scene = scene;
//불러올 함수들 명
this._setupCamera();
this._setupLight();
this._setupModel();
this._setupControls();
this._setupPicking();
window.onresize = this.resize.bind(this);
this.resize();
requestAnimationFrame(this.render.bind(this));
}
_setupControls() {
new OrbitControls(this._camera, this._divContainer);
}
_setupPicking() { //마우스에 따라 box 가 인식하는 함수
const raycaster = new THREE.Raycaster();
raycaster.cursorNormalizedPosition = undefined;
this._divContainer.addEventListener("mousemove", this._onMouseMove.bind(this));
this._raycaster = raycaster;
}
_onMouseMove(event) {
const width = this._divContainer.clientWidth;
const height = this._divContainer.clientHeight;
const x = (event.offsetX / width) * 2 - 1;
const y = -(event.offsetY / height) * 2 + 1;
this._raycaster.cursorNormalizedPosition = { x, y };
}
_setupModel() { //particle 각각 의 객채를 구성 하는 함수
const geometry = new THREE.BoxGeometry();
for(let x=-20; x<=20; x+=1.1) {
for(let y=-20; y<=20; y+=1.1) {
const color = new THREE.Color();
color.setHSL(0, 0, 0.1);
const material = new THREE.MeshStandardMaterial({ color });
new Particle(this._scene, geometry, material, x, y);
}
}
}
_setupCamera() { //보여주는 시점을 저으이 한 함수
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
100
);
camera.position.z = 40;
this._camera = camera;
}
_setupLight() { //물체를 볼수 있게 해주는 함수 입니다.
const color = 0xffffff;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
this._scene.add(light);
}
update(time) { //마우스의 위치의 따라 변화 하는 파티클을 움직이는 함수 입니다.
time *= 0.001; // second unit
if(this._raycaster && this._raycaster.cursorNormalizedPosition) {
this._raycaster.setFromCamera(this._raycaster.cursorNormalizedPosition, this._camera);
const targets = this._raycaster.intersectObjects(this._scene.children);
if(targets.length > 0) {
const mesh = targets[0].object;
const particle = mesh.wrapper;
particle.awake(time);
}
}
this._scene.traverse((obj3d) => {
if(obj3d instanceof THREE.Mesh) {
obj3d.wrapper.update(time);
}
});
}
render(time) { //화면에 보여주는 함수
this._renderer.render(this._scene, this._camera);
this.update(time);
requestAnimationFrame(this.render.bind(this));
}
resize() {//반응형으로 만들기 위한 사이즈 크기 함수
const width = this._divContainer.clientWidth;
const height = this._divContainer.clientHeight;
this._camera.aspect = width / height;
this._camera.updateProjectionMatrix();
this._renderer.setSize(width, height);
}
}
//App 이라는 클래스를 사용 하는 함수
window.onload = function () {
new App();
}
이상 입니다.