개발 · 컴퓨터공학/three.js / / 2024. 5. 10. 09:47

Threejs Cloth Tailor 개발일지 - TypeScripts 코드 모듈화

728x90
반응형

 

 

목표

자 이제 템플릿 프로젝트로 무엇을 해야 하느냐. 목표는 물리 리뮬레이션이 포함된 옷 재단 시뮬레이터.

우선 각각의 테스트 scene을 만드는 것이 첫 단계이다.

 

Test Scene

그렇다면 어떤 테스트 scene을 만들 것이냐. 우선은 cloth simulation을 구현하는 것이 가장 먼저가 될 것 같다.

orbit controler 환경도 이미 구현되어있기 때문에 carmen씨의 코드와 포스팅을 참고해보면서 threejs 기반 cloth simulation test 환경을 직접 개발하는 것이 그 첫번째 목표이다.

 

 

코드 모듈화

먼저 scene.ts에 모여있는 기능들을 하나씩 분리하여 모듈화시키자.

 

render-setting.ts

  // ===== 🖼️ CANVAS, RENDERER, & SCENE =====
  {
    canvas = document.querySelector(`canvas#${CANVAS_ID}`)!
    renderer = new WebGLRenderer({ canvas, antialias: true, alpha: true })
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
    renderer.shadowMap.enabled = true
    renderer.shadowMap.type = PCFSoftShadowMap
    scene = new Scene()
  }

canvas에 rendering initialize하는 기능을 모듈로  분리한다.

canvas의 id를 이용해서 원하는 canvas에 renderer를 할당하게 하는데, 현재는 canvas 하나만 단순히 사용할 예정이라. 간단히 처리한다.

 

import { PCFSoftShadowMap, Scene, WebGLRenderer } from "three"

export function InitScene(canvas_id : string): {scene:Scene, canvas:HTMLElement, renderer:WebGLRenderer}{
  const canvas: HTMLElement = document.querySelector(`canvas#${canvas_id}`)!
  const renderer: WebGLRenderer = new WebGLRenderer({ canvas, antialias: true, alpha: true })
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
  renderer.shadowMap.enabled = true
  renderer.shadowMap.type = PCFSoftShadowMap
  const scene: Scene = new Scene()
  return {scene, canvas, renderer};
}

이렇게 canvas와 renderer, scene을 분리해서 함수형으로 만들고 나면, scene.ts에서 InitScene 함수를 호출함으로써 초기화하도록 한다. 

 

controls.ts

import { Camera } from "three"
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"
import { toggleFullScreen } from "./helpers/fullscreen"

export function SetCamera
  (camera: Camera, canvas: HTMLElement): {
    cameraControls: OrbitControls,
  } {
  const cameraControls = new OrbitControls(camera, canvas)
  cameraControls.enableDamping = true
  cameraControls.autoRotate = false
  cameraControls.update()

  return { cameraControls }
}

export function SetFullScreenEvent(canvas: HTMLElement){
  window.addEventListener('dblclick', (event) => {
    if (event.target === canvas) {
      toggleFullScreen(canvas)
    }
  })
}

dragControl은 아직은 사용하지 않을 것이라서 제외시켰다.

camera controler와 full screen event 세팅을 해준다.

 

아참 함수 이름은 Camel식으로 하되 시작은 소문자로 변경했다.

 

debug-gui.ts

import GUI from 'lil-gui'
import { AmbientLight, AxesHelper, GridHelper, Mesh, PointLight, PointLightHelper, Scene } from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

let axesHelper: AxesHelper
let pointLightHelper: PointLightHelper

export function setHelper(scene: Scene, pointLight: PointLight) {
  axesHelper = new AxesHelper(4)
  axesHelper.visible = false
  scene.add(axesHelper)

  pointLightHelper = new PointLightHelper(pointLight, undefined, 'orange')
  pointLightHelper.visible = false
  scene.add(pointLightHelper)

  const gridHelper = new GridHelper(20, 20, 'teal', 'darkgray')
  gridHelper.position.y = -0.01
  scene.add(gridHelper)
}

/**
 * @desc must call after setHelper
 * @desc-kr 반드시 setHelper 호출 후에 호출될 것
 */
export function setDebug
  (target: Mesh, pointLight: PointLight, ambientLight: AmbientLight, cameraControls: OrbitControls) {
  const gui = new GUI({ title: '🐞 Debug GUI', width: 300 })
  const targetOneFolder = gui.addFolder('target one')

  targetOneFolder.add(target.position, 'x').min(-5).max(5).step(0.5).name('pos x')
  targetOneFolder.add(target.position, 'y').min(-5).max(5).step(0.5).name('pos y')
  targetOneFolder.add(target.position, 'z').min(-5).max(5).step(0.5).name('pos z')

  targetOneFolder.add(target.material, 'wireframe')
  targetOneFolder.addColor(target.material, 'color')
  targetOneFolder.add(target.material, 'metalness', 0, 1, 0.1)
  targetOneFolder.add(target.material, 'roughness', 0, 1, 0.1)

  targetOneFolder
    .add(target.rotation, 'x', -Math.PI * 2, Math.PI * 2, Math.PI / 4)
    .name('rotate x')
  targetOneFolder
    .add(target.rotation, 'y', -Math.PI * 2, Math.PI * 2, Math.PI / 4)
    .name('rotate y')
  targetOneFolder
    .add(target.rotation, 'z', -Math.PI * 2, Math.PI * 2, Math.PI / 4)
    .name('rotate z')

  const lightsFolder = gui.addFolder('Lights')
  lightsFolder.add(pointLight, 'visible').name('point light')
  lightsFolder.add(ambientLight, 'visible').name('ambient light')

  //chek helper is initialized
  if (!axesHelper || !pointLightHelper) {
    throw Error("helper is not initialized");
  }
  else {
    const helpersFolder = gui.addFolder('Helpers')
    helpersFolder.add(axesHelper, 'visible').name('axes')
    helpersFolder.add(pointLightHelper, 'visible').name('pointLight')

  }

  const cameraFolder = gui.addFolder('Camera')
  cameraFolder.add(cameraControls, 'autoRotate')

  // persist GUI state in local storage on changes
  gui.onFinishChange(() => {
    const guiState = gui.save()
    localStorage.setItem('guiState', JSON.stringify(guiState))
  })

  // load GUI state if available in local storage
  const guiState = localStorage.getItem('guiState')
  if (guiState) gui.load(JSON.parse(guiState))

  // reset GUI state button
  const resetGui = () => {
    localStorage.removeItem('guiState')
    gui.reset()
  }
  gui.add({ resetGui }, 'resetGui').name('RESET')

  gui.close()
}

debug gui와 helper도 따로 모듈화 하였다.

 

다음에는 OBJ 파일을 load할 수 있도록 OBJLoader를 가져와서 기능을 추가하자.

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