개발/three.js / / 2022. 11. 1. 16:25

three.js point 위치에서 mesh 생성해보기 + Image Panel FX 만들기

반응형

Point 좌표에 Mesh 생성

특정 geometry의 각 포인트들이 위치한 좌표에서 mesh를 생성해보자.

 

// Mesh
const planeMesh = new THREE.Mesh(
    new THREE.PlaneGeometry(0.3, 0.3),
    new THREE.MeshBasicMaterial({
        color: 'red',
        side: THREE.DoubleSide,
    })
)

// Points
const sphereGeometry = new THREE.SphereGeometry(1, 8, 8);
const positionArray = sphereGeometry.attributes.position.array;

// create several Plane Mesh
let plane;
for (let i = 0; i < positionArray.length; i+=3){
    plane = planeMesh.clone();
    plane.position.x = positionArray[i];
    plane.position.y = positionArray[i + 1];
    plane.position.z = positionArray[i + 2];

    plane.lookAt(0,0,0);

    scene.add(plane);
}

SphereGeometry를 생성하면 attribute 중에 각 포인트들이 있는 position이 담긴 array가 있다.

이 position array를 꺼내와서, planeMesh를 array 개수만큼 복사하고 위치를 지정한다.

 

lookat을 이용하면 모든 plane이 중심을 바라보도록 설정할 수 있다.

 

Image Panel 이펙트 만들기

위에서 다룬 point에 mesh를 생성하는 방법을 이용해서 image panel을 만들고 panel끼리 모양을 생성했다가 움직였다가 하는 이펙트를 만들어보자.

 

// main.js

import { ImagePanel } from './ImagePanel';

...

// Mesh
const planeGeometry = new THREE.PlaneGeometry(0.3, 0.3);

const textureLoader = new THREE.TextureLoader();

// Points
const sphereGeometry = new THREE.SphereGeometry(1, 8, 8);
const positionArray = sphereGeometry.attributes.position.array;

// create several Plane Mesh
let imagePanel;
for (let i = 0; i < positionArray.length; i+=3){
    imagePanel = new ImagePanel({
        textureLoader,
        scene,
        geometry: planeGeometry,
        imageSrc: `/images/0${Math.ceil(Math.random() * 5)}.jpg`,
        x: positionArray[i],
        y: positionArray[i + 1],
        z: positionArray[i + 2],
    })
}

PlaneGeometry로 panel mesh를 생성하고, 각 위치 역할을 할 SphereGeometry를 생성한다. 

밑에서 정의할 ImagePanel 클래스에 textLoader와 imageSrc경로 geometry등 정보를 생성자 매개변수로 전달하여 imagePanel을 생성한다.

 

// ImagePanel.js

import { DoubleSide, Mesh, MeshBasicMaterial } from "three";

export class ImagePanel{
    constructor(info){
        const texture = info.textureLoader.load(info.imageSrc);
        const material = new MeshBasicMaterial({
            map: texture,
            side: DoubleSide,
        });

        this.mesh = new Mesh(info.geometry, material);
        this.mesh.position.set(info.x, info.y, info.z);
        this.mesh.lookAt(0, 0, 0);

        info.scene.add(this.mesh);
    }
}

ImagePanel 클래스를 보자. 

매개변수 info로 받아온 textureLoader와 src를 이용해서 texture를 load해온다. 

material에 texture를 mapping하고 양쪽 다 렌더링하기 위해서 doubleside로 설정하여 생성한다.

 

생성한 material과 geometry로 mesh를 생성하고, 중심을 바라보게 하여 scene에 적용하면 이미지 패널을 생성할 수 있다.

 

Image Panel의 형태를 변경하는 이벤트 만들기

버튼을 누르면 이미지 패널들이 배치된 형태를 변경할 수 있도록 하기 위해서 버튼 이벤트를 만들어야한다. 

 

// Points
const sphereGeometry = new THREE.SphereGeometry(1, 8, 8);
const spherePositionArray = sphereGeometry.attributes.position.array;
const randomPositionArray = [];
for(let i = 0; i < spherePositionArray.length; i++){
    randomPositionArray.push((Math.random() - 0.5) * 10);
}

...

function setShape(e) {
    console.log(e.target);
    switch(e.target.dataset.type){
        case 'random':
            //
            console.log('random');
            break;
        case 'sphere':
            // 
            console.log('sphere');
            break;
    }
}

// button
const btnWrapper = document.createElement('div');
btnWrapper.classList.add('btns');

const randomBtn = document.createElement('button');
randomBtn.dataset.type = 'random';
randomBtn.style.cssText = 'position: absolute; left: 20px; top: 20px';
randomBtn.innerHTML = 'Random';
btnWrapper.append(randomBtn);

const sphereBtn = document.createElement('button');
sphereBtn.dataset.type = 'sphere';
sphereBtn.style.cssText = 'position: absolute; left: 20px; top: 50px';
sphereBtn.innerHTML = 'Sphere';
btnWrapper.append(sphereBtn);

document.body.append(btnWrapper);

// event
btnWrapper.addEventListener('click', setShape);
window.addEventListener('resize', setSize);

div를 생성하고, 그 안에 두 개의 버튼이 있도록 append하여 dom을 구성한다. 

각 버튼에 click하였을 때 setShape라는 함수가 실행되도록 addListener를 설정한다.

 

실행되는 이벤트에서는 event 변수의 타겟의 dataset.type의 문자열이 무엇인가에 따라서 다른 이벤트를 설정할 수 있도록 틀을 구성하였다.

 

이제 본래의 형태인 spherePosition으로 돌아오는 geometry의 position array와 변형되는 형태의 geometry position array 두 개가 필요하므로 만들어 놓는다.

 

Image Panel 이벤트로 형태 움직이는 모션 적용하기

이제 이벤트를 통해 Image Panel이 실제로 움직이도록 해보자.

 

// ImagePanel.js

import { DoubleSide, Mesh, MeshBasicMaterial } from "three";

export class ImagePanel{
    constructor(info){
    	
        ...
        
        // save rotation of sphere status
        this.sphereRotationX = this.mesh.rotation.x;
        this.sphereRotationY = this.mesh.rotation.y;
        this.sphereRotationZ = this.mesh.rotation.z;

        info.scene.add(this.mesh);
    }
}

ImagePanel 클래스에서는 sphere 상태일 때 원래 rotation으로 돌아갈 값을 저장해놓는다.

 

// main.js

import gsap from 'gsap';

...

// create several Plane Mesh
const imagePanels = [];
let imagePanel;
for (let i = 0; i < spherePositionArray.length; i+=3){
    imagePanel = new ImagePanel({
        textureLoader,
        scene,
        geometry: planeGeometry,
        imageSrc: `/images/0${Math.ceil(Math.random() * 5)}.jpg`,
        x: spherePositionArray[i],
        y: spherePositionArray[i + 1],
        z: spherePositionArray[i + 2],
    });

    imagePanels.push(imagePanel);
}

...

function setShape(e) {
    const type = e.target.dataset.type;
    let array; 

    switch(type){
        case 'random':
            array = randomPositionArray;
            break;
        case 'sphere':
            array = spherePositionArray;
            break;
    }

    for (let i =0; i < imagePanels.length; i++){
        // move position
        gsap.to(
            imagePanels[i].mesh.position,
            {
                duration: 2,
                x: array[i * 3],
                y: array[i * 3 + 1],
                z: array[i * 3 + 2],
            }
        );

        // rotate
        if (type === 'random'){
            gsap.to(
                imagePanels[i].mesh.rotation,
                {
                    duration: 2, 
                    x: 0,
                    y: 0,
                    z: 0,
                }
            );
        }
        else if(type === 'sphere'){
            gsap.to(
                imagePanels[i].mesh.rotation,
                {
                    duration: 2,
                    x: imagePanels[i].sphereRotationX,
                    y: imagePanels[i].sphereRotationY,
                    z: imagePanels[i].sphereRotationZ,
                }
            )
        }
    }
}

움직이는 애니메이션을 위해서 gsap를 npm으로 설치하고 import하였다. 

gsap를 각각의 image panel에 적용하기 위해서 mesh를 생성할 때, imagePanel array를 생성하여 객체들을 넣어놓는다.

 

버튼 이벤트인 setShape함수에서는 target에 dataset으로 넣은 type값에 따라서 position과 rotation을 gsap로 변경해주는 작업을 한다.

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유