diff --git a/dist/index.js b/dist/index.js index 2df2a1a..1247499 100644 --- a/dist/index.js +++ b/dist/index.js @@ -402,10 +402,11 @@ var ImageDistortion = ({ }, [currentAreas, isReady]); const animationCallback = (0, import_react2.useCallback)((deltaTime) => { if (!isReady) return; - const updatedAreas = AnimationLoop.updateProgress(currentAreas, deltaTime); - const areasWithVectors = AnimationLoop.updateAreaDragVectors(updatedAreas); - setCurrentAreas(areasWithVectors); - }, [currentAreas, isReady]); + setCurrentAreas((prevAreas) => { + const updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime); + return AnimationLoop.updateAreaDragVectors(updatedAreas); + }); + }, [isReady]); useAnimationFrame(animationCallback, isPlaying); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "div", diff --git a/dist/index.js.map b/dist/index.js.map index 2a99bdd..da8873e 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/index.ts","../src/components/ImageDistortion.tsx","../src/engine/ThreeScene.ts","../src/engine/ShaderManager.ts","../src/utils/easing.ts","../src/engine/AnimationLoop.ts","../src/hooks/useAnimationFrame.ts","../src/utils/constants.ts"],"sourcesContent":["// 메인 컴포넌트\nexport { ImageDistortion } from './components/ImageDistortion';\nexport type { ImageDistortionProps } from './components/ImageDistortion';\n\n// 타입 정의\nexport type {\n Point,\n EasingFunction,\n DistortionMovement,\n DistortionArea,\n AreaBounds,\n ShaderUniforms,\n ShaderConfig,\n AnimationState,\n AnimationTicker,\n} from './types';\n\n// 유틸리티 함수\nexport { applyEasing } from './utils/easing';\nexport { SHADER_CONFIG, ANIMATION_CONFIG, DEFAULT_AREA } from './utils/constants';\n\n// 엔진 클래스 (고급 사용자용)\nexport { ThreeScene } from './engine/ThreeScene';\nexport { ShaderManager } from './engine/ShaderManager';\nexport { AnimationLoop } from './engine/AnimationLoop';\n\n// 훅\nexport { useAnimationFrame } from './hooks/useAnimationFrame';","import React, { useEffect, useRef, useState, useCallback } from 'react';\nimport * as THREE from 'three';\nimport { DistortionArea } from '../types';\nimport { ThreeScene } from '../engine/ThreeScene';\nimport { ShaderManager } from '../engine/ShaderManager';\nimport { AnimationLoop } from '../engine/AnimationLoop';\nimport { useAnimationFrame } from '../hooks/useAnimationFrame';\nimport { SHADER_CONFIG } from '../utils/constants';\n\n/**\n * ImageDistortion 컴포넌트 Props\n */\nexport interface ImageDistortionProps {\n /** 이미지 소스 URL */\n imageSrc: string;\n /** 왜곡 영역 배열 */\n areas: DistortionArea[];\n /** 버텍스 셰이더 경로 (선택사항) */\n vertexShaderPath?: string;\n /** 프래그먼트 셰이더 경로 (선택사항) */\n fragmentShaderPath?: string;\n /** 애니메이션 재생 여부 */\n isPlaying?: boolean;\n /** 컨테이너 스타일 */\n style?: React.CSSProperties;\n /** 컨테이너 클래스명 */\n className?: string;\n}\n\n/**\n * GPU 가속 이미지 왜곡 컴포넌트\n * Three.js와 GLSL 셰이더를 사용하여 실시간 이미지 왜곡 효과를 제공합니다.\n */\nexport const ImageDistortion: React.FC = ({\n imageSrc,\n areas,\n vertexShaderPath,\n fragmentShaderPath,\n isPlaying = true,\n style,\n className,\n}) => {\n const containerRef = useRef(null);\n const sceneRef = useRef(null);\n const shaderManagerRef = useRef(new ShaderManager());\n const textureRef = useRef(null);\n\n const [isReady, setIsReady] = useState(false);\n const [currentAreas, setCurrentAreas] = useState(areas);\n\n // 영역 변경 시 상태 업데이트\n useEffect(() => {\n setCurrentAreas(areas);\n }, [areas]);\n\n // Three.js 씬 초기화\n useEffect(() => {\n if (!containerRef.current) return;\n\n const scene = new ThreeScene(containerRef.current);\n sceneRef.current = scene;\n\n // 셰이더 로드\n const vertPath = vertexShaderPath || '/shaders/distortion.vert.glsl';\n const fragPath = fragmentShaderPath || '/shaders/distortion.frag.glsl';\n\n shaderManagerRef.current\n .loadShaders(vertPath, fragPath)\n .then(({ vertex, fragment }) => {\n scene.setShaderMaterial(vertex, fragment);\n setIsReady(true);\n })\n .catch((error) => {\n console.error('셰이더 로드 실패:', error);\n });\n\n return () => {\n scene.dispose();\n if (textureRef.current) {\n textureRef.current.dispose();\n }\n };\n }, [vertexShaderPath, fragmentShaderPath]);\n\n // 이미지 텍스처 로드\n useEffect(() => {\n if (!imageSrc || !isReady) return;\n\n const loader = new THREE.TextureLoader();\n loader.load(\n imageSrc,\n (texture) => {\n textureRef.current = texture;\n if (sceneRef.current) {\n sceneRef.current.updateUniforms({\n u_texture: { value: texture },\n });\n sceneRef.current.render();\n }\n },\n undefined,\n (error) => {\n console.error('이미지 로드 실패:', error);\n }\n );\n\n return () => {\n if (textureRef.current) {\n textureRef.current.dispose();\n textureRef.current = null;\n }\n };\n }, [imageSrc, isReady]);\n\n // 셰이더 유니폼 업데이트\n useEffect(() => {\n if (!sceneRef.current || !isReady) return;\n\n // 포인트 배열 생성\n const points = new Float32Array(SHADER_CONFIG.MAX_POINTS * 2);\n currentAreas.forEach((area, areaIndex) => {\n area.basePoints.forEach((point, pointIndex) => {\n const index = (areaIndex * 4 + pointIndex) * 2;\n points[index] = point.x;\n points[index + 1] = point.y;\n });\n });\n\n // 드래그 벡터 배열 생성\n const dragVectors = new Float32Array(SHADER_CONFIG.MAX_DRAG_VECTORS * 2);\n currentAreas.forEach((area, index) => {\n const baseIndex = index * 2;\n dragVectors[baseIndex] = area.dragVector.x;\n dragVectors[baseIndex + 1] = area.dragVector.y;\n });\n\n // 강도 배열 생성\n const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS);\n currentAreas.forEach((area, index) => {\n strengths[index] = area.distortionStrength;\n });\n\n sceneRef.current.updateUniforms({\n u_numAreas: { value: currentAreas.length },\n u_points: { value: points },\n u_dragVectors: { value: dragVectors },\n u_distortionStrengths: { value: strengths },\n });\n\n sceneRef.current.render();\n }, [currentAreas, isReady]);\n\n // 애니메이션 루프\n const animationCallback = useCallback((deltaTime: number) => {\n if (!isReady) return;\n\n // 진행도 업데이트\n const updatedAreas = AnimationLoop.updateProgress(currentAreas, deltaTime);\n\n // 드래그 벡터 업데이트\n const areasWithVectors = AnimationLoop.updateAreaDragVectors(updatedAreas);\n\n setCurrentAreas(areasWithVectors);\n }, [currentAreas, isReady]);\n\n useAnimationFrame(animationCallback, isPlaying);\n\n return (\n \n );\n};","import * as THREE from 'three';\nimport { ShaderUniforms } from '@/types';\n\n/**\n * Three.js 씬 관리 클래스\n */\nexport class ThreeScene {\n private scene: THREE.Scene;\n private camera: THREE.OrthographicCamera;\n private renderer: THREE.WebGLRenderer;\n private mesh: THREE.Mesh | null = null;\n private uniforms: ShaderUniforms;\n\n constructor(private container: HTMLElement) {\n // 씬 생성\n this.scene = new THREE.Scene();\n\n // 2D용 직교 카메라 설정\n this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n // 렌더러 설정\n this.renderer = new THREE.WebGLRenderer({\n antialias: true,\n alpha: false,\n });\n this.renderer.setPixelRatio(window.devicePixelRatio);\n this.container.appendChild(this.renderer.domElement);\n\n // 유니폼 초기화\n this.uniforms = {\n u_resolution: { value: new THREE.Vector2() },\n u_texture: { value: null },\n u_points: { value: new Float32Array(64) }, // 32포인트 × 2(x,y)\n u_numAreas: { value: 0 },\n u_dragVectors: { value: new Float32Array(16) }, // 8벡터 × 2(x,y)\n u_distortionStrengths: { value: new Float32Array(8) },\n };\n\n this.handleResize();\n window.addEventListener('resize', this.handleResize);\n }\n\n /**\n * 윈도우 리사이즈 핸들러\n */\n private handleResize = () => {\n const width = this.container.clientWidth;\n const height = this.container.clientHeight;\n\n this.renderer.setSize(width, height);\n this.uniforms.u_resolution.value.set(width, height);\n\n if (this.mesh) {\n this.render();\n }\n };\n\n /**\n * 셰이더 머티리얼 설정\n * @param vertexShader 버텍스 셰이더 소스\n * @param fragmentShader 프래그먼트 셰이더 소스\n */\n public setShaderMaterial(vertexShader: string, fragmentShader: string) {\n const geometry = new THREE.PlaneGeometry(2, 2);\n const material = new THREE.ShaderMaterial({\n uniforms: this.uniforms,\n vertexShader,\n fragmentShader,\n });\n\n if (this.mesh) {\n this.scene.remove(this.mesh);\n }\n\n this.mesh = new THREE.Mesh(geometry, material);\n this.scene.add(this.mesh);\n }\n\n /**\n * 유니폼 값 업데이트\n * @param updates 업데이트할 유니폼 값들\n */\n public updateUniforms(updates: Partial) {\n Object.keys(updates).forEach((key) => {\n const uniformKey = key as keyof ShaderUniforms;\n this.uniforms[uniformKey].value = updates[uniformKey]!.value;\n });\n }\n\n /**\n * 씬 렌더링\n */\n public render() {\n this.renderer.render(this.scene, this.camera);\n }\n\n /**\n * 리소스 정리\n */\n public dispose() {\n window.removeEventListener('resize', this.handleResize);\n this.renderer.dispose();\n if (this.mesh) {\n this.mesh.geometry.dispose();\n (this.mesh.material as THREE.Material).dispose();\n }\n if (this.container.contains(this.renderer.domElement)) {\n this.container.removeChild(this.renderer.domElement);\n }\n }\n}","/**\n * 셰이더 파일 로딩 및 관리 클래스\n */\nexport class ShaderManager {\n private vertexShaderSource: string | null = null;\n private fragmentShaderSource: string | null = null;\n\n /**\n * 셰이더 파일들을 비동기로 로드\n * @param vertexPath 버텍스 셰이더 파일 경로\n * @param fragmentPath 프래그먼트 셰이더 파일 경로\n * @returns 로드된 셰이더 소스 코드\n */\n public async loadShaders(\n vertexPath: string,\n fragmentPath: string\n ): Promise<{ vertex: string; fragment: string }> {\n try {\n const [vertexResponse, fragmentResponse] = await Promise.all([\n fetch(vertexPath),\n fetch(fragmentPath),\n ]);\n\n if (!vertexResponse.ok) {\n throw new Error(`버텍스 셰이더 로드 실패: ${vertexResponse.statusText}`);\n }\n if (!fragmentResponse.ok) {\n throw new Error(`프래그먼트 셰이더 로드 실패: ${fragmentResponse.statusText}`);\n }\n\n this.vertexShaderSource = await vertexResponse.text();\n this.fragmentShaderSource = await fragmentResponse.text();\n\n return {\n vertex: this.vertexShaderSource,\n fragment: this.fragmentShaderSource,\n };\n } catch (error) {\n console.error('셰이더 로드 실패:', error);\n throw new Error('셰이더 로딩에 실패했습니다');\n }\n }\n\n /**\n * 버텍스 셰이더 소스 코드 반환\n */\n public getVertexShader(): string {\n if (!this.vertexShaderSource) {\n throw new Error('버텍스 셰이더가 로드되지 않았습니다');\n }\n return this.vertexShaderSource;\n }\n\n /**\n * 프래그먼트 셰이더 소스 코드 반환\n */\n public getFragmentShader(): string {\n if (!this.fragmentShaderSource) {\n throw new Error('프래그먼트 셰이더가 로드되지 않았습니다');\n }\n return this.fragmentShaderSource;\n }\n}","import { EasingFunction } from '../types';\n\ntype EasingFunc = (t: number) => number;\n\n/**\n * 이징 함수 구현 맵\n */\nconst easingFunctions: Record = {\n linear: (t) => t,\n\n easeIn: (t) => t * t,\n easeOut: (t) => t * (2 - t),\n easeInOut: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),\n\n easeInQuad: (t) => t * t,\n easeOutQuad: (t) => t * (2 - t),\n};\n\n/**\n * 진행도에 이징 함수를 적용\n * @param progress 진행도 (0.0 - 1.0)\n * @param easingType 적용할 이징 함수 타입\n * @returns 이징이 적용된 진행도 (0.0 - 1.0)\n */\nexport const applyEasing = (\n progress: number,\n easingType: EasingFunction\n): number => {\n const clampedProgress = Math.max(0, Math.min(1, progress));\n return easingFunctions[easingType](clampedProgress);\n};","import { DistortionArea, Point } from '../types';\nimport { applyEasing } from '../utils/easing';\n\n/**\n * 애니메이션 루프 관리 클래스\n */\nexport class AnimationLoop {\n /**\n * 영역들의 드래그 벡터를 현재 진행도에 따라 업데이트\n * @param areas 왜곡 영역 배열\n * @returns 업데이트된 영역 배열\n */\n public static updateAreaDragVectors(\n areas: DistortionArea[]\n ): DistortionArea[] {\n return areas.map((area) => {\n const { progress, movement } = area;\n\n // 이징 적용\n const easedProgress = applyEasing(progress, movement.easing);\n\n // 벡터 간 보간\n let dragVector: Point;\n\n if (easedProgress < 0.5) {\n // 0.0 -> 0.5: 0에서 vectorA로 보간\n const t = easedProgress * 2;\n dragVector = {\n x: movement.vectorA.x * t,\n y: movement.vectorA.y * t,\n };\n } else {\n // 0.5 -> 1.0: vectorA에서 0으로 보간\n const t = (easedProgress - 0.5) * 2;\n dragVector = {\n x: movement.vectorA.x * (1 - t),\n y: movement.vectorA.y * (1 - t),\n };\n }\n\n return {\n ...area,\n dragVector,\n };\n });\n }\n\n /**\n * 모든 영역의 진행도를 델타 타임만큼 업데이트\n * @param areas 왜곡 영역 배열\n * @param deltaTime 델타 타임 (초)\n * @returns 업데이트된 영역 배열\n */\n public static updateProgress(\n areas: DistortionArea[],\n deltaTime: number\n ): DistortionArea[] {\n return areas.map((area) => {\n let newProgress = area.progress + deltaTime / area.movement.duration;\n newProgress %= 1.0; // 루프\n\n return {\n ...area,\n progress: newProgress,\n };\n });\n }\n}","import { useEffect, useRef } from 'react';\n\n/**\n * requestAnimationFrame을 사용한 애니메이션 루프 훅\n * @param callback 매 프레임마다 호출될 콜백 (deltaTime을 인자로 받음)\n * @param isPlaying 애니메이션 재생 여부\n */\nexport const useAnimationFrame = (\n callback: (deltaTime: number) => void,\n isPlaying: boolean = true\n) => {\n const requestRef = useRef(undefined);\n const previousTimeRef = useRef(undefined);\n\n useEffect(() => {\n if (!isPlaying) return;\n\n const animate = (time: number) => {\n if (previousTimeRef.current !== undefined) {\n const deltaTime = (time - previousTimeRef.current) / 1000; // 밀리초를 초로 변환\n callback(deltaTime);\n }\n previousTimeRef.current = time;\n requestRef.current = requestAnimationFrame(animate);\n };\n\n requestRef.current = requestAnimationFrame(animate);\n\n return () => {\n if (requestRef.current) {\n cancelAnimationFrame(requestRef.current);\n }\n };\n }, [callback, isPlaying]);\n};","/**\n * 셰이더 관련 설정\n */\nexport const SHADER_CONFIG = {\n /** 최대 영역 개수 */\n MAX_AREAS: 8,\n /** 최대 포인트 개수 (8영역 × 4포인트) */\n MAX_POINTS: 32,\n /** 최대 드래그 벡터 개수 */\n MAX_DRAG_VECTORS: 8,\n /** 최대 강도 배열 크기 */\n MAX_STRENGTHS: 8,\n} as const;\n\n/**\n * 애니메이션 관련 설정\n */\nexport const ANIMATION_CONFIG = {\n /** 목표 FPS */\n TARGET_FPS: 60,\n /** 델타 타임 (약 16.67ms) */\n DELTA_TIME: 1 / 60,\n} as const;\n\n/**\n * 기본 영역 설정값\n */\nexport const DEFAULT_AREA = {\n /** 기본 왜곡 강도 */\n DISTORTION_STRENGTH: 0.5,\n /** 기본 애니메이션 지속 시간 (초) */\n DURATION: 2.0,\n /** 기본 이징 함수 */\n EASING: 'easeInOut' as const,\n /** 기본 벡터 A */\n VECTOR_A: { x: 0.1, y: 0.1 },\n /** 기본 벡터 B */\n VECTOR_B: { x: -0.1, y: -0.1 },\n} as const;"],"mappings":"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","names":["import_react","THREE"]} \ No newline at end of file +{"version":3,"sources":["../src/index.ts","../src/components/ImageDistortion.tsx","../src/engine/ThreeScene.ts","../src/engine/ShaderManager.ts","../src/utils/easing.ts","../src/engine/AnimationLoop.ts","../src/hooks/useAnimationFrame.ts","../src/utils/constants.ts"],"sourcesContent":["// 메인 컴포넌트\nexport { ImageDistortion } from './components/ImageDistortion';\nexport type { ImageDistortionProps } from './components/ImageDistortion';\n\n// 타입 정의\nexport type {\n Point,\n EasingFunction,\n DistortionMovement,\n DistortionArea,\n AreaBounds,\n ShaderUniforms,\n ShaderConfig,\n AnimationState,\n AnimationTicker,\n} from './types';\n\n// 유틸리티 함수\nexport { applyEasing } from './utils/easing';\nexport { SHADER_CONFIG, ANIMATION_CONFIG, DEFAULT_AREA } from './utils/constants';\n\n// 엔진 클래스 (고급 사용자용)\nexport { ThreeScene } from './engine/ThreeScene';\nexport { ShaderManager } from './engine/ShaderManager';\nexport { AnimationLoop } from './engine/AnimationLoop';\n\n// 훅\nexport { useAnimationFrame } from './hooks/useAnimationFrame';","import React, { useEffect, useRef, useState, useCallback } from 'react';\nimport * as THREE from 'three';\nimport { DistortionArea } from '../types';\nimport { ThreeScene } from '../engine/ThreeScene';\nimport { ShaderManager } from '../engine/ShaderManager';\nimport { AnimationLoop } from '../engine/AnimationLoop';\nimport { useAnimationFrame } from '../hooks/useAnimationFrame';\nimport { SHADER_CONFIG } from '../utils/constants';\n\n/**\n * ImageDistortion 컴포넌트 Props\n */\nexport interface ImageDistortionProps {\n /** 이미지 소스 URL */\n imageSrc: string;\n /** 왜곡 영역 배열 */\n areas: DistortionArea[];\n /** 버텍스 셰이더 경로 (선택사항) */\n vertexShaderPath?: string;\n /** 프래그먼트 셰이더 경로 (선택사항) */\n fragmentShaderPath?: string;\n /** 애니메이션 재생 여부 */\n isPlaying?: boolean;\n /** 컨테이너 스타일 */\n style?: React.CSSProperties;\n /** 컨테이너 클래스명 */\n className?: string;\n}\n\n/**\n * GPU 가속 이미지 왜곡 컴포넌트\n * Three.js와 GLSL 셰이더를 사용하여 실시간 이미지 왜곡 효과를 제공합니다.\n */\nexport const ImageDistortion: React.FC = ({\n imageSrc,\n areas,\n vertexShaderPath,\n fragmentShaderPath,\n isPlaying = true,\n style,\n className,\n}) => {\n const containerRef = useRef(null);\n const sceneRef = useRef(null);\n const shaderManagerRef = useRef(new ShaderManager());\n const textureRef = useRef(null);\n\n const [isReady, setIsReady] = useState(false);\n const [currentAreas, setCurrentAreas] = useState(areas);\n\n // 영역 변경 시 상태 업데이트\n useEffect(() => {\n setCurrentAreas(areas);\n }, [areas]);\n\n // Three.js 씬 초기화\n useEffect(() => {\n if (!containerRef.current) return;\n\n const scene = new ThreeScene(containerRef.current);\n sceneRef.current = scene;\n\n // 셰이더 로드\n const vertPath = vertexShaderPath || '/shaders/distortion.vert.glsl';\n const fragPath = fragmentShaderPath || '/shaders/distortion.frag.glsl';\n\n shaderManagerRef.current\n .loadShaders(vertPath, fragPath)\n .then(({ vertex, fragment }) => {\n scene.setShaderMaterial(vertex, fragment);\n setIsReady(true);\n })\n .catch((error) => {\n console.error('셰이더 로드 실패:', error);\n });\n\n return () => {\n scene.dispose();\n if (textureRef.current) {\n textureRef.current.dispose();\n }\n };\n }, [vertexShaderPath, fragmentShaderPath]);\n\n // 이미지 텍스처 로드\n useEffect(() => {\n if (!imageSrc || !isReady) return;\n\n const loader = new THREE.TextureLoader();\n loader.load(\n imageSrc,\n (texture) => {\n textureRef.current = texture;\n if (sceneRef.current) {\n sceneRef.current.updateUniforms({\n u_texture: { value: texture },\n });\n sceneRef.current.render();\n }\n },\n undefined,\n (error) => {\n console.error('이미지 로드 실패:', error);\n }\n );\n\n return () => {\n if (textureRef.current) {\n textureRef.current.dispose();\n textureRef.current = null;\n }\n };\n }, [imageSrc, isReady]);\n\n // 셰이더 유니폼 업데이트\n useEffect(() => {\n if (!sceneRef.current || !isReady) return;\n\n // 포인트 배열 생성\n const points = new Float32Array(SHADER_CONFIG.MAX_POINTS * 2);\n currentAreas.forEach((area, areaIndex) => {\n area.basePoints.forEach((point, pointIndex) => {\n const index = (areaIndex * 4 + pointIndex) * 2;\n points[index] = point.x;\n points[index + 1] = point.y;\n });\n });\n\n // 드래그 벡터 배열 생성\n const dragVectors = new Float32Array(SHADER_CONFIG.MAX_DRAG_VECTORS * 2);\n currentAreas.forEach((area, index) => {\n const baseIndex = index * 2;\n dragVectors[baseIndex] = area.dragVector.x;\n dragVectors[baseIndex + 1] = area.dragVector.y;\n });\n\n // 강도 배열 생성\n const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS);\n currentAreas.forEach((area, index) => {\n strengths[index] = area.distortionStrength;\n });\n\n sceneRef.current.updateUniforms({\n u_numAreas: { value: currentAreas.length },\n u_points: { value: points },\n u_dragVectors: { value: dragVectors },\n u_distortionStrengths: { value: strengths },\n });\n\n sceneRef.current.render();\n }, [currentAreas, isReady]);\n\n // 애니메이션 루프\n const animationCallback = useCallback((deltaTime: number) => {\n if (!isReady) return;\n\n setCurrentAreas((prevAreas) => {\n // 진행도 업데이트\n const updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);\n // 드래그 벡터 업데이트\n return AnimationLoop.updateAreaDragVectors(updatedAreas);\n });\n }, [isReady]);\n\n useAnimationFrame(animationCallback, isPlaying);\n\n return (\n \n );\n};","import * as THREE from 'three';\nimport { ShaderUniforms } from '@/types';\n\n/**\n * Three.js 씬 관리 클래스\n */\nexport class ThreeScene {\n private scene: THREE.Scene;\n private camera: THREE.OrthographicCamera;\n private renderer: THREE.WebGLRenderer;\n private mesh: THREE.Mesh | null = null;\n private uniforms: ShaderUniforms;\n\n constructor(private container: HTMLElement) {\n // 씬 생성\n this.scene = new THREE.Scene();\n\n // 2D용 직교 카메라 설정\n this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n // 렌더러 설정\n this.renderer = new THREE.WebGLRenderer({\n antialias: true,\n alpha: false,\n });\n this.renderer.setPixelRatio(window.devicePixelRatio);\n this.container.appendChild(this.renderer.domElement);\n\n // 유니폼 초기화\n this.uniforms = {\n u_resolution: { value: new THREE.Vector2() },\n u_texture: { value: null },\n u_points: { value: new Float32Array(64) }, // 32포인트 × 2(x,y)\n u_numAreas: { value: 0 },\n u_dragVectors: { value: new Float32Array(16) }, // 8벡터 × 2(x,y)\n u_distortionStrengths: { value: new Float32Array(8) },\n };\n\n this.handleResize();\n window.addEventListener('resize', this.handleResize);\n }\n\n /**\n * 윈도우 리사이즈 핸들러\n */\n private handleResize = () => {\n const width = this.container.clientWidth;\n const height = this.container.clientHeight;\n\n this.renderer.setSize(width, height);\n this.uniforms.u_resolution.value.set(width, height);\n\n if (this.mesh) {\n this.render();\n }\n };\n\n /**\n * 셰이더 머티리얼 설정\n * @param vertexShader 버텍스 셰이더 소스\n * @param fragmentShader 프래그먼트 셰이더 소스\n */\n public setShaderMaterial(vertexShader: string, fragmentShader: string) {\n const geometry = new THREE.PlaneGeometry(2, 2);\n const material = new THREE.ShaderMaterial({\n uniforms: this.uniforms,\n vertexShader,\n fragmentShader,\n });\n\n if (this.mesh) {\n this.scene.remove(this.mesh);\n }\n\n this.mesh = new THREE.Mesh(geometry, material);\n this.scene.add(this.mesh);\n }\n\n /**\n * 유니폼 값 업데이트\n * @param updates 업데이트할 유니폼 값들\n */\n public updateUniforms(updates: Partial) {\n Object.keys(updates).forEach((key) => {\n const uniformKey = key as keyof ShaderUniforms;\n this.uniforms[uniformKey].value = updates[uniformKey]!.value;\n });\n }\n\n /**\n * 씬 렌더링\n */\n public render() {\n this.renderer.render(this.scene, this.camera);\n }\n\n /**\n * 리소스 정리\n */\n public dispose() {\n window.removeEventListener('resize', this.handleResize);\n this.renderer.dispose();\n if (this.mesh) {\n this.mesh.geometry.dispose();\n (this.mesh.material as THREE.Material).dispose();\n }\n if (this.container.contains(this.renderer.domElement)) {\n this.container.removeChild(this.renderer.domElement);\n }\n }\n}","/**\n * 셰이더 파일 로딩 및 관리 클래스\n */\nexport class ShaderManager {\n private vertexShaderSource: string | null = null;\n private fragmentShaderSource: string | null = null;\n\n /**\n * 셰이더 파일들을 비동기로 로드\n * @param vertexPath 버텍스 셰이더 파일 경로\n * @param fragmentPath 프래그먼트 셰이더 파일 경로\n * @returns 로드된 셰이더 소스 코드\n */\n public async loadShaders(\n vertexPath: string,\n fragmentPath: string\n ): Promise<{ vertex: string; fragment: string }> {\n try {\n const [vertexResponse, fragmentResponse] = await Promise.all([\n fetch(vertexPath),\n fetch(fragmentPath),\n ]);\n\n if (!vertexResponse.ok) {\n throw new Error(`버텍스 셰이더 로드 실패: ${vertexResponse.statusText}`);\n }\n if (!fragmentResponse.ok) {\n throw new Error(`프래그먼트 셰이더 로드 실패: ${fragmentResponse.statusText}`);\n }\n\n this.vertexShaderSource = await vertexResponse.text();\n this.fragmentShaderSource = await fragmentResponse.text();\n\n return {\n vertex: this.vertexShaderSource,\n fragment: this.fragmentShaderSource,\n };\n } catch (error) {\n console.error('셰이더 로드 실패:', error);\n throw new Error('셰이더 로딩에 실패했습니다');\n }\n }\n\n /**\n * 버텍스 셰이더 소스 코드 반환\n */\n public getVertexShader(): string {\n if (!this.vertexShaderSource) {\n throw new Error('버텍스 셰이더가 로드되지 않았습니다');\n }\n return this.vertexShaderSource;\n }\n\n /**\n * 프래그먼트 셰이더 소스 코드 반환\n */\n public getFragmentShader(): string {\n if (!this.fragmentShaderSource) {\n throw new Error('프래그먼트 셰이더가 로드되지 않았습니다');\n }\n return this.fragmentShaderSource;\n }\n}","import { EasingFunction } from '../types';\n\ntype EasingFunc = (t: number) => number;\n\n/**\n * 이징 함수 구현 맵\n */\nconst easingFunctions: Record = {\n linear: (t) => t,\n\n easeIn: (t) => t * t,\n easeOut: (t) => t * (2 - t),\n easeInOut: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),\n\n easeInQuad: (t) => t * t,\n easeOutQuad: (t) => t * (2 - t),\n};\n\n/**\n * 진행도에 이징 함수를 적용\n * @param progress 진행도 (0.0 - 1.0)\n * @param easingType 적용할 이징 함수 타입\n * @returns 이징이 적용된 진행도 (0.0 - 1.0)\n */\nexport const applyEasing = (\n progress: number,\n easingType: EasingFunction\n): number => {\n const clampedProgress = Math.max(0, Math.min(1, progress));\n return easingFunctions[easingType](clampedProgress);\n};","import { DistortionArea, Point } from '../types';\nimport { applyEasing } from '../utils/easing';\n\n/**\n * 애니메이션 루프 관리 클래스\n */\nexport class AnimationLoop {\n /**\n * 영역들의 드래그 벡터를 현재 진행도에 따라 업데이트\n * @param areas 왜곡 영역 배열\n * @returns 업데이트된 영역 배열\n */\n public static updateAreaDragVectors(\n areas: DistortionArea[]\n ): DistortionArea[] {\n return areas.map((area) => {\n const { progress, movement } = area;\n\n // 이징 적용\n const easedProgress = applyEasing(progress, movement.easing);\n\n // 벡터 간 보간\n let dragVector: Point;\n\n if (easedProgress < 0.5) {\n // 0.0 -> 0.5: 0에서 vectorA로 보간\n const t = easedProgress * 2;\n dragVector = {\n x: movement.vectorA.x * t,\n y: movement.vectorA.y * t,\n };\n } else {\n // 0.5 -> 1.0: vectorA에서 0으로 보간\n const t = (easedProgress - 0.5) * 2;\n dragVector = {\n x: movement.vectorA.x * (1 - t),\n y: movement.vectorA.y * (1 - t),\n };\n }\n\n return {\n ...area,\n dragVector,\n };\n });\n }\n\n /**\n * 모든 영역의 진행도를 델타 타임만큼 업데이트\n * @param areas 왜곡 영역 배열\n * @param deltaTime 델타 타임 (초)\n * @returns 업데이트된 영역 배열\n */\n public static updateProgress(\n areas: DistortionArea[],\n deltaTime: number\n ): DistortionArea[] {\n return areas.map((area) => {\n let newProgress = area.progress + deltaTime / area.movement.duration;\n newProgress %= 1.0; // 루프\n\n return {\n ...area,\n progress: newProgress,\n };\n });\n }\n}","import { useEffect, useRef } from 'react';\n\n/**\n * requestAnimationFrame을 사용한 애니메이션 루프 훅\n * @param callback 매 프레임마다 호출될 콜백 (deltaTime을 인자로 받음)\n * @param isPlaying 애니메이션 재생 여부\n */\nexport const useAnimationFrame = (\n callback: (deltaTime: number) => void,\n isPlaying: boolean = true\n) => {\n const requestRef = useRef(undefined);\n const previousTimeRef = useRef(undefined);\n\n useEffect(() => {\n if (!isPlaying) return;\n\n const animate = (time: number) => {\n if (previousTimeRef.current !== undefined) {\n const deltaTime = (time - previousTimeRef.current) / 1000; // 밀리초를 초로 변환\n callback(deltaTime);\n }\n previousTimeRef.current = time;\n requestRef.current = requestAnimationFrame(animate);\n };\n\n requestRef.current = requestAnimationFrame(animate);\n\n return () => {\n if (requestRef.current) {\n cancelAnimationFrame(requestRef.current);\n }\n };\n }, [callback, isPlaying]);\n};","/**\n * 셰이더 관련 설정\n */\nexport const SHADER_CONFIG = {\n /** 최대 영역 개수 */\n MAX_AREAS: 8,\n /** 최대 포인트 개수 (8영역 × 4포인트) */\n MAX_POINTS: 32,\n /** 최대 드래그 벡터 개수 */\n MAX_DRAG_VECTORS: 8,\n /** 최대 강도 배열 크기 */\n MAX_STRENGTHS: 8,\n} as const;\n\n/**\n * 애니메이션 관련 설정\n */\nexport const ANIMATION_CONFIG = {\n /** 목표 FPS */\n TARGET_FPS: 60,\n /** 델타 타임 (약 16.67ms) */\n DELTA_TIME: 1 / 60,\n} as const;\n\n/**\n * 기본 영역 설정값\n */\nexport const DEFAULT_AREA = {\n /** 기본 왜곡 강도 */\n DISTORTION_STRENGTH: 0.5,\n /** 기본 애니메이션 지속 시간 (초) */\n DURATION: 2.0,\n /** 기본 이징 함수 */\n EASING: 'easeInOut' as const,\n /** 기본 벡터 A */\n VECTOR_A: { x: 0.1, y: 0.1 },\n /** 기본 벡터 B */\n VECTOR_B: { x: -0.1, y: -0.1 },\n} as const;"],"mappings":"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","names":["import_react","THREE"]} \ No newline at end of file diff --git a/dist/index.mjs b/dist/index.mjs index 7ba02f8..61b018c 100644 --- a/dist/index.mjs +++ b/dist/index.mjs @@ -358,10 +358,11 @@ var ImageDistortion = ({ }, [currentAreas, isReady]); const animationCallback = useCallback((deltaTime) => { if (!isReady) return; - const updatedAreas = AnimationLoop.updateProgress(currentAreas, deltaTime); - const areasWithVectors = AnimationLoop.updateAreaDragVectors(updatedAreas); - setCurrentAreas(areasWithVectors); - }, [currentAreas, isReady]); + setCurrentAreas((prevAreas) => { + const updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime); + return AnimationLoop.updateAreaDragVectors(updatedAreas); + }); + }, [isReady]); useAnimationFrame(animationCallback, isPlaying); return /* @__PURE__ */ jsx( "div", diff --git a/dist/index.mjs.map b/dist/index.mjs.map index f9a0d14..bc3d6ff 100644 --- a/dist/index.mjs.map +++ b/dist/index.mjs.map @@ -1 +1 @@ -{"version":3,"sources":["../src/components/ImageDistortion.tsx","../src/engine/ThreeScene.ts","../src/engine/ShaderManager.ts","../src/utils/easing.ts","../src/engine/AnimationLoop.ts","../src/hooks/useAnimationFrame.ts","../src/utils/constants.ts"],"sourcesContent":["import React, { useEffect, useRef, useState, useCallback } from 'react';\nimport * as THREE from 'three';\nimport { DistortionArea } from '../types';\nimport { ThreeScene } from '../engine/ThreeScene';\nimport { ShaderManager } from '../engine/ShaderManager';\nimport { AnimationLoop } from '../engine/AnimationLoop';\nimport { useAnimationFrame } from '../hooks/useAnimationFrame';\nimport { SHADER_CONFIG } from '../utils/constants';\n\n/**\n * ImageDistortion 컴포넌트 Props\n */\nexport interface ImageDistortionProps {\n /** 이미지 소스 URL */\n imageSrc: string;\n /** 왜곡 영역 배열 */\n areas: DistortionArea[];\n /** 버텍스 셰이더 경로 (선택사항) */\n vertexShaderPath?: string;\n /** 프래그먼트 셰이더 경로 (선택사항) */\n fragmentShaderPath?: string;\n /** 애니메이션 재생 여부 */\n isPlaying?: boolean;\n /** 컨테이너 스타일 */\n style?: React.CSSProperties;\n /** 컨테이너 클래스명 */\n className?: string;\n}\n\n/**\n * GPU 가속 이미지 왜곡 컴포넌트\n * Three.js와 GLSL 셰이더를 사용하여 실시간 이미지 왜곡 효과를 제공합니다.\n */\nexport const ImageDistortion: React.FC = ({\n imageSrc,\n areas,\n vertexShaderPath,\n fragmentShaderPath,\n isPlaying = true,\n style,\n className,\n}) => {\n const containerRef = useRef(null);\n const sceneRef = useRef(null);\n const shaderManagerRef = useRef(new ShaderManager());\n const textureRef = useRef(null);\n\n const [isReady, setIsReady] = useState(false);\n const [currentAreas, setCurrentAreas] = useState(areas);\n\n // 영역 변경 시 상태 업데이트\n useEffect(() => {\n setCurrentAreas(areas);\n }, [areas]);\n\n // Three.js 씬 초기화\n useEffect(() => {\n if (!containerRef.current) return;\n\n const scene = new ThreeScene(containerRef.current);\n sceneRef.current = scene;\n\n // 셰이더 로드\n const vertPath = vertexShaderPath || '/shaders/distortion.vert.glsl';\n const fragPath = fragmentShaderPath || '/shaders/distortion.frag.glsl';\n\n shaderManagerRef.current\n .loadShaders(vertPath, fragPath)\n .then(({ vertex, fragment }) => {\n scene.setShaderMaterial(vertex, fragment);\n setIsReady(true);\n })\n .catch((error) => {\n console.error('셰이더 로드 실패:', error);\n });\n\n return () => {\n scene.dispose();\n if (textureRef.current) {\n textureRef.current.dispose();\n }\n };\n }, [vertexShaderPath, fragmentShaderPath]);\n\n // 이미지 텍스처 로드\n useEffect(() => {\n if (!imageSrc || !isReady) return;\n\n const loader = new THREE.TextureLoader();\n loader.load(\n imageSrc,\n (texture) => {\n textureRef.current = texture;\n if (sceneRef.current) {\n sceneRef.current.updateUniforms({\n u_texture: { value: texture },\n });\n sceneRef.current.render();\n }\n },\n undefined,\n (error) => {\n console.error('이미지 로드 실패:', error);\n }\n );\n\n return () => {\n if (textureRef.current) {\n textureRef.current.dispose();\n textureRef.current = null;\n }\n };\n }, [imageSrc, isReady]);\n\n // 셰이더 유니폼 업데이트\n useEffect(() => {\n if (!sceneRef.current || !isReady) return;\n\n // 포인트 배열 생성\n const points = new Float32Array(SHADER_CONFIG.MAX_POINTS * 2);\n currentAreas.forEach((area, areaIndex) => {\n area.basePoints.forEach((point, pointIndex) => {\n const index = (areaIndex * 4 + pointIndex) * 2;\n points[index] = point.x;\n points[index + 1] = point.y;\n });\n });\n\n // 드래그 벡터 배열 생성\n const dragVectors = new Float32Array(SHADER_CONFIG.MAX_DRAG_VECTORS * 2);\n currentAreas.forEach((area, index) => {\n const baseIndex = index * 2;\n dragVectors[baseIndex] = area.dragVector.x;\n dragVectors[baseIndex + 1] = area.dragVector.y;\n });\n\n // 강도 배열 생성\n const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS);\n currentAreas.forEach((area, index) => {\n strengths[index] = area.distortionStrength;\n });\n\n sceneRef.current.updateUniforms({\n u_numAreas: { value: currentAreas.length },\n u_points: { value: points },\n u_dragVectors: { value: dragVectors },\n u_distortionStrengths: { value: strengths },\n });\n\n sceneRef.current.render();\n }, [currentAreas, isReady]);\n\n // 애니메이션 루프\n const animationCallback = useCallback((deltaTime: number) => {\n if (!isReady) return;\n\n // 진행도 업데이트\n const updatedAreas = AnimationLoop.updateProgress(currentAreas, deltaTime);\n\n // 드래그 벡터 업데이트\n const areasWithVectors = AnimationLoop.updateAreaDragVectors(updatedAreas);\n\n setCurrentAreas(areasWithVectors);\n }, [currentAreas, isReady]);\n\n useAnimationFrame(animationCallback, isPlaying);\n\n return (\n \n );\n};","import * as THREE from 'three';\nimport { ShaderUniforms } from '@/types';\n\n/**\n * Three.js 씬 관리 클래스\n */\nexport class ThreeScene {\n private scene: THREE.Scene;\n private camera: THREE.OrthographicCamera;\n private renderer: THREE.WebGLRenderer;\n private mesh: THREE.Mesh | null = null;\n private uniforms: ShaderUniforms;\n\n constructor(private container: HTMLElement) {\n // 씬 생성\n this.scene = new THREE.Scene();\n\n // 2D용 직교 카메라 설정\n this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n // 렌더러 설정\n this.renderer = new THREE.WebGLRenderer({\n antialias: true,\n alpha: false,\n });\n this.renderer.setPixelRatio(window.devicePixelRatio);\n this.container.appendChild(this.renderer.domElement);\n\n // 유니폼 초기화\n this.uniforms = {\n u_resolution: { value: new THREE.Vector2() },\n u_texture: { value: null },\n u_points: { value: new Float32Array(64) }, // 32포인트 × 2(x,y)\n u_numAreas: { value: 0 },\n u_dragVectors: { value: new Float32Array(16) }, // 8벡터 × 2(x,y)\n u_distortionStrengths: { value: new Float32Array(8) },\n };\n\n this.handleResize();\n window.addEventListener('resize', this.handleResize);\n }\n\n /**\n * 윈도우 리사이즈 핸들러\n */\n private handleResize = () => {\n const width = this.container.clientWidth;\n const height = this.container.clientHeight;\n\n this.renderer.setSize(width, height);\n this.uniforms.u_resolution.value.set(width, height);\n\n if (this.mesh) {\n this.render();\n }\n };\n\n /**\n * 셰이더 머티리얼 설정\n * @param vertexShader 버텍스 셰이더 소스\n * @param fragmentShader 프래그먼트 셰이더 소스\n */\n public setShaderMaterial(vertexShader: string, fragmentShader: string) {\n const geometry = new THREE.PlaneGeometry(2, 2);\n const material = new THREE.ShaderMaterial({\n uniforms: this.uniforms,\n vertexShader,\n fragmentShader,\n });\n\n if (this.mesh) {\n this.scene.remove(this.mesh);\n }\n\n this.mesh = new THREE.Mesh(geometry, material);\n this.scene.add(this.mesh);\n }\n\n /**\n * 유니폼 값 업데이트\n * @param updates 업데이트할 유니폼 값들\n */\n public updateUniforms(updates: Partial) {\n Object.keys(updates).forEach((key) => {\n const uniformKey = key as keyof ShaderUniforms;\n this.uniforms[uniformKey].value = updates[uniformKey]!.value;\n });\n }\n\n /**\n * 씬 렌더링\n */\n public render() {\n this.renderer.render(this.scene, this.camera);\n }\n\n /**\n * 리소스 정리\n */\n public dispose() {\n window.removeEventListener('resize', this.handleResize);\n this.renderer.dispose();\n if (this.mesh) {\n this.mesh.geometry.dispose();\n (this.mesh.material as THREE.Material).dispose();\n }\n if (this.container.contains(this.renderer.domElement)) {\n this.container.removeChild(this.renderer.domElement);\n }\n }\n}","/**\n * 셰이더 파일 로딩 및 관리 클래스\n */\nexport class ShaderManager {\n private vertexShaderSource: string | null = null;\n private fragmentShaderSource: string | null = null;\n\n /**\n * 셰이더 파일들을 비동기로 로드\n * @param vertexPath 버텍스 셰이더 파일 경로\n * @param fragmentPath 프래그먼트 셰이더 파일 경로\n * @returns 로드된 셰이더 소스 코드\n */\n public async loadShaders(\n vertexPath: string,\n fragmentPath: string\n ): Promise<{ vertex: string; fragment: string }> {\n try {\n const [vertexResponse, fragmentResponse] = await Promise.all([\n fetch(vertexPath),\n fetch(fragmentPath),\n ]);\n\n if (!vertexResponse.ok) {\n throw new Error(`버텍스 셰이더 로드 실패: ${vertexResponse.statusText}`);\n }\n if (!fragmentResponse.ok) {\n throw new Error(`프래그먼트 셰이더 로드 실패: ${fragmentResponse.statusText}`);\n }\n\n this.vertexShaderSource = await vertexResponse.text();\n this.fragmentShaderSource = await fragmentResponse.text();\n\n return {\n vertex: this.vertexShaderSource,\n fragment: this.fragmentShaderSource,\n };\n } catch (error) {\n console.error('셰이더 로드 실패:', error);\n throw new Error('셰이더 로딩에 실패했습니다');\n }\n }\n\n /**\n * 버텍스 셰이더 소스 코드 반환\n */\n public getVertexShader(): string {\n if (!this.vertexShaderSource) {\n throw new Error('버텍스 셰이더가 로드되지 않았습니다');\n }\n return this.vertexShaderSource;\n }\n\n /**\n * 프래그먼트 셰이더 소스 코드 반환\n */\n public getFragmentShader(): string {\n if (!this.fragmentShaderSource) {\n throw new Error('프래그먼트 셰이더가 로드되지 않았습니다');\n }\n return this.fragmentShaderSource;\n }\n}","import { EasingFunction } from '../types';\n\ntype EasingFunc = (t: number) => number;\n\n/**\n * 이징 함수 구현 맵\n */\nconst easingFunctions: Record = {\n linear: (t) => t,\n\n easeIn: (t) => t * t,\n easeOut: (t) => t * (2 - t),\n easeInOut: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),\n\n easeInQuad: (t) => t * t,\n easeOutQuad: (t) => t * (2 - t),\n};\n\n/**\n * 진행도에 이징 함수를 적용\n * @param progress 진행도 (0.0 - 1.0)\n * @param easingType 적용할 이징 함수 타입\n * @returns 이징이 적용된 진행도 (0.0 - 1.0)\n */\nexport const applyEasing = (\n progress: number,\n easingType: EasingFunction\n): number => {\n const clampedProgress = Math.max(0, Math.min(1, progress));\n return easingFunctions[easingType](clampedProgress);\n};","import { DistortionArea, Point } from '../types';\nimport { applyEasing } from '../utils/easing';\n\n/**\n * 애니메이션 루프 관리 클래스\n */\nexport class AnimationLoop {\n /**\n * 영역들의 드래그 벡터를 현재 진행도에 따라 업데이트\n * @param areas 왜곡 영역 배열\n * @returns 업데이트된 영역 배열\n */\n public static updateAreaDragVectors(\n areas: DistortionArea[]\n ): DistortionArea[] {\n return areas.map((area) => {\n const { progress, movement } = area;\n\n // 이징 적용\n const easedProgress = applyEasing(progress, movement.easing);\n\n // 벡터 간 보간\n let dragVector: Point;\n\n if (easedProgress < 0.5) {\n // 0.0 -> 0.5: 0에서 vectorA로 보간\n const t = easedProgress * 2;\n dragVector = {\n x: movement.vectorA.x * t,\n y: movement.vectorA.y * t,\n };\n } else {\n // 0.5 -> 1.0: vectorA에서 0으로 보간\n const t = (easedProgress - 0.5) * 2;\n dragVector = {\n x: movement.vectorA.x * (1 - t),\n y: movement.vectorA.y * (1 - t),\n };\n }\n\n return {\n ...area,\n dragVector,\n };\n });\n }\n\n /**\n * 모든 영역의 진행도를 델타 타임만큼 업데이트\n * @param areas 왜곡 영역 배열\n * @param deltaTime 델타 타임 (초)\n * @returns 업데이트된 영역 배열\n */\n public static updateProgress(\n areas: DistortionArea[],\n deltaTime: number\n ): DistortionArea[] {\n return areas.map((area) => {\n let newProgress = area.progress + deltaTime / area.movement.duration;\n newProgress %= 1.0; // 루프\n\n return {\n ...area,\n progress: newProgress,\n };\n });\n }\n}","import { useEffect, useRef } from 'react';\n\n/**\n * requestAnimationFrame을 사용한 애니메이션 루프 훅\n * @param callback 매 프레임마다 호출될 콜백 (deltaTime을 인자로 받음)\n * @param isPlaying 애니메이션 재생 여부\n */\nexport const useAnimationFrame = (\n callback: (deltaTime: number) => void,\n isPlaying: boolean = true\n) => {\n const requestRef = useRef(undefined);\n const previousTimeRef = useRef(undefined);\n\n useEffect(() => {\n if (!isPlaying) return;\n\n const animate = (time: number) => {\n if (previousTimeRef.current !== undefined) {\n const deltaTime = (time - previousTimeRef.current) / 1000; // 밀리초를 초로 변환\n callback(deltaTime);\n }\n previousTimeRef.current = time;\n requestRef.current = requestAnimationFrame(animate);\n };\n\n requestRef.current = requestAnimationFrame(animate);\n\n return () => {\n if (requestRef.current) {\n cancelAnimationFrame(requestRef.current);\n }\n };\n }, [callback, isPlaying]);\n};","/**\n * 셰이더 관련 설정\n */\nexport const SHADER_CONFIG = {\n /** 최대 영역 개수 */\n MAX_AREAS: 8,\n /** 최대 포인트 개수 (8영역 × 4포인트) */\n MAX_POINTS: 32,\n /** 최대 드래그 벡터 개수 */\n MAX_DRAG_VECTORS: 8,\n /** 최대 강도 배열 크기 */\n MAX_STRENGTHS: 8,\n} as const;\n\n/**\n * 애니메이션 관련 설정\n */\nexport const ANIMATION_CONFIG = {\n /** 목표 FPS */\n TARGET_FPS: 60,\n /** 델타 타임 (약 16.67ms) */\n DELTA_TIME: 1 / 60,\n} as const;\n\n/**\n * 기본 영역 설정값\n */\nexport const DEFAULT_AREA = {\n /** 기본 왜곡 강도 */\n DISTORTION_STRENGTH: 0.5,\n /** 기본 애니메이션 지속 시간 (초) */\n DURATION: 2.0,\n /** 기본 이징 함수 */\n EASING: 'easeInOut' as const,\n /** 기본 벡터 A */\n VECTOR_A: { x: 0.1, y: 0.1 },\n /** 기본 벡터 B */\n VECTOR_B: { x: -0.1, y: -0.1 },\n} as const;"],"mappings":"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","names":["useEffect","useRef","THREE","useRef","useEffect"]} \ No newline at end of file +{"version":3,"sources":["../src/components/ImageDistortion.tsx","../src/engine/ThreeScene.ts","../src/engine/ShaderManager.ts","../src/utils/easing.ts","../src/engine/AnimationLoop.ts","../src/hooks/useAnimationFrame.ts","../src/utils/constants.ts"],"sourcesContent":["import React, { useEffect, useRef, useState, useCallback } from 'react';\nimport * as THREE from 'three';\nimport { DistortionArea } from '../types';\nimport { ThreeScene } from '../engine/ThreeScene';\nimport { ShaderManager } from '../engine/ShaderManager';\nimport { AnimationLoop } from '../engine/AnimationLoop';\nimport { useAnimationFrame } from '../hooks/useAnimationFrame';\nimport { SHADER_CONFIG } from '../utils/constants';\n\n/**\n * ImageDistortion 컴포넌트 Props\n */\nexport interface ImageDistortionProps {\n /** 이미지 소스 URL */\n imageSrc: string;\n /** 왜곡 영역 배열 */\n areas: DistortionArea[];\n /** 버텍스 셰이더 경로 (선택사항) */\n vertexShaderPath?: string;\n /** 프래그먼트 셰이더 경로 (선택사항) */\n fragmentShaderPath?: string;\n /** 애니메이션 재생 여부 */\n isPlaying?: boolean;\n /** 컨테이너 스타일 */\n style?: React.CSSProperties;\n /** 컨테이너 클래스명 */\n className?: string;\n}\n\n/**\n * GPU 가속 이미지 왜곡 컴포넌트\n * Three.js와 GLSL 셰이더를 사용하여 실시간 이미지 왜곡 효과를 제공합니다.\n */\nexport const ImageDistortion: React.FC = ({\n imageSrc,\n areas,\n vertexShaderPath,\n fragmentShaderPath,\n isPlaying = true,\n style,\n className,\n}) => {\n const containerRef = useRef(null);\n const sceneRef = useRef(null);\n const shaderManagerRef = useRef(new ShaderManager());\n const textureRef = useRef(null);\n\n const [isReady, setIsReady] = useState(false);\n const [currentAreas, setCurrentAreas] = useState(areas);\n\n // 영역 변경 시 상태 업데이트\n useEffect(() => {\n setCurrentAreas(areas);\n }, [areas]);\n\n // Three.js 씬 초기화\n useEffect(() => {\n if (!containerRef.current) return;\n\n const scene = new ThreeScene(containerRef.current);\n sceneRef.current = scene;\n\n // 셰이더 로드\n const vertPath = vertexShaderPath || '/shaders/distortion.vert.glsl';\n const fragPath = fragmentShaderPath || '/shaders/distortion.frag.glsl';\n\n shaderManagerRef.current\n .loadShaders(vertPath, fragPath)\n .then(({ vertex, fragment }) => {\n scene.setShaderMaterial(vertex, fragment);\n setIsReady(true);\n })\n .catch((error) => {\n console.error('셰이더 로드 실패:', error);\n });\n\n return () => {\n scene.dispose();\n if (textureRef.current) {\n textureRef.current.dispose();\n }\n };\n }, [vertexShaderPath, fragmentShaderPath]);\n\n // 이미지 텍스처 로드\n useEffect(() => {\n if (!imageSrc || !isReady) return;\n\n const loader = new THREE.TextureLoader();\n loader.load(\n imageSrc,\n (texture) => {\n textureRef.current = texture;\n if (sceneRef.current) {\n sceneRef.current.updateUniforms({\n u_texture: { value: texture },\n });\n sceneRef.current.render();\n }\n },\n undefined,\n (error) => {\n console.error('이미지 로드 실패:', error);\n }\n );\n\n return () => {\n if (textureRef.current) {\n textureRef.current.dispose();\n textureRef.current = null;\n }\n };\n }, [imageSrc, isReady]);\n\n // 셰이더 유니폼 업데이트\n useEffect(() => {\n if (!sceneRef.current || !isReady) return;\n\n // 포인트 배열 생성\n const points = new Float32Array(SHADER_CONFIG.MAX_POINTS * 2);\n currentAreas.forEach((area, areaIndex) => {\n area.basePoints.forEach((point, pointIndex) => {\n const index = (areaIndex * 4 + pointIndex) * 2;\n points[index] = point.x;\n points[index + 1] = point.y;\n });\n });\n\n // 드래그 벡터 배열 생성\n const dragVectors = new Float32Array(SHADER_CONFIG.MAX_DRAG_VECTORS * 2);\n currentAreas.forEach((area, index) => {\n const baseIndex = index * 2;\n dragVectors[baseIndex] = area.dragVector.x;\n dragVectors[baseIndex + 1] = area.dragVector.y;\n });\n\n // 강도 배열 생성\n const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS);\n currentAreas.forEach((area, index) => {\n strengths[index] = area.distortionStrength;\n });\n\n sceneRef.current.updateUniforms({\n u_numAreas: { value: currentAreas.length },\n u_points: { value: points },\n u_dragVectors: { value: dragVectors },\n u_distortionStrengths: { value: strengths },\n });\n\n sceneRef.current.render();\n }, [currentAreas, isReady]);\n\n // 애니메이션 루프\n const animationCallback = useCallback((deltaTime: number) => {\n if (!isReady) return;\n\n setCurrentAreas((prevAreas) => {\n // 진행도 업데이트\n const updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);\n // 드래그 벡터 업데이트\n return AnimationLoop.updateAreaDragVectors(updatedAreas);\n });\n }, [isReady]);\n\n useAnimationFrame(animationCallback, isPlaying);\n\n return (\n \n );\n};","import * as THREE from 'three';\nimport { ShaderUniforms } from '@/types';\n\n/**\n * Three.js 씬 관리 클래스\n */\nexport class ThreeScene {\n private scene: THREE.Scene;\n private camera: THREE.OrthographicCamera;\n private renderer: THREE.WebGLRenderer;\n private mesh: THREE.Mesh | null = null;\n private uniforms: ShaderUniforms;\n\n constructor(private container: HTMLElement) {\n // 씬 생성\n this.scene = new THREE.Scene();\n\n // 2D용 직교 카메라 설정\n this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);\n\n // 렌더러 설정\n this.renderer = new THREE.WebGLRenderer({\n antialias: true,\n alpha: false,\n });\n this.renderer.setPixelRatio(window.devicePixelRatio);\n this.container.appendChild(this.renderer.domElement);\n\n // 유니폼 초기화\n this.uniforms = {\n u_resolution: { value: new THREE.Vector2() },\n u_texture: { value: null },\n u_points: { value: new Float32Array(64) }, // 32포인트 × 2(x,y)\n u_numAreas: { value: 0 },\n u_dragVectors: { value: new Float32Array(16) }, // 8벡터 × 2(x,y)\n u_distortionStrengths: { value: new Float32Array(8) },\n };\n\n this.handleResize();\n window.addEventListener('resize', this.handleResize);\n }\n\n /**\n * 윈도우 리사이즈 핸들러\n */\n private handleResize = () => {\n const width = this.container.clientWidth;\n const height = this.container.clientHeight;\n\n this.renderer.setSize(width, height);\n this.uniforms.u_resolution.value.set(width, height);\n\n if (this.mesh) {\n this.render();\n }\n };\n\n /**\n * 셰이더 머티리얼 설정\n * @param vertexShader 버텍스 셰이더 소스\n * @param fragmentShader 프래그먼트 셰이더 소스\n */\n public setShaderMaterial(vertexShader: string, fragmentShader: string) {\n const geometry = new THREE.PlaneGeometry(2, 2);\n const material = new THREE.ShaderMaterial({\n uniforms: this.uniforms,\n vertexShader,\n fragmentShader,\n });\n\n if (this.mesh) {\n this.scene.remove(this.mesh);\n }\n\n this.mesh = new THREE.Mesh(geometry, material);\n this.scene.add(this.mesh);\n }\n\n /**\n * 유니폼 값 업데이트\n * @param updates 업데이트할 유니폼 값들\n */\n public updateUniforms(updates: Partial) {\n Object.keys(updates).forEach((key) => {\n const uniformKey = key as keyof ShaderUniforms;\n this.uniforms[uniformKey].value = updates[uniformKey]!.value;\n });\n }\n\n /**\n * 씬 렌더링\n */\n public render() {\n this.renderer.render(this.scene, this.camera);\n }\n\n /**\n * 리소스 정리\n */\n public dispose() {\n window.removeEventListener('resize', this.handleResize);\n this.renderer.dispose();\n if (this.mesh) {\n this.mesh.geometry.dispose();\n (this.mesh.material as THREE.Material).dispose();\n }\n if (this.container.contains(this.renderer.domElement)) {\n this.container.removeChild(this.renderer.domElement);\n }\n }\n}","/**\n * 셰이더 파일 로딩 및 관리 클래스\n */\nexport class ShaderManager {\n private vertexShaderSource: string | null = null;\n private fragmentShaderSource: string | null = null;\n\n /**\n * 셰이더 파일들을 비동기로 로드\n * @param vertexPath 버텍스 셰이더 파일 경로\n * @param fragmentPath 프래그먼트 셰이더 파일 경로\n * @returns 로드된 셰이더 소스 코드\n */\n public async loadShaders(\n vertexPath: string,\n fragmentPath: string\n ): Promise<{ vertex: string; fragment: string }> {\n try {\n const [vertexResponse, fragmentResponse] = await Promise.all([\n fetch(vertexPath),\n fetch(fragmentPath),\n ]);\n\n if (!vertexResponse.ok) {\n throw new Error(`버텍스 셰이더 로드 실패: ${vertexResponse.statusText}`);\n }\n if (!fragmentResponse.ok) {\n throw new Error(`프래그먼트 셰이더 로드 실패: ${fragmentResponse.statusText}`);\n }\n\n this.vertexShaderSource = await vertexResponse.text();\n this.fragmentShaderSource = await fragmentResponse.text();\n\n return {\n vertex: this.vertexShaderSource,\n fragment: this.fragmentShaderSource,\n };\n } catch (error) {\n console.error('셰이더 로드 실패:', error);\n throw new Error('셰이더 로딩에 실패했습니다');\n }\n }\n\n /**\n * 버텍스 셰이더 소스 코드 반환\n */\n public getVertexShader(): string {\n if (!this.vertexShaderSource) {\n throw new Error('버텍스 셰이더가 로드되지 않았습니다');\n }\n return this.vertexShaderSource;\n }\n\n /**\n * 프래그먼트 셰이더 소스 코드 반환\n */\n public getFragmentShader(): string {\n if (!this.fragmentShaderSource) {\n throw new Error('프래그먼트 셰이더가 로드되지 않았습니다');\n }\n return this.fragmentShaderSource;\n }\n}","import { EasingFunction } from '../types';\n\ntype EasingFunc = (t: number) => number;\n\n/**\n * 이징 함수 구현 맵\n */\nconst easingFunctions: Record = {\n linear: (t) => t,\n\n easeIn: (t) => t * t,\n easeOut: (t) => t * (2 - t),\n easeInOut: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),\n\n easeInQuad: (t) => t * t,\n easeOutQuad: (t) => t * (2 - t),\n};\n\n/**\n * 진행도에 이징 함수를 적용\n * @param progress 진행도 (0.0 - 1.0)\n * @param easingType 적용할 이징 함수 타입\n * @returns 이징이 적용된 진행도 (0.0 - 1.0)\n */\nexport const applyEasing = (\n progress: number,\n easingType: EasingFunction\n): number => {\n const clampedProgress = Math.max(0, Math.min(1, progress));\n return easingFunctions[easingType](clampedProgress);\n};","import { DistortionArea, Point } from '../types';\nimport { applyEasing } from '../utils/easing';\n\n/**\n * 애니메이션 루프 관리 클래스\n */\nexport class AnimationLoop {\n /**\n * 영역들의 드래그 벡터를 현재 진행도에 따라 업데이트\n * @param areas 왜곡 영역 배열\n * @returns 업데이트된 영역 배열\n */\n public static updateAreaDragVectors(\n areas: DistortionArea[]\n ): DistortionArea[] {\n return areas.map((area) => {\n const { progress, movement } = area;\n\n // 이징 적용\n const easedProgress = applyEasing(progress, movement.easing);\n\n // 벡터 간 보간\n let dragVector: Point;\n\n if (easedProgress < 0.5) {\n // 0.0 -> 0.5: 0에서 vectorA로 보간\n const t = easedProgress * 2;\n dragVector = {\n x: movement.vectorA.x * t,\n y: movement.vectorA.y * t,\n };\n } else {\n // 0.5 -> 1.0: vectorA에서 0으로 보간\n const t = (easedProgress - 0.5) * 2;\n dragVector = {\n x: movement.vectorA.x * (1 - t),\n y: movement.vectorA.y * (1 - t),\n };\n }\n\n return {\n ...area,\n dragVector,\n };\n });\n }\n\n /**\n * 모든 영역의 진행도를 델타 타임만큼 업데이트\n * @param areas 왜곡 영역 배열\n * @param deltaTime 델타 타임 (초)\n * @returns 업데이트된 영역 배열\n */\n public static updateProgress(\n areas: DistortionArea[],\n deltaTime: number\n ): DistortionArea[] {\n return areas.map((area) => {\n let newProgress = area.progress + deltaTime / area.movement.duration;\n newProgress %= 1.0; // 루프\n\n return {\n ...area,\n progress: newProgress,\n };\n });\n }\n}","import { useEffect, useRef } from 'react';\n\n/**\n * requestAnimationFrame을 사용한 애니메이션 루프 훅\n * @param callback 매 프레임마다 호출될 콜백 (deltaTime을 인자로 받음)\n * @param isPlaying 애니메이션 재생 여부\n */\nexport const useAnimationFrame = (\n callback: (deltaTime: number) => void,\n isPlaying: boolean = true\n) => {\n const requestRef = useRef(undefined);\n const previousTimeRef = useRef(undefined);\n\n useEffect(() => {\n if (!isPlaying) return;\n\n const animate = (time: number) => {\n if (previousTimeRef.current !== undefined) {\n const deltaTime = (time - previousTimeRef.current) / 1000; // 밀리초를 초로 변환\n callback(deltaTime);\n }\n previousTimeRef.current = time;\n requestRef.current = requestAnimationFrame(animate);\n };\n\n requestRef.current = requestAnimationFrame(animate);\n\n return () => {\n if (requestRef.current) {\n cancelAnimationFrame(requestRef.current);\n }\n };\n }, [callback, isPlaying]);\n};","/**\n * 셰이더 관련 설정\n */\nexport const SHADER_CONFIG = {\n /** 최대 영역 개수 */\n MAX_AREAS: 8,\n /** 최대 포인트 개수 (8영역 × 4포인트) */\n MAX_POINTS: 32,\n /** 최대 드래그 벡터 개수 */\n MAX_DRAG_VECTORS: 8,\n /** 최대 강도 배열 크기 */\n MAX_STRENGTHS: 8,\n} as const;\n\n/**\n * 애니메이션 관련 설정\n */\nexport const ANIMATION_CONFIG = {\n /** 목표 FPS */\n TARGET_FPS: 60,\n /** 델타 타임 (약 16.67ms) */\n DELTA_TIME: 1 / 60,\n} as const;\n\n/**\n * 기본 영역 설정값\n */\nexport const DEFAULT_AREA = {\n /** 기본 왜곡 강도 */\n DISTORTION_STRENGTH: 0.5,\n /** 기본 애니메이션 지속 시간 (초) */\n DURATION: 2.0,\n /** 기본 이징 함수 */\n EASING: 'easeInOut' as const,\n /** 기본 벡터 A */\n VECTOR_A: { x: 0.1, y: 0.1 },\n /** 기본 벡터 B */\n VECTOR_B: { x: -0.1, y: -0.1 },\n} as const;"],"mappings":"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","names":["useEffect","useRef","THREE","useRef","useEffect"]} \ No newline at end of file diff --git a/src/components/ImageDistortion.tsx b/src/components/ImageDistortion.tsx index 68919cd..e961cca 100644 --- a/src/components/ImageDistortion.tsx +++ b/src/components/ImageDistortion.tsx @@ -154,14 +154,13 @@ export const ImageDistortion: React.FC = ({ const animationCallback = useCallback((deltaTime: number) => { if (!isReady) return; - // 진행도 업데이트 - const updatedAreas = AnimationLoop.updateProgress(currentAreas, deltaTime); - - // 드래그 벡터 업데이트 - const areasWithVectors = AnimationLoop.updateAreaDragVectors(updatedAreas); - - setCurrentAreas(areasWithVectors); - }, [currentAreas, isReady]); + setCurrentAreas((prevAreas) => { + // 진행도 업데이트 + const updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime); + // 드래그 벡터 업데이트 + return AnimationLoop.updateAreaDragVectors(updatedAreas); + }); + }, [isReady]); useAnimationFrame(animationCallback, isPlaying);