BaekRyang 530e6d0396 Refactor sprite effects to be independent and support sprite sheets
- 스프라이트 이펙트를 왜곡 영역에서 분리하여 독립적인 영역으로 관리
- 스프라이트 시트 애니메이션 기능 추가 및 UV 제어 로직 구현
- 에디터 내 스프라이트 이펙트 영역 시각화 및 드래그 이동 기능 추가
- 이펙트 감지 방식을 다각형에서 원형(Radius) 기반으로 변경
- 관련 타입 정의 및 매니저 클래스 리팩토링
2026-03-11 08:32:36 +09:00

150 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}
}