- 스프라이트 이펙트를 왜곡 영역에서 분리하여 독립적인 영역으로 관리 - 스프라이트 시트 애니메이션 기능 추가 및 UV 제어 로직 구현 - 에디터 내 스프라이트 이펙트 영역 시각화 및 드래그 이동 기능 추가 - 이펙트 감지 방식을 다각형에서 원형(Radius) 기반으로 변경 - 관련 타입 정의 및 매니저 클래스 리팩토링
150 lines
4.1 KiB
TypeScript
150 lines
4.1 KiB
TypeScript
import * as THREE from 'three';
|
||
import type { ShaderUniforms } from '@/types';
|
||
|
||
/**
|
||
* Three.js 씬 관리 클래스
|
||
*/
|
||
export class ThreeScene {
|
||
private scene: THREE.Scene;
|
||
private camera: THREE.OrthographicCamera;
|
||
private renderer: THREE.WebGLRenderer;
|
||
private mesh: THREE.Mesh | null = null;
|
||
private uniforms: ShaderUniforms;
|
||
|
||
constructor(private container: HTMLElement) {
|
||
// 씬 생성
|
||
this.scene = new THREE.Scene();
|
||
|
||
// 2D용 직교 카메라 설정 (카메라는 -z 방향, near=0 ~ far=1)
|
||
this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
|
||
|
||
// 렌더러 설정
|
||
this.renderer = new THREE.WebGLRenderer({
|
||
antialias: true,
|
||
alpha: false,
|
||
});
|
||
this.renderer.setPixelRatio(window.devicePixelRatio);
|
||
this.container.appendChild(this.renderer.domElement);
|
||
|
||
// 유니폼 초기화
|
||
this.uniforms = {
|
||
u_resolution: { value: new THREE.Vector2() },
|
||
u_texture: { value: null },
|
||
u_points: { value: new Float32Array(64) }, // 32포인트 × 2(x,y)
|
||
u_numAreas: { value: 0 },
|
||
u_dragVectors: { value: new Float32Array(16) }, // 8벡터 × 2(x,y)
|
||
u_distortionStrengths: { value: new Float32Array(8) },
|
||
u_lensEffects: { value: new Float32Array(8) },
|
||
};
|
||
|
||
this.handleResize();
|
||
window.addEventListener('resize', this.handleResize);
|
||
}
|
||
|
||
/**
|
||
* 윈도우 리사이즈 핸들러
|
||
*/
|
||
private handleResize = () => {
|
||
const width = this.container.clientWidth;
|
||
const height = this.container.clientHeight;
|
||
|
||
this.renderer.setSize(width, height);
|
||
this.uniforms.u_resolution.value.set(width, height);
|
||
|
||
if (this.mesh) {
|
||
this.render();
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 셰이더 머티리얼 설정
|
||
* @param vertexShader 버텍스 셰이더 소스
|
||
* @param fragmentShader 프래그먼트 셰이더 소스
|
||
*/
|
||
public setShaderMaterial(vertexShader: string, fragmentShader: string) {
|
||
console.log('[ThreeScene] setShaderMaterial 호출됨');
|
||
console.log('[ThreeScene] vertexShader 길이:', vertexShader.length);
|
||
console.log('[ThreeScene] fragmentShader 길이:', fragmentShader.length);
|
||
|
||
const geometry = new THREE.PlaneGeometry(2, 2);
|
||
const material = new THREE.ShaderMaterial({
|
||
uniforms: this.uniforms,
|
||
vertexShader,
|
||
fragmentShader,
|
||
});
|
||
|
||
console.log('[ThreeScene] ShaderMaterial 생성됨');
|
||
|
||
// 셰이더 컴파일 에러 확인
|
||
const renderer = this.renderer;
|
||
const testScene = new THREE.Scene();
|
||
const testMesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), material);
|
||
testScene.add(testMesh);
|
||
|
||
try {
|
||
renderer.compile(testScene, this.camera);
|
||
console.log('[ThreeScene] 셰이더 컴파일 성공!');
|
||
} catch (e) {
|
||
console.error('[ThreeScene] 셰이더 컴파일 에러:', e);
|
||
}
|
||
|
||
if (this.mesh) {
|
||
this.scene.remove(this.mesh);
|
||
}
|
||
|
||
this.mesh = new THREE.Mesh(geometry, material);
|
||
this.mesh.renderOrder = 0;
|
||
this.scene.add(this.mesh);
|
||
console.log('[ThreeScene] mesh를 씬에 추가함');
|
||
}
|
||
|
||
/**
|
||
* Three.js 씬 객체 반환
|
||
*/
|
||
public getScene(): THREE.Scene {
|
||
return this.scene;
|
||
}
|
||
|
||
/**
|
||
* 유니폼 값 업데이트
|
||
* @param updates 업데이트할 유니폼 값들
|
||
*/
|
||
public updateUniforms(updates: Partial<ShaderUniforms>) {
|
||
Object.keys(updates).forEach((key) => {
|
||
const uniformKey = key as keyof ShaderUniforms;
|
||
this.uniforms[uniformKey].value = updates[uniformKey]!.value;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 씬 렌더링
|
||
*/
|
||
public render() {
|
||
this.renderer.render(this.scene, this.camera);
|
||
}
|
||
|
||
/**
|
||
* 현재 해상도 가져오기
|
||
*/
|
||
public getResolution(): { x: number; y: number } {
|
||
return {
|
||
x: this.uniforms.u_resolution.value.x,
|
||
y: this.uniforms.u_resolution.value.y,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 리소스 정리
|
||
*/
|
||
public dispose() {
|
||
window.removeEventListener('resize', this.handleResize);
|
||
this.renderer.dispose();
|
||
if (this.mesh) {
|
||
this.mesh.geometry.dispose();
|
||
(this.mesh.material as THREE.Material).dispose();
|
||
}
|
||
if (this.container.contains(this.renderer.domElement)) {
|
||
this.container.removeChild(this.renderer.domElement);
|
||
}
|
||
}
|
||
} |