diff --git a/dist/index.d.mts b/dist/index.d.mts index ffa840b..b1db660 100644 --- a/dist/index.d.mts +++ b/dist/index.d.mts @@ -13,9 +13,20 @@ interface Point { */ type EasingFunction = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'easeInQuad' | 'easeOutQuad'; /** - * 모션 프리셋 타입 + * 내장 모션 프리셋 타입 */ -type MotionPreset = 'none' | 'horizontal' | 'vertical' | 'rotate-cw' | 'rotate-ccw' | 'pulse' | 'diagonal-1' | 'diagonal-2'; +type BuiltInMotionPreset = 'none' | 'horizontal' | 'vertical' | 'rotate-cw' | 'rotate-ccw' | 'pulse' | 'diagonal-1' | 'diagonal-2'; +/** + * 모션 프리셋 타입 (내장 + 커스텀) + * 커스텀 프리셋은 registerMotionPreset()으로 등록 후 사용 + */ +type MotionPreset = BuiltInMotionPreset | (string & {}); +/** + * 모션 프리셋 정의 + * @param strength 모션 강도 (기본값: 0.1) + * @returns x, y 벡터값 + */ +type MotionPresetDefinition = (strength: number) => Point; /** * 왜곡 애니메이션 움직임 설정 */ @@ -409,6 +420,62 @@ declare const DEFAULT_AREA: { }; }; +/** + * 커스텀 모션 프리셋 등록 + * @param name 프리셋 이름 + * @param definition 프리셋 정의 함수 (strength를 받아 Point 반환) + * @param options 추가 옵션 + * @param options.isRotation 회전 애니메이션 여부 (true면 원운동) + * + * @example + * // 좌우 진짜 왕복 (좌↔우) + * registerMotionPreset('horizontal-full', (strength) => ({ + * x: strength * 2, // 진폭 2배 + * y: 0 + * })); + * + * // 8자 모양 운동 (회전) + * registerMotionPreset('figure-8', (strength) => ({ + * x: strength, + * y: strength * 0.5 + * }), { isRotation: true }); + */ +declare function registerMotionPreset(name: string, definition: MotionPresetDefinition, options?: { + isRotation?: boolean; +}): void; +/** + * 여러 프리셋을 한번에 등록 + * @param presets 프리셋 맵 (이름 → 정의) + * @param rotationPresetNames 회전 프리셋 이름 목록 + * + * @example + * registerMotionPresets({ + * 'horizontal-full': (s) => ({x: s * 2, y: 0}), + * 'wave': (s) => ({x: s, y: s * 0.3}), + * }, ['wave']); // wave는 회전 애니메이션 + */ +declare function registerMotionPresets(presets: Record, rotationPresetNames?: string[]): void; +/** + * 프리셋 등록 해제 + * @param name 프리셋 이름 + * @returns 해제 성공 여부 + */ +declare function unregisterMotionPreset(name: string): boolean; +/** + * 등록된 모든 프리셋 이름 조회 + * @returns 프리셋 이름 배열 + */ +declare function getRegisteredPresets(): string[]; +/** + * 프리셋 존재 여부 확인 + * @param name 프리셋 이름 + * @returns 존재 여부 + */ +declare function hasPreset(name: string): boolean; +/** + * 내장 프리셋으로 초기화 (커스텀 프리셋 모두 제거) + */ +declare function resetToBuiltInPresets(): void; /** * 모션 프리셋을 벡터로 변환 * @param preset 모션 프리셋 @@ -586,4 +653,4 @@ declare const useMouseInteraction: (containerRef: React.RefObject Set; }; -export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, AreaList, type AreaListProps, type AreaOutlineStyle, type CenterPointStyle, type CircleLevelStyle, DEFAULT_AREA, DEFAULT_EDITOR_CANVAS_STYLE, type DistortionArea, type DistortionMovement, type EasingFunction, type EditMode, EditorCanvas, type EditorCanvasProps, type EditorCanvasStyle, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MouseInteractionConfig, type MouseState, ParameterPanel, type ParameterPanelProps, type Point, type PointHandleStyle, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, isRotationPreset, presetToVector, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity }; +export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, AreaList, type AreaListProps, type AreaOutlineStyle, type BuiltInMotionPreset, type CenterPointStyle, type CircleLevelStyle, DEFAULT_AREA, DEFAULT_EDITOR_CANVAS_STYLE, type DistortionArea, type DistortionMovement, type EasingFunction, type EditMode, EditorCanvas, type EditorCanvasProps, type EditorCanvasStyle, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MotionPresetDefinition, type MouseInteractionConfig, type MouseState, ParameterPanel, type ParameterPanelProps, type Point, type PointHandleStyle, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, getRegisteredPresets, hasPreset, isRotationPreset, presetToVector, registerMotionPreset, registerMotionPresets, resetToBuiltInPresets, unregisterMotionPreset, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity }; diff --git a/dist/index.d.ts b/dist/index.d.ts index ffa840b..b1db660 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -13,9 +13,20 @@ interface Point { */ type EasingFunction = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'easeInQuad' | 'easeOutQuad'; /** - * 모션 프리셋 타입 + * 내장 모션 프리셋 타입 */ -type MotionPreset = 'none' | 'horizontal' | 'vertical' | 'rotate-cw' | 'rotate-ccw' | 'pulse' | 'diagonal-1' | 'diagonal-2'; +type BuiltInMotionPreset = 'none' | 'horizontal' | 'vertical' | 'rotate-cw' | 'rotate-ccw' | 'pulse' | 'diagonal-1' | 'diagonal-2'; +/** + * 모션 프리셋 타입 (내장 + 커스텀) + * 커스텀 프리셋은 registerMotionPreset()으로 등록 후 사용 + */ +type MotionPreset = BuiltInMotionPreset | (string & {}); +/** + * 모션 프리셋 정의 + * @param strength 모션 강도 (기본값: 0.1) + * @returns x, y 벡터값 + */ +type MotionPresetDefinition = (strength: number) => Point; /** * 왜곡 애니메이션 움직임 설정 */ @@ -409,6 +420,62 @@ declare const DEFAULT_AREA: { }; }; +/** + * 커스텀 모션 프리셋 등록 + * @param name 프리셋 이름 + * @param definition 프리셋 정의 함수 (strength를 받아 Point 반환) + * @param options 추가 옵션 + * @param options.isRotation 회전 애니메이션 여부 (true면 원운동) + * + * @example + * // 좌우 진짜 왕복 (좌↔우) + * registerMotionPreset('horizontal-full', (strength) => ({ + * x: strength * 2, // 진폭 2배 + * y: 0 + * })); + * + * // 8자 모양 운동 (회전) + * registerMotionPreset('figure-8', (strength) => ({ + * x: strength, + * y: strength * 0.5 + * }), { isRotation: true }); + */ +declare function registerMotionPreset(name: string, definition: MotionPresetDefinition, options?: { + isRotation?: boolean; +}): void; +/** + * 여러 프리셋을 한번에 등록 + * @param presets 프리셋 맵 (이름 → 정의) + * @param rotationPresetNames 회전 프리셋 이름 목록 + * + * @example + * registerMotionPresets({ + * 'horizontal-full': (s) => ({x: s * 2, y: 0}), + * 'wave': (s) => ({x: s, y: s * 0.3}), + * }, ['wave']); // wave는 회전 애니메이션 + */ +declare function registerMotionPresets(presets: Record, rotationPresetNames?: string[]): void; +/** + * 프리셋 등록 해제 + * @param name 프리셋 이름 + * @returns 해제 성공 여부 + */ +declare function unregisterMotionPreset(name: string): boolean; +/** + * 등록된 모든 프리셋 이름 조회 + * @returns 프리셋 이름 배열 + */ +declare function getRegisteredPresets(): string[]; +/** + * 프리셋 존재 여부 확인 + * @param name 프리셋 이름 + * @returns 존재 여부 + */ +declare function hasPreset(name: string): boolean; +/** + * 내장 프리셋으로 초기화 (커스텀 프리셋 모두 제거) + */ +declare function resetToBuiltInPresets(): void; /** * 모션 프리셋을 벡터로 변환 * @param preset 모션 프리셋 @@ -586,4 +653,4 @@ declare const useMouseInteraction: (containerRef: React.RefObject Set; }; -export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, AreaList, type AreaListProps, type AreaOutlineStyle, type CenterPointStyle, type CircleLevelStyle, DEFAULT_AREA, DEFAULT_EDITOR_CANVAS_STYLE, type DistortionArea, type DistortionMovement, type EasingFunction, type EditMode, EditorCanvas, type EditorCanvasProps, type EditorCanvasStyle, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MouseInteractionConfig, type MouseState, ParameterPanel, type ParameterPanelProps, type Point, type PointHandleStyle, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, isRotationPreset, presetToVector, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity }; +export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, AreaList, type AreaListProps, type AreaOutlineStyle, type BuiltInMotionPreset, type CenterPointStyle, type CircleLevelStyle, DEFAULT_AREA, DEFAULT_EDITOR_CANVAS_STYLE, type DistortionArea, type DistortionMovement, type EasingFunction, type EditMode, EditorCanvas, type EditorCanvasProps, type EditorCanvasStyle, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MotionPresetDefinition, type MouseInteractionConfig, type MouseState, ParameterPanel, type ParameterPanelProps, type Point, type PointHandleStyle, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, getRegisteredPresets, hasPreset, isRotationPreset, presetToVector, registerMotionPreset, registerMotionPresets, resetToBuiltInPresets, unregisterMotionPreset, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity }; diff --git a/dist/index.js b/dist/index.js index 53bae0d..f791dc6 100644 --- a/dist/index.js +++ b/dist/index.js @@ -43,8 +43,14 @@ __export(index_exports, { SpringPhysics: () => SpringPhysics, ThreeScene: () => ThreeScene, applyEasing: () => applyEasing, + getRegisteredPresets: () => getRegisteredPresets, + hasPreset: () => hasPreset, isRotationPreset: () => isRotationPreset, presetToVector: () => presetToVector, + registerMotionPreset: () => registerMotionPreset, + registerMotionPresets: () => registerMotionPresets, + resetToBuiltInPresets: () => resetToBuiltInPresets, + unregisterMotionPreset: () => unregisterMotionPreset, useAnimationFrame: () => useAnimationFrame, useDistortionEditor: () => useDistortionEditor, useMouseInteraction: () => useMouseInteraction, @@ -250,31 +256,65 @@ var applyEasing = (progress, easingType) => { }; // src/utils/motionPresets.ts -function presetToVector(preset, strength = 0.1) { - switch (preset) { - case "none": - return { x: 0, y: 0 }; - case "horizontal": - return { x: strength, y: 0 }; - case "vertical": - return { x: 0, y: strength }; - case "rotate-cw": - return { x: strength, y: 0 }; - case "rotate-ccw": - return { x: -strength, y: 0 }; - case "pulse": - return { x: strength, y: strength }; - case "diagonal-1": - return { x: strength * 0.707, y: strength * 0.707 }; - // √2/2 ≈ 0.707 - case "diagonal-2": - return { x: strength * 0.707, y: -strength * 0.707 }; - default: - return { x: 0, y: 0 }; +var presetRegistry = /* @__PURE__ */ new Map(); +var rotationPresets = /* @__PURE__ */ new Set(["rotate-cw", "rotate-ccw"]); +var BUILT_IN_PRESETS = { + "none": () => ({ x: 0, y: 0 }), + "horizontal": (strength) => ({ x: strength, y: 0 }), + "vertical": (strength) => ({ x: 0, y: strength }), + "rotate-cw": (strength) => ({ x: strength, y: 0 }), + "rotate-ccw": (strength) => ({ x: -strength, y: 0 }), + "pulse": (strength) => ({ x: strength, y: strength }), + "diagonal-1": (strength) => ({ x: strength * 0.707, y: strength * 0.707 }), + "diagonal-2": (strength) => ({ x: strength * 0.707, y: -strength * 0.707 }) +}; +Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => { + presetRegistry.set(name, definition); +}); +function registerMotionPreset(name, definition, options) { + presetRegistry.set(name, definition); + if (options?.isRotation) { + rotationPresets.add(name); + } else { + rotationPresets.delete(name); } } +function registerMotionPresets(presets, rotationPresetNames) { + Object.entries(presets).forEach(([name, definition]) => { + presetRegistry.set(name, definition); + }); + rotationPresetNames?.forEach((name) => rotationPresets.add(name)); +} +function unregisterMotionPreset(name) { + rotationPresets.delete(name); + return presetRegistry.delete(name); +} +function getRegisteredPresets() { + return Array.from(presetRegistry.keys()); +} +function hasPreset(name) { + return presetRegistry.has(name); +} +function resetToBuiltInPresets() { + presetRegistry.clear(); + rotationPresets.clear(); + Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => { + presetRegistry.set(name, definition); + }); + rotationPresets.add("rotate-cw"); + rotationPresets.add("rotate-ccw"); +} +function presetToVector(preset, strength = 0.1) { + const definition = presetRegistry.get(preset); + if (definition) { + return definition(strength); + } + console.warn(`Unknown motion preset: "${preset}". Falling back to "none".`); + return { x: 0, y: 0 }; +} function isRotationPreset(preset) { - return preset === "rotate-cw" || preset === "rotate-ccw"; + if (!preset) return false; + return rotationPresets.has(preset); } // src/engine/AnimationLoop.ts @@ -1616,8 +1656,14 @@ var EditorCanvas = ({ SpringPhysics, ThreeScene, applyEasing, + getRegisteredPresets, + hasPreset, isRotationPreset, presetToVector, + registerMotionPreset, + registerMotionPresets, + resetToBuiltInPresets, + unregisterMotionPreset, useAnimationFrame, useDistortionEditor, useMouseInteraction, diff --git a/dist/index.js.map b/dist/index.js.map index 27540b6..e51954a 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/utils/motionPresets.ts","../src/engine/AnimationLoop.ts","../src/hooks/useAnimationFrame.ts","../src/hooks/useMouseInteraction.ts","../src/hooks/useMouseVelocity.ts","../src/engine/SpringPhysics.ts","../src/utils/constants.ts","../src/editor/components/EditorCanvas.tsx","../src/editor/components/AreaList.tsx","../src/editor/components/ParameterPanel.tsx","../src/editor/hooks/useDistortionEditor.ts","../src/editor/constants.ts"],"sourcesContent":["// 메인 컴포넌트\nexport { ImageDistortion } from './components/ImageDistortion';\nexport type { ImageDistortionProps } from './components/ImageDistortion';\n\n// 에디터 컴포넌트들 (개별적으로 조합 가능)\nexport { EditorCanvas } from './editor/components/EditorCanvas';\nexport type { EditorCanvasProps } from './editor/components/EditorCanvas';\nexport { AreaList } from './editor/components/AreaList';\nexport type { AreaListProps } from './editor/components/AreaList';\nexport { ParameterPanel } from './editor/components/ParameterPanel';\nexport type { ParameterPanelProps } from './editor/components/ParameterPanel';\n\n// 에디터 상태 관리 훅\nexport { useDistortionEditor } from './editor/hooks/useDistortionEditor';\n\n// 에디터 타입 및 스타일\nexport type {\n EditorState,\n EditMode,\n EditorCanvasStyle,\n CircleLevelStyle,\n CenterPointStyle,\n PointHandleStyle,\n AreaOutlineStyle,\n} from './editor/types';\n\n// 에디터 기본 스타일 상수\nexport { DEFAULT_EDITOR_CANVAS_STYLE } from './editor/constants';\n\n// 타입 정의\nexport type {\n Point,\n EasingFunction,\n MotionPreset,\n DistortionMovement,\n DistortionArea,\n AreaBounds,\n ShaderUniforms,\n ShaderConfig,\n AnimationState,\n AnimationTicker,\n} from './types';\n\n// 마우스 인터랙션 타입\nexport type {\n SpringPhysicsConfig,\n MouseInteractionConfig,\n MouseState,\n SpringState,\n} from './types/interaction';\n\n// 유틸리티 함수\nexport { applyEasing } from './utils/easing';\nexport { SHADER_CONFIG, ANIMATION_CONFIG, DEFAULT_AREA } from './utils/constants';\nexport { presetToVector, isRotationPreset } from './utils/motionPresets';\n\n// 엔진 클래스 (고급 사용자용)\nexport { ThreeScene } from './engine/ThreeScene';\nexport { ShaderManager } from './engine/ShaderManager';\nexport { AnimationLoop } from './engine/AnimationLoop';\nexport { SpringPhysics } from './engine/SpringPhysics';\n\n// 훅\nexport { useAnimationFrame } from './hooks/useAnimationFrame';\nexport { useMouseVelocity } from './hooks/useMouseVelocity';\nexport { useMouseInteraction } from './hooks/useMouseInteraction';","import React, { useEffect, useRef, useState, useCallback } from 'react';\r\nimport * as THREE from 'three';\r\nimport { type DistortionArea } from '@/types';\r\nimport { ThreeScene } from '@/engine/ThreeScene';\r\nimport { ShaderManager } from '@/engine/ShaderManager';\r\nimport { AnimationLoop } from '@/engine/AnimationLoop';\r\nimport { useAnimationFrame } from '@/hooks/useAnimationFrame';\r\nimport { useMouseInteraction } from '@/hooks/useMouseInteraction';\r\nimport { SHADER_CONFIG } from '@/utils/constants';\r\nimport { MouseInteractionConfig } from '@/types/interaction';\r\n\r\n/**\r\n * ImageDistortion 컴포넌트 Props\r\n */\r\nexport interface ImageDistortionProps {\r\n /** 이미지 소스 URL */\r\n imageSrc: string;\r\n /** 왜곡 영역 배열 */\r\n areas: DistortionArea[];\r\n /** 버텍스 셰이더 경로 (선택사항) */\r\n vertexShaderPath?: string;\r\n /** 프래그먼트 셰이더 경로 (선택사항) */\r\n fragmentShaderPath?: string;\r\n /** 애니메이션 재생 여부 */\r\n isPlaying?: boolean;\r\n /** 컨테이너 스타일 */\r\n style?: React.CSSProperties;\r\n /** 컨테이너 클래스명 */\r\n className?: string;\r\n /** 마우스 인터랙션 설정 */\r\n mouseInteraction?: MouseInteractionConfig;\r\n}\r\n\r\n/**\r\n * GPU 가속 이미지 왜곡 컴포넌트\r\n * Three.js와 GLSL 셰이더를 사용하여 실시간 이미지 왜곡 효과를 제공합니다.\r\n */\r\nexport const ImageDistortion: React.FC = ({\r\n imageSrc,\r\n areas,\r\n vertexShaderPath,\r\n fragmentShaderPath,\r\n isPlaying = true,\r\n style,\r\n className,\r\n mouseInteraction,\r\n}) => {\r\n const containerRef = useRef(null);\r\n const sceneRef = useRef(null);\r\n const shaderManagerRef = useRef(new ShaderManager());\r\n const textureRef = useRef(null);\r\n\r\n const [isReady, setIsReady] = useState(false);\r\n const [imageLoaded, setImageLoaded] = useState(false);\r\n const [currentAreas, setCurrentAreas] = useState(areas);\r\n\r\n // 마우스 인터랙션 훅\r\n const mouseInteractionHook = useMouseInteraction(\r\n containerRef,\r\n mouseInteraction || {\r\n enabled: false,\r\n physics: {\r\n stiffness: 100,\r\n damping: 10,\r\n mass: 1,\r\n influenceRadius: 0.2,\r\n maxStrength: 1.0,\r\n },\r\n }\r\n );\r\n\r\n // 영역 변경 시 상태 업데이트\r\n useEffect(() => {\r\n setCurrentAreas(areas);\r\n }, [areas]);\r\n\r\n // 마우스 인터랙션 설정 변경 시 업데이트\r\n useEffect(() => {\r\n if (mouseInteraction) {\r\n mouseInteractionHook.updateConfig(mouseInteraction);\r\n }\r\n }, [mouseInteraction, mouseInteractionHook]);\r\n\r\n // Three.js 씬 초기화\r\n useEffect(() => {\r\n console.log('[ImageDistortion] useEffect 실행, containerRef.current:', containerRef.current);\r\n\r\n if (!containerRef.current) {\r\n console.warn('[ImageDistortion] containerRef.current가 null입니다. 컴포넌트가 제대로 마운트되지 않았습니다.');\r\n return;\r\n }\r\n\r\n console.log('[ImageDistortion] 초기화 시작');\r\n const scene = new ThreeScene(containerRef.current);\r\n sceneRef.current = scene;\r\n\r\n // 셰이더 로드\r\n const vertPath = vertexShaderPath || '/shaders/distortion.vert.glsl';\r\n const fragPath = fragmentShaderPath || '/shaders/distortion.frag.glsl';\r\n\r\n console.log('[ImageDistortion] 셰이더 로드 시도:', { vertPath, fragPath });\r\n\r\n shaderManagerRef.current\r\n .loadShaders(vertPath, fragPath)\r\n .then(({ vertex, fragment }) => {\r\n console.log('[ImageDistortion] 셰이더 로드 성공');\r\n scene.setShaderMaterial(vertex, fragment);\r\n setIsReady(true);\r\n })\r\n .catch((error) => {\r\n console.error('[ImageDistortion] 셰이더 로드 실패:', error);\r\n });\r\n\r\n return () => {\r\n scene.dispose();\r\n if (textureRef.current) {\r\n textureRef.current.dispose();\r\n }\r\n };\r\n }, [vertexShaderPath, fragmentShaderPath]);\r\n\r\n // 이미지 텍스처 로드\r\n useEffect(() => {\r\n if (!imageSrc || !isReady) {\r\n console.log('[ImageDistortion] 이미지 로드 스킵:', { imageSrc, isReady });\r\n return;\r\n }\r\n\r\n console.log('[ImageDistortion] 이미지 로드 시작:', imageSrc);\r\n setImageLoaded(false);\r\n\r\n const loader = new THREE.TextureLoader();\r\n loader.load(\r\n imageSrc,\r\n (texture) => {\r\n console.log('[ImageDistortion] 이미지 로드 성공!', {\r\n width: texture.image.width,\r\n height: texture.image.height\r\n });\r\n textureRef.current = texture;\r\n setImageLoaded(true);\r\n if (sceneRef.current) {\r\n sceneRef.current.updateUniforms({\r\n u_texture: { value: texture },\r\n });\r\n sceneRef.current.render();\r\n console.log('[ImageDistortion] 텍스처 업데이트 및 렌더링 완료');\r\n }\r\n },\r\n (progress) => {\r\n console.log('[ImageDistortion] 이미지 로딩 중...',\r\n Math.round((progress.loaded / progress.total) * 100) + '%'\r\n );\r\n },\r\n (error) => {\r\n console.error('[ImageDistortion] 이미지 로드 실패:', error);\r\n setImageLoaded(false);\r\n }\r\n );\r\n\r\n return () => {\r\n if (textureRef.current) {\r\n textureRef.current.dispose();\r\n textureRef.current = null;\r\n }\r\n };\r\n }, [imageSrc, isReady]);\r\n\r\n // 셰이더 유니폼 업데이트\r\n useEffect(() => {\r\n if (!sceneRef.current || !isReady) return;\r\n\r\n // 현재 해상도 가져오기\r\n const resolution = sceneRef.current.getResolution();\r\n\r\n // 포인트 배열 생성\r\n // UI는 좌상단 (0,0), WebGL은 좌하단 (0,0)이므로 y 좌표를 반전\r\n const points = new Float32Array(SHADER_CONFIG.MAX_POINTS * 2);\r\n currentAreas.forEach((area, areaIndex) => {\r\n area.basePoints.forEach((point, pointIndex) => {\r\n const index = (areaIndex * 4 + pointIndex) * 2;\r\n points[index] = point.x;\r\n points[index + 1] = 1.0 - point.y; // y 좌표 반전\r\n });\r\n });\r\n\r\n // 드래그 벡터 배열 생성\r\n // dragVector도 y 좌표계를 맞춰야 하므로 y를 반전\r\n const dragVectors = new Float32Array(SHADER_CONFIG.MAX_DRAG_VECTORS * 2);\r\n currentAreas.forEach((area, index) => {\r\n const baseIndex = index * 2;\r\n dragVectors[baseIndex] = area.dragVector.x;\r\n dragVectors[baseIndex + 1] = -area.dragVector.y; // y 방향 반전\r\n });\r\n\r\n // 강도 배열 생성\r\n const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS);\r\n currentAreas.forEach((area, index) => {\r\n strengths[index] = area.distortionStrength;\r\n });\r\n\r\n sceneRef.current.updateUniforms({\r\n u_numAreas: { value: currentAreas.length },\r\n u_points: { value: points },\r\n u_dragVectors: { value: dragVectors },\r\n u_distortionStrengths: { value: strengths },\r\n });\r\n\r\n sceneRef.current.render();\r\n }, [currentAreas, isReady]);\r\n\r\n // 애니메이션 루프\r\n const animationCallback = useCallback((deltaTime: number) => {\r\n if (!isReady) return;\r\n\r\n setCurrentAreas((prevAreas) => {\r\n // 현재 인터랙션 중인 영역 인덱스 가져오기\r\n const interactingIndices = mouseInteractionHook.getInteractingAreaIndices?.() || new Set();\r\n\r\n // 1. 자동 애니메이션 업데이트\r\n let updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);\r\n updatedAreas = AnimationLoop.updateAreaDragVectors(updatedAreas);\r\n\r\n // 인터랙션 중인 영역만 dragVector를 0으로 설정\r\n if (interactingIndices.size > 0) {\r\n updatedAreas = updatedAreas.map((area, index) => {\r\n if (interactingIndices.has(index)) {\r\n return {\r\n ...area,\r\n dragVector: { x: 0, y: 0 }\r\n };\r\n }\r\n return area;\r\n });\r\n }\r\n\r\n // 2. 마우스 인터랙션 적용 (기존 dragVector에 스프링 변위 추가)\r\n if (mouseInteraction?.enabled) {\r\n updatedAreas = mouseInteractionHook.updateInteraction(updatedAreas, deltaTime);\r\n }\r\n\r\n return updatedAreas;\r\n });\r\n }, [isReady, mouseInteraction, mouseInteractionHook]);\r\n\r\n // 애니메이션은 항상 실행 (마우스 인터랙션 포함)\r\n useAnimationFrame(animationCallback, isPlaying || mouseInteraction?.enabled || false);\r\n\r\n return (\r\n \r\n {!imageLoaded && (\r\n \r\n 이미지 로딩 중...\r\n \r\n )}\r\n \r\n );\r\n};","import * as THREE from 'three';\nimport type { 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 console.log('[ThreeScene] setShaderMaterial 호출됨');\n console.log('[ThreeScene] vertexShader 길이:', vertexShader.length);\n console.log('[ThreeScene] fragmentShader 길이:', fragmentShader.length);\n\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 console.log('[ThreeScene] ShaderMaterial 생성됨');\n\n // 셰이더 컴파일 에러 확인\n const renderer = this.renderer;\n const testScene = new THREE.Scene();\n const testMesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), material);\n testScene.add(testMesh);\n\n try {\n renderer.compile(testScene, this.camera);\n console.log('[ThreeScene] 셰이더 컴파일 성공!');\n } catch (e) {\n console.error('[ThreeScene] 셰이더 컴파일 에러:', e);\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 console.log('[ThreeScene] 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 getResolution(): { x: number; y: number } {\n return {\n x: this.uniforms.u_resolution.value.x,\n y: this.uniforms.u_resolution.value.y,\n };\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 console.log('[ShaderManager] loadShaders 시작:', { vertexPath, fragmentPath });\n\n try {\n console.log('[ShaderManager] fetch 시작...');\n const [vertexResponse, fragmentResponse] = await Promise.all([\n fetch(vertexPath),\n fetch(fragmentPath),\n ]);\n\n console.log('[ShaderManager] fetch 완료:', {\n vertexStatus: vertexResponse.status,\n fragmentStatus: fragmentResponse.status\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 console.log('[ShaderManager] text() 변환 시작...');\n this.vertexShaderSource = await vertexResponse.text();\n this.fragmentShaderSource = await fragmentResponse.text();\n\n console.log('[ShaderManager] 셰이더 로드 완료!', {\n vertexLength: this.vertexShaderSource.length,\n fragmentLength: this.fragmentShaderSource.length\n });\n\n return {\n vertex: this.vertexShaderSource,\n fragment: this.fragmentShaderSource,\n };\n } catch (error) {\n console.error('[ShaderManager] 셰이더 로드 실패:', 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 { type EasingFunction } from '../types';\r\n\r\ntype EasingFunc = (t: number) => number;\r\n\r\n/**\r\n * 이징 함수 구현 맵\r\n */\r\nconst easingFunctions: Record = {\r\n linear: (t) => t,\r\n\r\n easeIn: (t) => t * t,\r\n easeOut: (t) => t * (2 - t),\r\n easeInOut: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),\r\n\r\n easeInQuad: (t) => t * t,\r\n easeOutQuad: (t) => t * (2 - t),\r\n};\r\n\r\n/**\r\n * 진행도에 이징 함수를 적용\r\n * @param progress 진행도 (0.0 - 1.0)\r\n * @param easingType 적용할 이징 함수 타입\r\n * @returns 이징이 적용된 진행도 (0.0 - 1.0)\r\n */\r\nexport const applyEasing = (\r\n progress: number,\r\n easingType: EasingFunction\r\n): number => {\r\n const clampedProgress = Math.max(0, Math.min(1, progress));\r\n return easingFunctions[easingType](clampedProgress);\r\n};","import type {MotionPreset, Point} from '../types';\r\n\r\n/**\r\n * 모션 프리셋을 벡터로 변환\r\n * @param preset 모션 프리셋\r\n * @param strength 모션 강도 (기본값: 0.1)\r\n * @returns 계산된 벡터 (vectorA)\r\n */\r\nexport function presetToVector(preset: MotionPreset, strength: number = 0.1): Point {\r\n\tswitch (preset) {\r\n\t\tcase 'none':\r\n\t\t\t// 애니메이션 없음\r\n\t\t\treturn {x: 0, y: 0};\r\n\r\n\t\tcase 'horizontal':\r\n\t\t\t// 좌우 왕복\r\n\t\t\treturn {x: strength, y: 0};\r\n\r\n\t\tcase 'vertical':\r\n\t\t\t// 상하 왕복\r\n\t\t\treturn {x: 0, y: strength};\r\n\r\n\t\tcase 'rotate-cw':\r\n\t\t\t// 시계방향 회전 (원운동의 시작점)\r\n\t\t\treturn {x: strength, y: 0};\r\n\r\n\t\tcase 'rotate-ccw':\r\n\t\t\t// 반시계방향 회전 (원운동의 시작점)\r\n\t\t\treturn {x: -strength, y: 0};\r\n\r\n\t\tcase 'pulse':\r\n\t\t\t// 펄스 (중심에서 바깥으로)\r\n\t\t\treturn {x: strength, y: strength};\r\n\r\n\t\tcase 'diagonal-1':\r\n\t\t\t// 대각선 (좌상→우하)\r\n\t\t\treturn {x: strength * 0.707, y: strength * 0.707}; // √2/2 ≈ 0.707\r\n\r\n\t\tcase 'diagonal-2':\r\n\t\t\t// 대각선 (우상→좌하)\r\n\t\t\treturn {x: strength * 0.707, y: -strength * 0.707};\r\n\r\n\t\tdefault:\r\n\t\t\treturn {x: 0, y: 0};\r\n\t}\r\n}\r\n\r\n/**\r\n * 프리셋이 회전 타입인지 확인\r\n */\r\nexport function isRotationPreset(preset?: MotionPreset): boolean {\r\n\treturn preset === 'rotate-cw' || preset === 'rotate-ccw';\r\n}\r\n","import { applyEasing } from '../utils/easing';\r\nimport { presetToVector, isRotationPreset } from '../utils/motionPresets';\r\nimport type {DistortionArea, Point} from \"../types\";\r\n\r\n/**\r\n * 애니메이션 루프 관리 클래스\r\n */\r\nexport class AnimationLoop {\r\n /**\r\n * 영역들의 드래그 벡터를 현재 진행도에 따라 업데이트\r\n * @param areas 왜곡 영역 배열\r\n * @returns 업데이트된 영역 배열\r\n */\r\n public static updateAreaDragVectors(\r\n areas: DistortionArea[]\r\n ): DistortionArea[] {\r\n return areas.map((area) => {\r\n const { progress, movement } = area;\r\n\r\n // duration이 0이거나 프리셋이 'none'이면 애니메이션 없음\r\n if (movement.duration <= 0 || movement.preset === 'none') {\r\n return {\r\n ...area,\r\n dragVector: { x: 0, y: 0 },\r\n };\r\n }\r\n\r\n // 프리셋이 설정되어 있으면 프리셋 기반 벡터 사용\r\n let baseVector: Point;\r\n if (movement.preset) {\r\n const strength = movement.strength ?? 0.1;\r\n baseVector = presetToVector(movement.preset, strength);\r\n } else {\r\n // 프리셋 없으면 기존 vectorA 사용 (하위 호환성)\r\n baseVector = movement.vectorA;\r\n }\r\n\r\n // 이징 적용\r\n const easedProgress = applyEasing(progress, movement.easing);\r\n\r\n // 벡터 계산\r\n let dragVector: Point;\r\n\r\n // 회전 프리셋인 경우 원운동\r\n if (movement.preset && isRotationPreset(movement.preset)) {\r\n const angle = easedProgress * Math.PI * 2;\r\n const radius = Math.sqrt(baseVector.x * baseVector.x + baseVector.y * baseVector.y);\r\n const direction = movement.preset === 'rotate-cw' ? 1 : -1;\r\n dragVector = {\r\n x: Math.cos(angle * direction) * radius,\r\n y: Math.sin(angle * direction) * radius,\r\n };\r\n } else {\r\n // 일반 왕복 모션\r\n if (easedProgress < 0.5) {\r\n // 0.0 -> 0.5: 0에서 baseVector로 보간\r\n const t = easedProgress * 2;\r\n dragVector = {\r\n x: baseVector.x * t,\r\n y: baseVector.y * t,\r\n };\r\n } else {\r\n // 0.5 -> 1.0: baseVector에서 0으로 보간\r\n const t = (easedProgress - 0.5) * 2;\r\n dragVector = {\r\n x: baseVector.x * (1 - t),\r\n y: baseVector.y * (1 - t),\r\n };\r\n }\r\n }\r\n\r\n return {\r\n ...area,\r\n dragVector,\r\n };\r\n });\r\n }\r\n\r\n /**\r\n * 모든 영역의 진행도를 델타 타임만큼 업데이트\r\n * @param areas 왜곡 영역 배열\r\n * @param deltaTime 델타 타임 (초)\r\n * @returns 업데이트된 영역 배열\r\n */\r\n public static updateProgress(\r\n areas: DistortionArea[],\r\n deltaTime: number\r\n ): DistortionArea[] {\r\n return areas.map((area) => {\r\n // duration이 0이면 progress 업데이트 안 함\r\n if (area.movement.duration <= 0) {\r\n return area;\r\n }\r\n\r\n let newProgress = area.progress + deltaTime / area.movement.duration;\r\n newProgress %= 1.0; // 루프\r\n\r\n return {\r\n ...area,\r\n progress: newProgress,\r\n };\r\n });\r\n }\r\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};","import { useRef, useCallback, useState } from 'react';\r\nimport { useMouseVelocity } from './useMouseVelocity';\r\nimport { SpringPhysics } from '@/engine/SpringPhysics';\r\nimport { DistortionArea, Point } from '@/types';\r\nimport { MouseInteractionConfig } from '@/types/interaction';\r\n\r\n/**\r\n * 점이 사각형 내부에 있는지 확인\r\n */\r\nconst isPointInPolygon = (point: Point, polygon: Point[]): boolean => {\r\n\tlet inside = false;\r\n\tfor (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\r\n\t\tconst xi = polygon[i].x, yi = polygon[i].y;\r\n\t\tconst xj = polygon[j].x, yj = polygon[j].y;\r\n\t\tconst intersect = ((yi > point.y) !== (yj > point.y))\r\n\t\t\t&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);\r\n\t\tif (intersect) inside = !inside;\r\n\t}\r\n\treturn inside;\r\n};\r\n\r\n/**\r\n * 마우스 인터랙션 기반 기존 영역 제어 훅\r\n * 마우스가 지나가는 모든 영역에 효과 적용\r\n */\r\nexport const useMouseInteraction = (\r\n\tcontainerRef: React.RefObject,\r\n\tconfig: MouseInteractionConfig\r\n) => {\r\n\tconst { getState } = useMouseVelocity(containerRef);\r\n\tconst [interactingAreaIndices, setInteractingAreaIndices] = useState>(new Set());\r\n\tconst springPhysicsMapRef = useRef>(new Map());\r\n\r\n\t/**\r\n\t * 특정 영역의 스프링 물리 엔진 가져오기 (없으면 생성)\r\n\t */\r\n\tconst getSpringPhysics = useCallback((areaIndex: number, area?: DistortionArea): SpringPhysics => {\r\n\t\tif (!springPhysicsMapRef.current.has(areaIndex)) {\r\n\t\t\t// 영역별 물리 설정이 있으면 사용, 없으면 전역 설정 사용\r\n\t\t\tconst physicsConfig = area?.physics || config.physics;\r\n\t\t\tspringPhysicsMapRef.current.set(areaIndex, new SpringPhysics(physicsConfig));\r\n\t\t}\r\n\t\treturn springPhysicsMapRef.current.get(areaIndex)!;\r\n\t}, [config.physics]);\r\n\r\n\t/**\r\n\t * 기존 영역들의 dragVector를 마우스 인터랙션으로 업데이트\r\n\t */\r\n\tconst updateInteraction = useCallback((areas: DistortionArea[], deltaTime: number): DistortionArea[] => {\r\n\t\tif (!config.enabled) return areas;\r\n\r\n\t\t// 영역별 물리 설정이 변경되었을 수 있으므로 모든 스프링 업데이트\r\n\t\tareas.forEach((area, index) => {\r\n\t\t\tif (area.physics && springPhysicsMapRef.current.has(index)) {\r\n\t\t\t\tconst spring = springPhysicsMapRef.current.get(index)!;\r\n\t\t\t\tspring.setConfig(area.physics);\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tconst mouseState = getState();\r\n\r\n\t\t// 마우스 클릭/드래그 중이고 위치가 있으면\r\n\t\tif (mouseState.isDragging && mouseState.position) {\r\n\t\t\t// 현재 마우스 위치가 포함된 모든 영역 찾기\r\n\t\t\tconst currentlyInAreas = new Set();\r\n\t\t\tfor (let i = 0; i < areas.length; i++) {\r\n\t\t\t\tif (isPointInPolygon(mouseState.position, areas[i].basePoints)) {\r\n\t\t\t\t\tcurrentlyInAreas.add(i);\r\n\r\n\t\t\t\t\t// 새로 진입한 영역이면 스프링 리셋\r\n\t\t\t\t\tif (!interactingAreaIndices.has(i)) {\r\n\t\t\t\t\t\tgetSpringPhysics(i, areas[i]).reset();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// 이전에 인터랙션하던 영역에서 벗어났으면 평형으로 복귀\r\n\t\t\tinteractingAreaIndices.forEach((areaIndex) => {\r\n\t\t\t\tif (!currentlyInAreas.has(areaIndex)) {\r\n\t\t\t\t\tgetSpringPhysics(areaIndex, areas[areaIndex]).returnToEquilibrium();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\t// 인터랙션 영역 업데이트\r\n\t\t\tsetInteractingAreaIndices(currentlyInAreas);\r\n\r\n\t\t\t// 현재 위치의 모든 영역에 속도 적용\r\n\t\t\tconst velocityMult = config.velocityMultiplier || 1.0;\r\n\t\t\tconst velocityMag = Math.sqrt(\r\n\t\t\t\tmouseState.velocity.x ** 2 + mouseState.velocity.y ** 2\r\n\t\t\t);\r\n\t\t\tconst minVel = config.minVelocity || 0.05;\r\n\t\t\tconst maxVel = config.maxVelocity || 5.0;\r\n\r\n\t\t\t// 속도 클램핑\r\n\t\t\tlet clampedVelocity = mouseState.velocity;\r\n\t\t\tif (velocityMag > maxVel) {\r\n\t\t\t\tconst scale = maxVel / velocityMag;\r\n\t\t\t\tclampedVelocity = {\r\n\t\t\t\t\tx: mouseState.velocity.x * scale,\r\n\t\t\t\t\ty: mouseState.velocity.y * scale,\r\n\t\t\t\t};\r\n\t\t\t}\r\n\r\n\t\t\tcurrentlyInAreas.forEach((areaIndex) => {\r\n\t\t\t\tconst spring = getSpringPhysics(areaIndex, areas[areaIndex]);\r\n\r\n\t\t\t\tif (velocityMag >= minVel) {\r\n\t\t\t\t\t// 드래그 중: 마우스 속도를 목표로 설정\r\n\t\t\t\t\tspring.setTarget(clampedVelocity, velocityMult);\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// 드래그 중이지만 마우스가 멈춰있으면 평형으로 복귀\r\n\t\t\t\t\tspring.returnToEquilibrium();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\t// 마우스를 놓았으면 인터랙션 중이던 모든 영역에 튕김 효과\r\n\t\t\tif (interactingAreaIndices.size > 0) {\r\n\t\t\t\tconst velocityMult = config.velocityMultiplier || 1.0;\r\n\t\t\t\tconst maxVel = config.maxVelocity || 5.0;\r\n\r\n\t\t\t\t// 속도 클램핑\r\n\t\t\t\tconst velocityMag = Math.sqrt(\r\n\t\t\t\t\tmouseState.velocity.x ** 2 + mouseState.velocity.y ** 2\r\n\t\t\t\t);\r\n\t\t\t\tlet clampedVelocity = mouseState.velocity;\r\n\t\t\t\tif (velocityMag > maxVel) {\r\n\t\t\t\t\tconst scale = maxVel / velocityMag;\r\n\t\t\t\t\tclampedVelocity = {\r\n\t\t\t\t\t\tx: mouseState.velocity.x * scale,\r\n\t\t\t\t\t\ty: mouseState.velocity.y * scale,\r\n\t\t\t\t\t};\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// 모든 인터랙션 영역에 초기 속도 설정\r\n\t\t\t\tinteractingAreaIndices.forEach((areaIndex) => {\r\n\t\t\t\t\tconst spring = getSpringPhysics(areaIndex, areas[areaIndex]);\r\n\t\t\t\t\tspring.setInitialVelocity(clampedVelocity, velocityMult);\r\n\t\t\t\t});\r\n\r\n\t\t\t\tsetInteractingAreaIndices(new Set());\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// 모든 영역의 스프링 물리 업데이트\r\n\t\treturn areas.map((area, index) => {\r\n\t\t\tconst spring = springPhysicsMapRef.current.get(index);\r\n\t\t\tif (!spring) return area;\r\n\r\n\t\t\t// 현재 드래그 중인 영역이거나 스프링이 활성 상태일 때만 업데이트\r\n\t\t\tconst springVelocity = spring.getVelocity();\r\n\t\t\tconst springDisplacement = spring.getDisplacement();\r\n\t\t\tconst isSpringActive = Math.sqrt(springVelocity.x ** 2 + springVelocity.y ** 2) > 0.001 ||\r\n\t\t\t\tMath.sqrt(springDisplacement.x ** 2 + springDisplacement.y ** 2) > 0.001;\r\n\r\n\t\t\t// 드래그 중이 아니고 스프링도 비활성이면 업데이트 안 함\r\n\t\t\tif (!interactingAreaIndices.has(index) && !isSpringActive) {\r\n\t\t\t\treturn area;\r\n\t\t\t}\r\n\r\n\t\t\t// 스프링 물리 업데이트\r\n\t\t\tconst displacement = spring.update(deltaTime);\r\n\r\n\t\t\t// 변위가 거의 0이면 원래 dragVector 유지\r\n\t\t\tconst displacementMag = Math.sqrt(displacement.x ** 2 + displacement.y ** 2);\r\n\t\t\tif (displacementMag < 0.001) {\r\n\t\t\t\treturn area;\r\n\t\t\t}\r\n\r\n\t\t\t// 스프링 변위를 dragVector에 추가 (기존 애니메이션과 혼합)\r\n\t\t\t// dragVector는 텍스처 샘플링 좌표 이동이므로,\r\n\t\t\t// 마우스 드래그 방향과 반대로 적용해야 이미지가 드래그 방향으로 밀림\r\n\t\t\t// 예: 우→좌 드래그(velocity < 0) → dragVector > 0 → 이미지 왼쪽으로 밀림\r\n\t\t\treturn {\r\n\t\t\t\t...area,\r\n\t\t\t\tdragVector: {\r\n\t\t\t\t\tx: area.dragVector.x - displacement.x,\r\n\t\t\t\t\ty: area.dragVector.y - displacement.y,\r\n\t\t\t\t},\r\n\t\t\t};\r\n\t\t});\r\n\t}, [config, getState, interactingAreaIndices, getSpringPhysics]);\r\n\r\n\t/**\r\n\t * 물리 파라미터 업데이트\r\n\t */\r\n\tconst updateConfig = useCallback((newConfig: Partial) => {\r\n\t\tconst physicsConfig = newConfig.physics;\r\n\t\tif (physicsConfig) {\r\n\t\t\t// 모든 스프링 물리 엔진의 설정 업데이트\r\n\t\t\tspringPhysicsMapRef.current.forEach((spring) => {\r\n\t\t\t\tspring.setConfig(physicsConfig);\r\n\t\t\t});\r\n\t\t}\r\n\t}, []);\r\n\r\n\t/**\r\n\t * 모든 영역의 스프링 상태 리셋\r\n\t */\r\n\tconst reset = useCallback(() => {\r\n\t\tspringPhysicsMapRef.current.forEach((spring) => {\r\n\t\t\tspring.reset();\r\n\t\t});\r\n\t\tsetInteractingAreaIndices(new Set());\r\n\t}, []);\r\n\r\n\t/**\r\n\t * 현재 드래그 중인지 확인\r\n\t */\r\n\tconst isDragging = useCallback((): boolean => {\r\n\t\tconst mouseState = getState();\r\n\t\treturn mouseState.isDragging;\r\n\t}, [getState]);\r\n\r\n\t/**\r\n\t * 현재 인터랙션 중인 영역 인덱스 가져오기\r\n\t */\r\n\tconst getInteractingAreaIndices = useCallback((): Set => {\r\n\t\treturn interactingAreaIndices;\r\n\t}, [interactingAreaIndices]);\r\n\r\n\treturn {\r\n\t\tupdateInteraction,\r\n\t\tupdateConfig,\r\n\t\treset,\r\n\t\tisDragging,\r\n\t\tgetInteractingAreaIndices,\r\n\t};\r\n};\r\n","import { useRef, useCallback, useEffect } from 'react';\nimport { Point } from '@/types';\nimport { MouseState } from '@/types/interaction';\n\n/**\n * 마우스 위치, 속도, 가속도를 추적하는 훅\n */\nexport const useMouseVelocity = (containerRef: React.RefObject) => {\n\tconst mouseStateRef = useRef({\n\t\tposition: null,\n\t\tprevPosition: null,\n\t\tvelocity: { x: 0, y: 0 },\n\t\tacceleration: { x: 0, y: 0 },\n\t\tisHovering: false,\n\t\tisDragging: false,\n\t});\n\n\tconst lastUpdateTimeRef = useRef(Date.now());\n\tconst prevVelocityRef = useRef({ x: 0, y: 0 });\n\n\t/**\n\t * 픽셀 좌표를 정규화 좌표(0-1)로 변환\n\t */\n\tconst toNormalized = useCallback((clientX: number, clientY: number): Point | null => {\n\t\tif (!containerRef.current) return null;\n\t\tconst rect = containerRef.current.getBoundingClientRect();\n\t\treturn {\n\t\t\tx: (clientX - rect.left) / rect.width,\n\t\t\ty: (clientY - rect.top) / rect.height,\n\t\t};\n\t}, [containerRef]);\n\n\t/**\n\t * 위치 업데이트 (마우스/터치 공통 로직)\n\t */\n\tconst updatePosition = useCallback((clientX: number, clientY: number) => {\n\t\tconst now = Date.now();\n\t\tconst deltaTime = (now - lastUpdateTimeRef.current) / 1000; // 초 단위\n\t\tlastUpdateTimeRef.current = now;\n\n\t\tconst normalizedPos = toNormalized(clientX, clientY);\n\t\tif (!normalizedPos) return;\n\n\t\tconst state = mouseStateRef.current;\n\t\tconst prevPos = state.position;\n\n\t\t// 속도 계산 (변위 / 시간)\n\t\tlet velocity: Point = { x: 0, y: 0 };\n\t\tif (prevPos && deltaTime > 0) {\n\t\t\tvelocity = {\n\t\t\t\tx: (normalizedPos.x - prevPos.x) / deltaTime,\n\t\t\t\ty: (normalizedPos.y - prevPos.y) / deltaTime,\n\t\t\t};\n\t\t}\n\n\t\t// 가속도 계산 (속도 변화 / 시간)\n\t\tconst prevVel = prevVelocityRef.current;\n\t\tlet acceleration: Point = { x: 0, y: 0 };\n\t\tif (deltaTime > 0) {\n\t\t\tacceleration = {\n\t\t\t\tx: (velocity.x - prevVel.x) / deltaTime,\n\t\t\t\ty: (velocity.y - prevVel.y) / deltaTime,\n\t\t\t};\n\t\t}\n\n\t\t// 상태 업데이트\n\t\tmouseStateRef.current = {\n\t\t\tposition: normalizedPos,\n\t\t\tprevPosition: prevPos,\n\t\t\tvelocity,\n\t\t\tacceleration,\n\t\t\tisHovering: true,\n\t\t\tisDragging: state.isDragging,\n\t\t};\n\t\tprevVelocityRef.current = velocity;\n\t}, [toNormalized]);\n\n\t/**\n\t * 마우스 이동 핸들러\n\t */\n\tconst handleMouseMove = useCallback((e: MouseEvent) => {\n\t\tupdatePosition(e.clientX, e.clientY);\n\t}, [updatePosition]);\n\n\t/**\n\t * 마우스 진입\n\t */\n\tconst handleMouseEnter = useCallback(() => {\n\t\tmouseStateRef.current.isHovering = true;\n\t}, []);\n\n\t/**\n\t * 마우스 나감\n\t */\n\tconst handleMouseLeave = useCallback(() => {\n\t\tmouseStateRef.current = {\n\t\t\tposition: null,\n\t\t\tprevPosition: null,\n\t\t\tvelocity: { x: 0, y: 0 },\n\t\t\tacceleration: { x: 0, y: 0 },\n\t\t\tisHovering: false,\n\t\t\tisDragging: false,\n\t\t};\n\t\tprevVelocityRef.current = { x: 0, y: 0 };\n\t}, []);\n\n\t/**\n\t * 마우스 다운\n\t */\n\tconst handleMouseDown = useCallback(() => {\n\t\tmouseStateRef.current.isDragging = true;\n\t}, []);\n\n\t/**\n\t * 마우스 업\n\t */\n\tconst handleMouseUp = useCallback(() => {\n\t\tmouseStateRef.current.isDragging = false;\n\t}, []);\n\n\t/**\n\t * 터치 이동 핸들러\n\t */\n\tconst handleTouchMove = useCallback((e: TouchEvent) => {\n\t\te.preventDefault(); // 스크롤 방지\n\t\tif (e.touches.length > 0) {\n\t\t\tconst touch = e.touches[0];\n\t\t\tupdatePosition(touch.clientX, touch.clientY);\n\t\t}\n\t}, [updatePosition]);\n\n\t/**\n\t * 터치 시작 핸들러\n\t */\n\tconst handleTouchStart = useCallback((e: TouchEvent) => {\n\t\te.preventDefault(); // 스크롤 방지\n\t\tmouseStateRef.current.isDragging = true;\n\t\tmouseStateRef.current.isHovering = true;\n\t\tif (e.touches.length > 0) {\n\t\t\tconst touch = e.touches[0];\n\t\t\tupdatePosition(touch.clientX, touch.clientY);\n\t\t}\n\t}, [updatePosition]);\n\n\t/**\n\t * 터치 종료 핸들러\n\t */\n\tconst handleTouchEnd = useCallback(() => {\n\t\tmouseStateRef.current.isDragging = false;\n\t\tmouseStateRef.current.isHovering = false;\n\t\tmouseStateRef.current.position = null;\n\t\tmouseStateRef.current.prevPosition = null;\n\t\tmouseStateRef.current.velocity = { x: 0, y: 0 };\n\t\tmouseStateRef.current.acceleration = { x: 0, y: 0 };\n\t\tprevVelocityRef.current = { x: 0, y: 0 };\n\t}, []);\n\n\t/**\n\t * 이벤트 리스너 등록\n\t */\n\tuseEffect(() => {\n\t\tconst container = containerRef.current;\n\t\tif (!container) return;\n\n\t\t// 마우스 이벤트\n\t\tcontainer.addEventListener('mousemove', handleMouseMove);\n\t\tcontainer.addEventListener('mouseenter', handleMouseEnter);\n\t\tcontainer.addEventListener('mouseleave', handleMouseLeave);\n\t\tcontainer.addEventListener('mousedown', handleMouseDown);\n\t\twindow.addEventListener('mouseup', handleMouseUp);\n\n\t\t// 터치 이벤트 (passive: false로 스크롤 방지 가능)\n\t\tcontainer.addEventListener('touchmove', handleTouchMove, { passive: false });\n\t\tcontainer.addEventListener('touchstart', handleTouchStart, { passive: false });\n\t\tcontainer.addEventListener('touchend', handleTouchEnd);\n\t\tcontainer.addEventListener('touchcancel', handleTouchEnd);\n\n\t\treturn () => {\n\t\t\t// 마우스 이벤트 제거\n\t\t\tcontainer.removeEventListener('mousemove', handleMouseMove);\n\t\t\tcontainer.removeEventListener('mouseenter', handleMouseEnter);\n\t\t\tcontainer.removeEventListener('mouseleave', handleMouseLeave);\n\t\t\tcontainer.removeEventListener('mousedown', handleMouseDown);\n\t\t\twindow.removeEventListener('mouseup', handleMouseUp);\n\n\t\t\t// 터치 이벤트 제거\n\t\t\tcontainer.removeEventListener('touchmove', handleTouchMove);\n\t\t\tcontainer.removeEventListener('touchstart', handleTouchStart);\n\t\t\tcontainer.removeEventListener('touchend', handleTouchEnd);\n\t\t\tcontainer.removeEventListener('touchcancel', handleTouchEnd);\n\t\t};\n\t}, [containerRef, handleMouseMove, handleMouseEnter, handleMouseLeave, handleMouseDown, handleMouseUp, handleTouchMove, handleTouchStart, handleTouchEnd]);\n\n\t/**\n\t * 현재 마우스 상태 가져오기\n\t */\n\tconst getState = useCallback((): MouseState => {\n\t\treturn { ...mouseStateRef.current };\n\t}, []);\n\n\treturn {\n\t\tgetState,\n\t};\n};\n","import { Point } from '@/types';\r\nimport { SpringPhysicsConfig, SpringState } from '@/types/interaction';\r\n\r\n/**\r\n * 스프링 기반 물리 시뮬레이션 엔진\r\n * Hooke's Law와 감쇠를 적용한 스프링-댐퍼 시스템\r\n */\r\nexport class SpringPhysics {\r\n\tprivate config: SpringPhysicsConfig;\r\n\tprivate state: SpringState;\r\n\r\n\tconstructor(config: SpringPhysicsConfig) {\r\n\t\tthis.config = config;\r\n\t\tthis.state = {\r\n\t\t\tdisplacement: { x: 0, y: 0 },\r\n\t\t\tvelocity: { x: 0, y: 0 },\r\n\t\t\ttarget: { x: 0, y: 0 },\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * 물리 파라미터 업데이트\r\n\t */\r\n\tpublic setConfig(config: Partial) {\r\n\t\tthis.config = { ...this.config, ...config };\r\n\t}\r\n\r\n\t/**\r\n\t * 목표 위치 설정 (마우스 속도 기반)\r\n\t */\r\n\tpublic setTarget(velocity: Point, velocityMultiplier: number = 1.0) {\r\n\t\t// 속도에 승수를 곱해서 목표 변위로 설정\r\n\t\tthis.state.target = {\r\n\t\t\tx: velocity.x * velocityMultiplier,\r\n\t\t\ty: velocity.y * velocityMultiplier,\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * 초기 속도 설정 (드래그 방향과 속도를 즉시 반영)\r\n\t * 드래그 방향으로 즉시 튕기는 효과\r\n\t */\r\n\tpublic setInitialVelocity(velocity: Point, multiplier: number = 1.0) {\r\n\t\t// 현재 속도를 즉시 변경\r\n\t\tthis.state.velocity = {\r\n\t\t\tx: velocity.x * multiplier,\r\n\t\t\ty: velocity.y * multiplier,\r\n\t\t};\r\n\t\t// 목표는 0 (평형 상태로 돌아가도록)\r\n\t\tthis.state.target = { x: 0, y: 0 };\r\n\t}\r\n\r\n\t/**\r\n\t * 스프링 물리 업데이트 (Hooke's Law + Damping)\r\n\t * F = -k * x - c * v\r\n\t * a = F / m\r\n\t * v += a * dt\r\n\t * x += v * dt\r\n\t */\r\n\tpublic update(deltaTime: number): Point {\r\n\t\tconst { stiffness, damping, mass } = this.config;\r\n\t\tconst { displacement, velocity, target } = this.state;\r\n\r\n\t\t// 평형 위치로부터의 변위 (target은 마우스 속도에서 계산된 목표)\r\n\t\tconst dx = displacement.x - target.x;\r\n\t\tconst dy = displacement.y - target.y;\r\n\r\n\t\t// 스프링 힘: F = -k * x (복원력)\r\n\t\tconst springForceX = -stiffness * dx;\r\n\t\tconst springForceY = -stiffness * dy;\r\n\r\n\t\t// 감쇠 힘: F = -c * v (마찰력)\r\n\t\tconst dampingForceX = -damping * velocity.x;\r\n\t\tconst dampingForceY = -damping * velocity.y;\r\n\r\n\t\t// 총 힘\r\n\t\tconst totalForceX = springForceX + dampingForceX;\r\n\t\tconst totalForceY = springForceY + dampingForceY;\r\n\r\n\t\t// 가속도: a = F / m\r\n\t\tconst accelerationX = totalForceX / mass;\r\n\t\tconst accelerationY = totalForceY / mass;\r\n\r\n\t\t// 속도 업데이트: v += a * dt\r\n\t\tconst newVelocityX = velocity.x + accelerationX * deltaTime;\r\n\t\tconst newVelocityY = velocity.y + accelerationY * deltaTime;\r\n\r\n\t\t// 위치 업데이트: x += v * dt\r\n\t\tconst newDisplacementX = displacement.x + newVelocityX * deltaTime;\r\n\t\tconst newDisplacementY = displacement.y + newVelocityY * deltaTime;\r\n\r\n\t\t// 상태 저장\r\n\t\tthis.state = {\r\n\t\t\tdisplacement: { x: newDisplacementX, y: newDisplacementY },\r\n\t\t\tvelocity: { x: newVelocityX, y: newVelocityY },\r\n\t\t\ttarget,\r\n\t\t};\r\n\r\n\t\t// 매우 작은 움직임은 0으로 처리 (정지 판정)\r\n\t\tconst isNearlyZero = (val: number) => Math.abs(val) < 0.0001;\r\n\t\tif (isNearlyZero(this.state.displacement.x) && isNearlyZero(this.state.displacement.y) &&\r\n\t\t\tisNearlyZero(this.state.velocity.x) && isNearlyZero(this.state.velocity.y)) {\r\n\t\t\tthis.reset();\r\n\t\t}\r\n\r\n\t\treturn this.state.displacement;\r\n\t}\r\n\r\n\t/**\r\n\t * 즉시 충격 적용 (마우스 가속도 기반)\r\n\t */\r\n\tpublic applyImpulse(acceleration: Point, multiplier: number = 1.0) {\r\n\t\t// 가속도를 속도로 변환하여 즉시 적용\r\n\t\tthis.state.velocity.x += acceleration.x * multiplier;\r\n\t\tthis.state.velocity.y += acceleration.y * multiplier;\r\n\t}\r\n\r\n\t/**\r\n\t * 현재 변위 가져오기\r\n\t */\r\n\tpublic getDisplacement(): Point {\r\n\t\treturn { ...this.state.displacement };\r\n\t}\r\n\r\n\t/**\r\n\t * 현재 속도 가져오기\r\n\t */\r\n\tpublic getVelocity(): Point {\r\n\t\treturn { ...this.state.velocity };\r\n\t}\r\n\r\n\t/**\r\n\t * 상태 리셋\r\n\t */\r\n\tpublic reset() {\r\n\t\tthis.state = {\r\n\t\t\tdisplacement: { x: 0, y: 0 },\r\n\t\t\tvelocity: { x: 0, y: 0 },\r\n\t\t\ttarget: { x: 0, y: 0 },\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * 마우스가 멈췄을 때 목표를 0으로 설정 (평형 상태로 복귀)\r\n\t */\r\n\tpublic returnToEquilibrium() {\r\n\t\tthis.state.target = { x: 0, y: 0 };\r\n\t}\r\n}\r\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;","import React, {useRef, useEffect, useState, useCallback, useMemo} from 'react';\r\nimport {DistortionArea, Point} from '@/types';\r\nimport {ImageDistortion} from '@/components/ImageDistortion';\r\nimport {EditorCanvasStyle} from '../types';\r\nimport {DEFAULT_EDITOR_CANVAS_STYLE} from '@/editor';\r\n\r\nexport interface EditorCanvasProps {\r\n\tareas: DistortionArea[];\r\n\tselectedAreaId: string | null;\r\n\timageSrc: string;\r\n\twidth: number;\r\n\theight: number;\r\n\tonUpdatePoint: (areaId: string, pointIndex: number, point: Point) => void;\r\n\tonUpdateArea: (areaId: string, updates: Partial) => void;\r\n\tdraggingPointIndex: number | null;\r\n\tonStartDragging: (pointIndex: number) => void;\r\n\tonStopDragging: () => void;\r\n\t/** 에디터 캔버스 스타일 커스터마이징 */\r\n\tstyle?: EditorCanvasStyle;\r\n\t/** 에디터 UI 표시 여부 (기본값: true) */\r\n\tshowEditor?: boolean;\r\n}\r\n\r\nexport const EditorCanvas: React.FC = ({\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t areas,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t selectedAreaId,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t imageSrc,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t width,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t height,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onUpdatePoint,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onUpdateArea,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t draggingPointIndex,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onStartDragging,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onStopDragging,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t style: customStyle,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t showEditor = true,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t }) => {\r\n\tconst containerRef = useRef(null);\r\n\tconst [canvasSize, setCanvasSize] = useState({width: 0, height: 0});\r\n\tconst [isDraggingArea, setIsDraggingArea] = useState(false);\r\n\tconst [dragStartPos, setDragStartPos] = useState(null);\r\n\r\n\t// 스타일 병합 (커스텀 스타일 우선)\r\n\tconst editorStyle = useMemo(() => ({\r\n\t\t...DEFAULT_EDITOR_CANVAS_STYLE,\r\n\t\t...customStyle,\r\n\t\tcircleLevels: customStyle?.circleLevels || DEFAULT_EDITOR_CANVAS_STYLE.circleLevels,\r\n\t\tcenterPoint: {\r\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.centerPoint,\r\n\t\t\t...customStyle?.centerPoint,\r\n\t\t},\r\n\t\tpointHandle: {\r\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.pointHandle,\r\n\t\t\t...customStyle?.pointHandle,\r\n\t\t},\r\n\t\tareaOutline: {\r\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.areaOutline,\r\n\t\t\t...customStyle?.areaOutline,\r\n\t\t},\r\n\t}), [customStyle]);\r\n\r\n\t// 컨테이너 크기 측정\r\n\tuseEffect(() => {\r\n\t\tif (!containerRef.current) return;\r\n\t\tconst rect = containerRef.current.getBoundingClientRect();\r\n\t\tsetCanvasSize({width: rect.width, height: rect.height});\r\n\t}, [width, height]);\r\n\r\n\t// 선택된 영역 찾기\r\n\tconst selectedArea = areas.find((a) => a.id === selectedAreaId);\r\n\r\n\t// 점이 사각형 내부에 있는지 확인 (Point-in-Polygon test)\r\n\tconst isPointInPolygon = useCallback((point: Point, polygon: Point[]): boolean => {\r\n\t\tlet inside = false;\r\n\t\tfor (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\r\n\t\t\tconst xi = polygon[i].x, yi = polygon[i].y;\r\n\t\t\tconst xj = polygon[j].x, yj = polygon[j].y;\r\n\r\n\t\t\tconst intersect = ((yi > point.y) !== (yj > point.y))\r\n\t\t\t\t&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);\r\n\t\t\tif (intersect) inside = !inside;\r\n\t\t}\r\n\t\treturn inside;\r\n\t}, []);\r\n\r\n\t// 포인트 핸들 클릭/터치 핸들러\r\n\tconst handlePointDown = useCallback(\r\n\t\t(pointIndex: number) => (e: React.MouseEvent | React.TouchEvent) => {\r\n\t\t\te.preventDefault();\r\n\t\t\te.stopPropagation();\r\n\t\t\tonStartDragging(pointIndex);\r\n\t\t},\r\n\t\t[onStartDragging]\r\n\t);\r\n\r\n\t// 캔버스 다운 (마우스/터치 공통)\r\n\tconst handleCanvasDown = useCallback(\r\n\t\t(e: React.MouseEvent | React.TouchEvent) => {\r\n\t\t\t// 에디터가 숨겨진 상태면 동작하지 않음\r\n\t\t\tif (!showEditor || !selectedArea || !containerRef.current) return;\r\n\r\n\t\t\tconst rect = containerRef.current.getBoundingClientRect();\r\n\r\n\t\t\t// 마우스 또는 터치 좌표 추출\r\n\t\t\tlet clientX: number, clientY: number;\r\n\t\t\tif ('touches' in e) {\r\n\t\t\t\tif (e.touches.length === 0) return;\r\n\t\t\t\tclientX = e.touches[0].clientX;\r\n\t\t\t\tclientY = e.touches[0].clientY;\r\n\t\t\t} else {\r\n\t\t\t\tclientX = e.clientX;\r\n\t\t\t\tclientY = e.clientY;\r\n\t\t\t}\r\n\r\n\t\t\tconst x = (clientX - rect.left) / rect.width;\r\n\t\t\tconst y = (clientY - rect.top) / rect.height;\r\n\t\t\tconst clickPoint = { x, y };\r\n\r\n\t\t\t// 사각형 내부를 클릭했는지 확인\r\n\t\t\tif (isPointInPolygon(clickPoint, selectedArea.basePoints)) {\r\n\t\t\t\tsetIsDraggingArea(true);\r\n\t\t\t\tsetDragStartPos(clickPoint);\r\n\t\t\t\te.preventDefault();\r\n\t\t\t}\r\n\t\t},\r\n\t\t[showEditor, selectedArea, isPointInPolygon]\r\n\t);\r\n\r\n\t// 이동 (마우스/터치 공통)\r\n\tconst handleMove = useCallback(\r\n\t\t(e: React.MouseEvent | React.TouchEvent) => {\r\n\t\t\t// 에디터가 숨겨진 상태면 동작하지 않음\r\n\t\t\tif (!showEditor || !selectedArea || !containerRef.current) return;\r\n\r\n\t\t\t// 터치 이벤트면 스크롤 방지\r\n\t\t\tif ('touches' in e && (draggingPointIndex !== null || isDraggingArea)) {\r\n\t\t\t\te.preventDefault();\r\n\t\t\t}\r\n\r\n\t\t\tconst rect = containerRef.current.getBoundingClientRect();\r\n\r\n\t\t\t// 마우스 또는 터치 좌표 추출\r\n\t\t\tlet clientX: number, clientY: number;\r\n\t\t\tif ('touches' in e) {\r\n\t\t\t\tif (e.touches.length === 0) return;\r\n\t\t\t\tclientX = e.touches[0].clientX;\r\n\t\t\t\tclientY = e.touches[0].clientY;\r\n\t\t\t} else {\r\n\t\t\t\tclientX = e.clientX;\r\n\t\t\t\tclientY = e.clientY;\r\n\t\t\t}\r\n\r\n\t\t\tconst x = (clientX - rect.left) / rect.width;\r\n\t\t\tconst y = (clientY - rect.top) / rect.height;\r\n\r\n\t\t\t// 포인트 드래그 중\r\n\t\t\tif (draggingPointIndex !== null) {\r\n\t\t\t\tconst clampedX = Math.max(0, Math.min(1, x));\r\n\t\t\t\tconst clampedY = Math.max(0, Math.min(1, y));\r\n\t\t\t\tonUpdatePoint(selectedArea.id, draggingPointIndex, {x: clampedX, y: clampedY});\r\n\t\t\t}\r\n\t\t\t// 사각형 전체 드래그 중\r\n\t\t\telse if (isDraggingArea && dragStartPos) {\r\n\t\t\t\tconst deltaX = x - dragStartPos.x;\r\n\t\t\t\tconst deltaY = y - dragStartPos.y;\r\n\r\n\t\t\t\t// 모든 포인트를 delta만큼 이동\r\n\t\t\t\tconst newPoints = selectedArea.basePoints.map((point) => ({\r\n\t\t\t\t\tx: Math.max(0, Math.min(1, point.x + deltaX)),\r\n\t\t\t\t\ty: Math.max(0, Math.min(1, point.y + deltaY)),\r\n\t\t\t\t})) as [Point, Point, Point, Point];\r\n\r\n\t\t\t\tonUpdateArea(selectedArea.id, { basePoints: newPoints });\r\n\t\t\t\tsetDragStartPos({ x, y }); // 다음 프레임을 위해 시작 위치 업데이트\r\n\t\t\t}\r\n\t\t},\r\n\t\t[showEditor, draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]\r\n\t);\r\n\r\n\t// 업 (마우스/터치 공통)\r\n\tconst handleUp = useCallback(() => {\r\n\t\tif (draggingPointIndex !== null) {\r\n\t\t\tonStopDragging();\r\n\t\t}\r\n\t\tif (isDraggingArea) {\r\n\t\t\tsetIsDraggingArea(false);\r\n\t\t\tsetDragStartPos(null);\r\n\t\t}\r\n\t}, [draggingPointIndex, isDraggingArea, onStopDragging]);\r\n\r\n\t// 전역 업 이벤트 (마우스/터치)\r\n\tuseEffect(() => {\r\n\t\tif (draggingPointIndex !== null || isDraggingArea) {\r\n\t\t\twindow.addEventListener('mouseup', handleUp);\r\n\t\t\twindow.addEventListener('touchend', handleUp);\r\n\t\t\twindow.addEventListener('touchcancel', handleUp);\r\n\t\t\treturn () => {\r\n\t\t\t\twindow.removeEventListener('mouseup', handleUp);\r\n\t\t\t\twindow.removeEventListener('touchend', handleUp);\r\n\t\t\t\twindow.removeEventListener('touchcancel', handleUp);\r\n\t\t\t};\r\n\t\t}\r\n\t}, [draggingPointIndex, isDraggingArea, handleUp]);\r\n\r\n\t// UV 좌표를 픽셀 좌표로 변환 (셰이더와 동일한 bilinear interpolation)\r\n\tconst uvToPixel = (\r\n\t\tu: number,\r\n\t\tv: number,\r\n\t\tpoints: [Point, Point, Point, Point],\r\n\t\tcanvasWidth: number,\r\n\t\tcanvasHeight: number\r\n\t): { x: number; y: number } => {\r\n\t\t// p0=좌상, p1=우상, p2=우하, p3=좌하\r\n\t\tconst [p0, p1, p2, p3] = points;\r\n\r\n\t\t// 셰이더 computeUV와 동일한 순서로 bilinear interpolation\r\n\t\t// left = mix(p0, p1, u) -> 상단 가장자리\r\n\t\t// right = mix(p3, p2, u) -> 하단 가장자리\r\n\t\t// position = mix(left, right, v)\r\n\t\tconst leftX = p0.x * (1 - u) + p1.x * u;\r\n\t\tconst leftY = p0.y * (1 - u) + p1.y * u;\r\n\r\n\t\tconst rightX = p3.x * (1 - u) + p2.x * u;\r\n\t\tconst rightY = p3.y * (1 - u) + p2.y * u;\r\n\r\n\t\tconst posX = leftX * (1 - v) + rightX * v;\r\n\t\tconst posY = leftY * (1 - v) + rightY * v;\r\n\r\n\t\treturn {\r\n\t\t\tx: posX * canvasWidth,\r\n\t\t\ty: posY * canvasHeight,\r\n\t\t};\r\n\t};\r\n\r\n\t// UV 좌표계의 원을 정확히 그리기 (찌그러진 원 형태)\r\n\tconst drawDistortionCircle = useCallback((\r\n\t\tctx: CanvasRenderingContext2D,\r\n\t\tpoints: [Point, Point, Point, Point],\r\n\t\tcanvasWidth: number,\r\n\t\tcanvasHeight: number\r\n\t) => {\r\n\t\tconst segments = 128; // 원을 128개 세그먼트로 촘촘히 분할\r\n\t\tconst centerU = 0.5;\r\n\t\tconst centerV = 0.5;\r\n\r\n\t\tconst circleLevels = editorStyle.circleLevels || [];\r\n\r\n\t\t// 원 레벨별로 그리기 (외부 -> 내부 순)\r\n\t\tcircleLevels.forEach((level, index) => {\r\n\t\t\tconst levelPoints: { x: number; y: number }[] = [];\r\n\t\t\tfor (let i = 0; i <= segments; i++) {\r\n\t\t\t\tconst theta = (i / segments) * 2 * Math.PI;\r\n\t\t\t\tconst u = centerU - level.radius * Math.sin(theta);\r\n\t\t\t\tconst v = centerV + level.radius * Math.cos(theta);\r\n\t\t\t\tconst pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);\r\n\t\t\t\tlevelPoints.push(pixelPos);\r\n\t\t\t}\r\n\r\n\t\t\tctx.beginPath();\r\n\t\t\tctx.moveTo(levelPoints[0].x, levelPoints[0].y);\r\n\t\t\tfor (let i = 1; i < levelPoints.length; i++) {\r\n\t\t\t\tctx.lineTo(levelPoints[i].x, levelPoints[i].y);\r\n\t\t\t}\r\n\t\t\tctx.closePath();\r\n\r\n\t\t\t// 원 테두리\r\n\t\t\tconst baseColor = level.color || 'rgba(255, 200, 0, 1)';\r\n\t\t\t// baseColor에서 RGB 추출하고 opacity 적용\r\n\t\t\tconst colorWithOpacity = baseColor.replace(/rgba?\\(([^)]+)\\)/, (_, rgb) => {\r\n\t\t\t\tconst parts = rgb.split(',').map((p: string) => p.trim());\r\n\t\t\t\treturn `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${level.opacity})`;\r\n\t\t\t});\r\n\t\t\tctx.strokeStyle = colorWithOpacity;\r\n\t\t\tctx.lineWidth = level.lineWidth;\r\n\t\t\tif (level.dashPattern) {\r\n\t\t\t\tctx.setLineDash(level.dashPattern);\r\n\t\t\t}\r\n\t\t\tctx.stroke();\r\n\t\t\tctx.setLineDash([]);\r\n\r\n\t\t\t// 가장 외부 원만 내부 채우기\r\n\t\t\tif (index === 0 && editorStyle.circleFillColor) {\r\n\t\t\t\tctx.fillStyle = editorStyle.circleFillColor;\r\n\t\t\t\tctx.fill();\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// 중심점 표시\r\n\t\tconst centerPointStyle = editorStyle.centerPoint || {};\r\n\t\tconst centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight);\r\n\t\tctx.beginPath();\r\n\t\tctx.arc(centerPixel.x, centerPixel.y, centerPointStyle.radius || 5, 0, 2 * Math.PI);\r\n\t\tif (centerPointStyle.fillColor) {\r\n\t\t\tctx.fillStyle = centerPointStyle.fillColor;\r\n\t\t\tctx.fill();\r\n\t\t}\r\n\t\tif (centerPointStyle.strokeColor) {\r\n\t\t\tctx.strokeStyle = centerPointStyle.strokeColor;\r\n\t\t\tctx.lineWidth = centerPointStyle.strokeWidth || 2;\r\n\t\t\tctx.stroke();\r\n\t\t}\r\n\t}, [editorStyle]);\r\n\r\n\t// 커서 스타일 결정\r\n\tconst getCursorStyle = () => {\r\n\t\tif (draggingPointIndex !== null) return 'grabbing';\r\n\t\tif (isDraggingArea) return 'grabbing';\r\n\t\treturn 'default';\r\n\t};\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t{/* ImageDistortion 컴포넌트 */}\r\n\t\t\t\r\n\r\n\t\t\t{/* 오버레이 SVG - 에디터 모드일 때만 표시 */}\r\n\t\t\t{showEditor && (\r\n\t\t\t\t\r\n\t\t\t\t\t{/* 모든 영역의 사각형 표시 */}\r\n\t\t\t\t\t{areas.map((area) => {\r\n\t\t\t\t\tconst isSelected = area.id === selectedAreaId;\r\n\t\t\t\t\tconst points = area.basePoints;\r\n\t\t\t\t\tconst outlineStyle = editorStyle.areaOutline || {};\r\n\t\t\t\t\treturn (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{/* 사각형 배경 및 경계선 */}\r\n\t\t\t\t\t\t\t `${p.x * canvasSize.width},${p.y * canvasSize.height}`)\r\n\t\t\t\t\t\t\t\t\t.join(' ')}\r\n\t\t\t\t\t\t\t\tfill={isSelected ? (outlineStyle.selectedFillColor || 'rgba(0, 170, 255, 0.08)') : (outlineStyle.unselectedFillColor || 'rgba(136, 136, 136, 0.03)')}\r\n\t\t\t\t\t\t\t\tstroke={isSelected ? (outlineStyle.selectedColor || '#00aaff') : (outlineStyle.unselectedColor || '#888')}\r\n\t\t\t\t\t\t\t\tstrokeWidth={isSelected ? (outlineStyle.selectedWidth || 2) : (outlineStyle.unselectedWidth || 1)}\r\n\t\t\t\t\t\t\t\tstrokeDasharray={isSelected ? '0' : (outlineStyle.unselectedDashPattern?.join(',') || '5,5')}\r\n\t\t\t\t\t\t\t\topacity={isSelected ? 1 : 0.5}\r\n\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t);\r\n\t\t\t\t\t})}\r\n\t\t\t\t\r\n\t\t\t)}\r\n\r\n\t\t\t{/* 선택된 영역의 타원 가이드 (Canvas로 그리기) - 에디터 모드일 때만 표시 */}\r\n\t\t\t{showEditor && selectedArea && canvasSize.width > 0 && (\r\n\t\t\t\t {\r\n\t\t\t\t\t\tif (canvas) {\r\n\t\t\t\t\t\t\tconst ctx = canvas.getContext('2d');\r\n\t\t\t\t\t\t\tif (ctx) {\r\n\t\t\t\t\t\t\t\tctx.clearRect(0, 0, canvasSize.width, canvasSize.height);\r\n\t\t\t\t\t\t\t\tdrawDistortionCircle(ctx, selectedArea.basePoints, canvasSize.width, canvasSize.height);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}}\r\n\t\t\t\t/>\r\n\t\t\t)}\r\n\r\n\t\t\t{/* 선택된 영역의 포인트 핸들 - 에디터 모드일 때만 표시 */}\r\n\t\t\t{showEditor && selectedArea &&\r\n\t\t\t\tselectedArea.basePoints.map((point, index) => {\r\n\t\t\t\t\tconst handleStyle = editorStyle.pointHandle || {};\r\n\t\t\t\t\treturn (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\tP{index + 1}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t);\r\n\t\t\t\t})}\r\n\t\t\r\n\t);\r\n};\r\n","import React from 'react';\nimport { DistortionArea } from '../../types/area';\n\nexport interface AreaListProps {\n\tareas: DistortionArea[];\n\tselectedAreaId: string | null;\n\tonSelectArea: (areaId: string) => void;\n\tonRemoveArea: (areaId: string) => void;\n\tonAddArea: () => void;\n}\n\nexport const AreaList: React.FC = ({\n\tareas,\n\tselectedAreaId,\n\tonSelectArea,\n\tonRemoveArea,\n\tonAddArea,\n}) => {\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t

왜곡 영역

\n\t\t\t\t= 8}\n\t\t\t\t\tclassName=\"btn-add\"\n\t\t\t\t\ttitle={areas.length >= 8 ? '최대 8개 영역까지 지원' : '새 영역 추가'}\n\t\t\t\t>\n\t\t\t\t\t+ 추가\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t\t{areas.length === 0 ? (\n\t\t\t\t\t
영역이 없습니다. + 추가 버튼을 눌러주세요.
\n\t\t\t\t) : (\n\t\t\t\t\tareas.map((area, index) => (\n\t\t\t\t\t\t onSelectArea(area.id)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t영역 {index + 1}\n\t\t\t\t\t\t\t\t강도: {(area.distortionStrength * 100).toFixed(0)}%\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t\t\t\t\tonRemoveArea(area.id);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\tclassName=\"btn-remove\"\n\t\t\t\t\t\t\t\ttitle=\"영역 삭제\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t))\n\t\t\t\t)}\n\t\t\t
\n\t\t\n\t);\n};\n","import React from 'react';\nimport { DistortionArea, EasingFunction } from '../../types/area';\n\nexport interface ParameterPanelProps {\n\tarea: DistortionArea | null;\n\tonUpdateArea: (updates: Partial) => void;\n}\n\nconst EASING_OPTIONS: { value: EasingFunction; label: string }[] = [\n\t{ value: 'linear', label: '선형 (Linear)' },\n\t{ value: 'easeIn', label: '가속 (Ease In)' },\n\t{ value: 'easeOut', label: '감속 (Ease Out)' },\n\t{ value: 'easeInOut', label: '가감속 (Ease In Out)' },\n\t{ value: 'easeInQuad', label: '가속² (Ease In Quad)' },\n\t{ value: 'easeOutQuad', label: '감속² (Ease Out Quad)' },\n];\n\nexport const ParameterPanel: React.FC = ({ area, onUpdateArea }) => {\n\tif (!area) {\n\t\treturn (\n\t\t\t
\n\t\t\t\t
영역을 선택해주세요
\n\t\t\t
\n\t\t);\n\t}\n\n\treturn (\n\t\t
\n\t\t\t

파라미터 편집

\n\n\t\t\t{/* 왜곡 강도 */}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t onUpdateArea({ distortionStrength: parseFloat(e.target.value) })}\n\t\t\t\t\tclassName=\"slider\"\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{/* 애니메이션 지속 시간 */}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\tonUpdateArea({\n\t\t\t\t\t\t\tmovement: { ...area.movement, duration: parseFloat(e.target.value) },\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tclassName=\"input-number\"\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{/* 이징 함수 */}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\tonUpdateArea({\n\t\t\t\t\t\t\tmovement: { ...area.movement, easing: e.target.value as EasingFunction },\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tclassName=\"select\"\n\t\t\t\t>\n\t\t\t\t\t{EASING_OPTIONS.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t
\n\n\t\t\t{/* 포인트 좌표 (읽기 전용 표시) */}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t{area.basePoints.map((point, idx) => (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\tP{idx + 1}: ({point.x.toFixed(3)}, {point.y.toFixed(3)})\n\t\t\t\t\t\t
\n\t\t\t\t\t))}\n\t\t\t\t
\n\t\t\t
\n\t\t
\n\t);\n};\n","import { useState, useCallback } from 'react';\r\nimport { DistortionArea, Point } from '../../types/area';\r\nimport { EditorState } from '../types';\r\n\r\nexport const useDistortionEditor = (initialAreas: DistortionArea[] = []) => {\r\n\tconst [state, setState] = useState({\r\n\t\tselectedAreaId: initialAreas[0]?.id || null,\r\n\t\tareas: initialAreas,\r\n\t\teditMode: 'normal',\r\n\t\tdraggingPointIndex: null,\r\n\t});\r\n\r\n\t/** 영역 선택 */\r\n\tconst selectArea = useCallback((areaId: string | null) => {\r\n\t\tsetState((prev) => ({ ...prev, selectedAreaId: areaId }));\r\n\t}, []);\r\n\r\n\t/** 영역 추가 */\r\n\tconst addArea = useCallback((area: DistortionArea) => {\r\n\t\tsetState((prev) => ({\r\n\t\t\t...prev,\r\n\t\t\tareas: [...prev.areas, area],\r\n\t\t\tselectedAreaId: area.id,\r\n\t\t}));\r\n\t}, []);\r\n\r\n\t/** 영역 삭제 */\r\n\tconst removeArea = useCallback((areaId: string) => {\r\n\t\tsetState((prev) => {\r\n\t\t\tconst newAreas = prev.areas.filter((a) => a.id !== areaId);\r\n\t\t\treturn {\r\n\t\t\t\t...prev,\r\n\t\t\t\tareas: newAreas,\r\n\t\t\t\tselectedAreaId:\r\n\t\t\t\t\tprev.selectedAreaId === areaId ? newAreas[0]?.id || null : prev.selectedAreaId,\r\n\t\t\t};\r\n\t\t});\r\n\t}, []);\r\n\r\n\t/** 영역 업데이트 */\r\n\tconst updateArea = useCallback((areaId: string, updates: Partial) => {\r\n\t\tsetState((prev) => ({\r\n\t\t\t...prev,\r\n\t\t\tareas: prev.areas.map((area) => (area.id === areaId ? { ...area, ...updates } : area)),\r\n\t\t}));\r\n\t}, []);\r\n\r\n\t/** 포인트 업데이트 */\r\n\tconst updatePoint = useCallback((areaId: string, pointIndex: number, point: Point) => {\r\n\t\tsetState((prev) => ({\r\n\t\t\t...prev,\r\n\t\t\tareas: prev.areas.map((area) => {\r\n\t\t\t\tif (area.id === areaId) {\r\n\t\t\t\t\tconst newPoints = [...area.basePoints] as [Point, Point, Point, Point];\r\n\t\t\t\t\tnewPoints[pointIndex] = point;\r\n\t\t\t\t\treturn { ...area, basePoints: newPoints };\r\n\t\t\t\t}\r\n\t\t\t\treturn area;\r\n\t\t\t}),\r\n\t\t}));\r\n\t}, []);\r\n\r\n\t/** 드래그 시작 */\r\n\tconst startDragging = useCallback((pointIndex: number) => {\r\n\t\tsetState((prev) => ({ ...prev, draggingPointIndex: pointIndex }));\r\n\t}, []);\r\n\r\n\t/** 드래그 종료 */\r\n\tconst stopDragging = useCallback(() => {\r\n\t\tsetState((prev) => ({ ...prev, draggingPointIndex: null }));\r\n\t}, []);\r\n\r\n\t/** 편집 모드 변경 */\r\n\tconst setEditMode = useCallback((mode: EditorState['editMode']) => {\r\n\t\tsetState((prev) => ({ ...prev, editMode: mode }));\r\n\t}, []);\r\n\r\n\t/** 선택된 영역 가져오기 */\r\n\tconst getSelectedArea = useCallback(() => {\r\n\t\treturn state.areas.find((a) => a.id === state.selectedAreaId) || null;\r\n\t}, [state.areas, state.selectedAreaId]);\r\n\r\n\treturn {\r\n\t\tstate,\r\n\t\tselectArea,\r\n\t\taddArea,\r\n\t\tremoveArea,\r\n\t\tupdateArea,\r\n\t\tupdatePoint,\r\n\t\tstartDragging,\r\n\t\tstopDragging,\r\n\t\tsetEditMode,\r\n\t\tgetSelectedArea,\r\n\t};\r\n};\r\n","import { EditorCanvasStyle } from './types';\n\n/**\n * 기본 에디터 캔버스 스타일\n */\nexport const DEFAULT_EDITOR_CANVAS_STYLE: EditorCanvasStyle = {\n\t// 3단계 원 스타일 (외부 -> 내부)\n\tcircleLevels: [\n\t\t{\n\t\t\tradius: 0.5,\n\t\t\topacity: 0.3,\n\t\t\tlineWidth: 2,\n\t\t\tcolor: 'rgba(255, 200, 0, 1)',\n\t\t\tdashPattern: [8, 4],\n\t\t},\n\t\t{\n\t\t\tradius: 0.33,\n\t\t\topacity: 0.6,\n\t\t\tlineWidth: 2.5,\n\t\t\tcolor: 'rgba(255, 200, 0, 1)',\n\t\t\tdashPattern: [8, 4],\n\t\t},\n\t\t{\n\t\t\tradius: 0.167,\n\t\t\topacity: 0.9,\n\t\t\tlineWidth: 3,\n\t\t\tcolor: 'rgba(255, 200, 0, 1)',\n\t\t\tdashPattern: [8, 4],\n\t\t},\n\t],\n\t// 원 내부 채우기\n\tcircleFillColor: 'rgba(255, 200, 0, 0.08)',\n\t// 중심점\n\tcenterPoint: {\n\t\tradius: 5,\n\t\tfillColor: 'rgba(255, 200, 0, 1)',\n\t\tstrokeColor: 'rgba(255, 255, 255, 0.8)',\n\t\tstrokeWidth: 2,\n\t},\n\t// 포인트 핸들\n\tpointHandle: {\n\t\tsize: 16,\n\t\tfillColor: '#00aaff',\n\t\tstrokeColor: 'white',\n\t\tstrokeWidth: 2,\n\t\tlabelColor: '#00aaff',\n\t\tlabelFontSize: 11,\n\t},\n\t// 영역 외곽선\n\tareaOutline: {\n\t\tselectedColor: '#00aaff',\n\t\tunselectedColor: '#888',\n\t\tselectedWidth: 2,\n\t\tunselectedWidth: 1,\n\t\tunselectedDashPattern: [5, 5],\n\t\tselectedFillColor: 'rgba(0, 170, 255, 0.08)', // 선택된 영역 배경 (연한 파란색)\n\t\tunselectedFillColor: 'rgba(136, 136, 136, 0.03)', // 선택 안된 영역 배경 (연한 회색)\n\t},\n};\n"],"mappings":"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","names":["import_react","THREE","import_react","import_react","import_react","import_jsx_runtime","import_jsx_runtime","import_react","import_jsx_runtime","isPointInPolygon"]} \ 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/utils/motionPresets.ts","../src/engine/AnimationLoop.ts","../src/hooks/useAnimationFrame.ts","../src/hooks/useMouseInteraction.ts","../src/hooks/useMouseVelocity.ts","../src/engine/SpringPhysics.ts","../src/utils/constants.ts","../src/editor/components/EditorCanvas.tsx","../src/editor/components/AreaList.tsx","../src/editor/components/ParameterPanel.tsx","../src/editor/hooks/useDistortionEditor.ts","../src/editor/constants.ts"],"sourcesContent":["// 메인 컴포넌트\nexport { ImageDistortion } from './components/ImageDistortion';\nexport type { ImageDistortionProps } from './components/ImageDistortion';\n\n// 에디터 컴포넌트들 (개별적으로 조합 가능)\nexport { EditorCanvas } from './editor/components/EditorCanvas';\nexport type { EditorCanvasProps } from './editor/components/EditorCanvas';\nexport { AreaList } from './editor/components/AreaList';\nexport type { AreaListProps } from './editor/components/AreaList';\nexport { ParameterPanel } from './editor/components/ParameterPanel';\nexport type { ParameterPanelProps } from './editor/components/ParameterPanel';\n\n// 에디터 상태 관리 훅\nexport { useDistortionEditor } from './editor/hooks/useDistortionEditor';\n\n// 에디터 타입 및 스타일\nexport type {\n EditorState,\n EditMode,\n EditorCanvasStyle,\n CircleLevelStyle,\n CenterPointStyle,\n PointHandleStyle,\n AreaOutlineStyle,\n} from './editor/types';\n\n// 에디터 기본 스타일 상수\nexport { DEFAULT_EDITOR_CANVAS_STYLE } from './editor/constants';\n\n// 타입 정의\nexport type {\n Point,\n EasingFunction,\n BuiltInMotionPreset,\n MotionPreset,\n MotionPresetDefinition,\n DistortionMovement,\n DistortionArea,\n AreaBounds,\n ShaderUniforms,\n ShaderConfig,\n AnimationState,\n AnimationTicker,\n} from './types';\n\n// 마우스 인터랙션 타입\nexport type {\n SpringPhysicsConfig,\n MouseInteractionConfig,\n MouseState,\n SpringState,\n} from './types/interaction';\n\n// 유틸리티 함수\nexport { applyEasing } from './utils/easing';\nexport { SHADER_CONFIG, ANIMATION_CONFIG, DEFAULT_AREA } from './utils/constants';\nexport {\n presetToVector,\n isRotationPreset,\n // 프리셋 레지스트리 API\n registerMotionPreset,\n registerMotionPresets,\n unregisterMotionPreset,\n getRegisteredPresets,\n hasPreset,\n resetToBuiltInPresets,\n} from './utils/motionPresets';\n\n// 엔진 클래스 (고급 사용자용)\nexport { ThreeScene } from './engine/ThreeScene';\nexport { ShaderManager } from './engine/ShaderManager';\nexport { AnimationLoop } from './engine/AnimationLoop';\nexport { SpringPhysics } from './engine/SpringPhysics';\n\n// 훅\nexport { useAnimationFrame } from './hooks/useAnimationFrame';\nexport { useMouseVelocity } from './hooks/useMouseVelocity';\nexport { useMouseInteraction } from './hooks/useMouseInteraction';","import React, { useEffect, useRef, useState, useCallback } from 'react';\r\nimport * as THREE from 'three';\r\nimport { type DistortionArea } from '@/types';\r\nimport { ThreeScene } from '@/engine/ThreeScene';\r\nimport { ShaderManager } from '@/engine/ShaderManager';\r\nimport { AnimationLoop } from '@/engine/AnimationLoop';\r\nimport { useAnimationFrame } from '@/hooks/useAnimationFrame';\r\nimport { useMouseInteraction } from '@/hooks/useMouseInteraction';\r\nimport { SHADER_CONFIG } from '@/utils/constants';\r\nimport { MouseInteractionConfig } from '@/types/interaction';\r\n\r\n/**\r\n * ImageDistortion 컴포넌트 Props\r\n */\r\nexport interface ImageDistortionProps {\r\n /** 이미지 소스 URL */\r\n imageSrc: string;\r\n /** 왜곡 영역 배열 */\r\n areas: DistortionArea[];\r\n /** 버텍스 셰이더 경로 (선택사항) */\r\n vertexShaderPath?: string;\r\n /** 프래그먼트 셰이더 경로 (선택사항) */\r\n fragmentShaderPath?: string;\r\n /** 애니메이션 재생 여부 */\r\n isPlaying?: boolean;\r\n /** 컨테이너 스타일 */\r\n style?: React.CSSProperties;\r\n /** 컨테이너 클래스명 */\r\n className?: string;\r\n /** 마우스 인터랙션 설정 */\r\n mouseInteraction?: MouseInteractionConfig;\r\n}\r\n\r\n/**\r\n * GPU 가속 이미지 왜곡 컴포넌트\r\n * Three.js와 GLSL 셰이더를 사용하여 실시간 이미지 왜곡 효과를 제공합니다.\r\n */\r\nexport const ImageDistortion: React.FC = ({\r\n imageSrc,\r\n areas,\r\n vertexShaderPath,\r\n fragmentShaderPath,\r\n isPlaying = true,\r\n style,\r\n className,\r\n mouseInteraction,\r\n}) => {\r\n const containerRef = useRef(null);\r\n const sceneRef = useRef(null);\r\n const shaderManagerRef = useRef(new ShaderManager());\r\n const textureRef = useRef(null);\r\n\r\n const [isReady, setIsReady] = useState(false);\r\n const [imageLoaded, setImageLoaded] = useState(false);\r\n const [currentAreas, setCurrentAreas] = useState(areas);\r\n\r\n // 마우스 인터랙션 훅\r\n const mouseInteractionHook = useMouseInteraction(\r\n containerRef,\r\n mouseInteraction || {\r\n enabled: false,\r\n physics: {\r\n stiffness: 100,\r\n damping: 10,\r\n mass: 1,\r\n influenceRadius: 0.2,\r\n maxStrength: 1.0,\r\n },\r\n }\r\n );\r\n\r\n // 영역 변경 시 상태 업데이트\r\n useEffect(() => {\r\n setCurrentAreas(areas);\r\n }, [areas]);\r\n\r\n // 마우스 인터랙션 설정 변경 시 업데이트\r\n useEffect(() => {\r\n if (mouseInteraction) {\r\n mouseInteractionHook.updateConfig(mouseInteraction);\r\n }\r\n }, [mouseInteraction, mouseInteractionHook]);\r\n\r\n // Three.js 씬 초기화\r\n useEffect(() => {\r\n console.log('[ImageDistortion] useEffect 실행, containerRef.current:', containerRef.current);\r\n\r\n if (!containerRef.current) {\r\n console.warn('[ImageDistortion] containerRef.current가 null입니다. 컴포넌트가 제대로 마운트되지 않았습니다.');\r\n return;\r\n }\r\n\r\n console.log('[ImageDistortion] 초기화 시작');\r\n const scene = new ThreeScene(containerRef.current);\r\n sceneRef.current = scene;\r\n\r\n // 셰이더 로드\r\n const vertPath = vertexShaderPath || '/shaders/distortion.vert.glsl';\r\n const fragPath = fragmentShaderPath || '/shaders/distortion.frag.glsl';\r\n\r\n console.log('[ImageDistortion] 셰이더 로드 시도:', { vertPath, fragPath });\r\n\r\n shaderManagerRef.current\r\n .loadShaders(vertPath, fragPath)\r\n .then(({ vertex, fragment }) => {\r\n console.log('[ImageDistortion] 셰이더 로드 성공');\r\n scene.setShaderMaterial(vertex, fragment);\r\n setIsReady(true);\r\n })\r\n .catch((error) => {\r\n console.error('[ImageDistortion] 셰이더 로드 실패:', error);\r\n });\r\n\r\n return () => {\r\n scene.dispose();\r\n if (textureRef.current) {\r\n textureRef.current.dispose();\r\n }\r\n };\r\n }, [vertexShaderPath, fragmentShaderPath]);\r\n\r\n // 이미지 텍스처 로드\r\n useEffect(() => {\r\n if (!imageSrc || !isReady) {\r\n console.log('[ImageDistortion] 이미지 로드 스킵:', { imageSrc, isReady });\r\n return;\r\n }\r\n\r\n console.log('[ImageDistortion] 이미지 로드 시작:', imageSrc);\r\n setImageLoaded(false);\r\n\r\n const loader = new THREE.TextureLoader();\r\n loader.load(\r\n imageSrc,\r\n (texture) => {\r\n console.log('[ImageDistortion] 이미지 로드 성공!', {\r\n width: texture.image.width,\r\n height: texture.image.height\r\n });\r\n textureRef.current = texture;\r\n setImageLoaded(true);\r\n if (sceneRef.current) {\r\n sceneRef.current.updateUniforms({\r\n u_texture: { value: texture },\r\n });\r\n sceneRef.current.render();\r\n console.log('[ImageDistortion] 텍스처 업데이트 및 렌더링 완료');\r\n }\r\n },\r\n (progress) => {\r\n console.log('[ImageDistortion] 이미지 로딩 중...',\r\n Math.round((progress.loaded / progress.total) * 100) + '%'\r\n );\r\n },\r\n (error) => {\r\n console.error('[ImageDistortion] 이미지 로드 실패:', error);\r\n setImageLoaded(false);\r\n }\r\n );\r\n\r\n return () => {\r\n if (textureRef.current) {\r\n textureRef.current.dispose();\r\n textureRef.current = null;\r\n }\r\n };\r\n }, [imageSrc, isReady]);\r\n\r\n // 셰이더 유니폼 업데이트\r\n useEffect(() => {\r\n if (!sceneRef.current || !isReady) return;\r\n\r\n // 현재 해상도 가져오기\r\n const resolution = sceneRef.current.getResolution();\r\n\r\n // 포인트 배열 생성\r\n // UI는 좌상단 (0,0), WebGL은 좌하단 (0,0)이므로 y 좌표를 반전\r\n const points = new Float32Array(SHADER_CONFIG.MAX_POINTS * 2);\r\n currentAreas.forEach((area, areaIndex) => {\r\n area.basePoints.forEach((point, pointIndex) => {\r\n const index = (areaIndex * 4 + pointIndex) * 2;\r\n points[index] = point.x;\r\n points[index + 1] = 1.0 - point.y; // y 좌표 반전\r\n });\r\n });\r\n\r\n // 드래그 벡터 배열 생성\r\n // dragVector도 y 좌표계를 맞춰야 하므로 y를 반전\r\n const dragVectors = new Float32Array(SHADER_CONFIG.MAX_DRAG_VECTORS * 2);\r\n currentAreas.forEach((area, index) => {\r\n const baseIndex = index * 2;\r\n dragVectors[baseIndex] = area.dragVector.x;\r\n dragVectors[baseIndex + 1] = -area.dragVector.y; // y 방향 반전\r\n });\r\n\r\n // 강도 배열 생성\r\n const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS);\r\n currentAreas.forEach((area, index) => {\r\n strengths[index] = area.distortionStrength;\r\n });\r\n\r\n sceneRef.current.updateUniforms({\r\n u_numAreas: { value: currentAreas.length },\r\n u_points: { value: points },\r\n u_dragVectors: { value: dragVectors },\r\n u_distortionStrengths: { value: strengths },\r\n });\r\n\r\n sceneRef.current.render();\r\n }, [currentAreas, isReady]);\r\n\r\n // 애니메이션 루프\r\n const animationCallback = useCallback((deltaTime: number) => {\r\n if (!isReady) return;\r\n\r\n setCurrentAreas((prevAreas) => {\r\n // 현재 인터랙션 중인 영역 인덱스 가져오기\r\n const interactingIndices = mouseInteractionHook.getInteractingAreaIndices?.() || new Set();\r\n\r\n // 1. 자동 애니메이션 업데이트\r\n let updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);\r\n updatedAreas = AnimationLoop.updateAreaDragVectors(updatedAreas);\r\n\r\n // 인터랙션 중인 영역만 dragVector를 0으로 설정\r\n if (interactingIndices.size > 0) {\r\n updatedAreas = updatedAreas.map((area, index) => {\r\n if (interactingIndices.has(index)) {\r\n return {\r\n ...area,\r\n dragVector: { x: 0, y: 0 }\r\n };\r\n }\r\n return area;\r\n });\r\n }\r\n\r\n // 2. 마우스 인터랙션 적용 (기존 dragVector에 스프링 변위 추가)\r\n if (mouseInteraction?.enabled) {\r\n updatedAreas = mouseInteractionHook.updateInteraction(updatedAreas, deltaTime);\r\n }\r\n\r\n return updatedAreas;\r\n });\r\n }, [isReady, mouseInteraction, mouseInteractionHook]);\r\n\r\n // 애니메이션은 항상 실행 (마우스 인터랙션 포함)\r\n useAnimationFrame(animationCallback, isPlaying || mouseInteraction?.enabled || false);\r\n\r\n return (\r\n \r\n {!imageLoaded && (\r\n \r\n 이미지 로딩 중...\r\n \r\n )}\r\n \r\n );\r\n};","import * as THREE from 'three';\nimport type { 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 console.log('[ThreeScene] setShaderMaterial 호출됨');\n console.log('[ThreeScene] vertexShader 길이:', vertexShader.length);\n console.log('[ThreeScene] fragmentShader 길이:', fragmentShader.length);\n\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 console.log('[ThreeScene] ShaderMaterial 생성됨');\n\n // 셰이더 컴파일 에러 확인\n const renderer = this.renderer;\n const testScene = new THREE.Scene();\n const testMesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), material);\n testScene.add(testMesh);\n\n try {\n renderer.compile(testScene, this.camera);\n console.log('[ThreeScene] 셰이더 컴파일 성공!');\n } catch (e) {\n console.error('[ThreeScene] 셰이더 컴파일 에러:', e);\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 console.log('[ThreeScene] 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 getResolution(): { x: number; y: number } {\n return {\n x: this.uniforms.u_resolution.value.x,\n y: this.uniforms.u_resolution.value.y,\n };\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 console.log('[ShaderManager] loadShaders 시작:', { vertexPath, fragmentPath });\n\n try {\n console.log('[ShaderManager] fetch 시작...');\n const [vertexResponse, fragmentResponse] = await Promise.all([\n fetch(vertexPath),\n fetch(fragmentPath),\n ]);\n\n console.log('[ShaderManager] fetch 완료:', {\n vertexStatus: vertexResponse.status,\n fragmentStatus: fragmentResponse.status\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 console.log('[ShaderManager] text() 변환 시작...');\n this.vertexShaderSource = await vertexResponse.text();\n this.fragmentShaderSource = await fragmentResponse.text();\n\n console.log('[ShaderManager] 셰이더 로드 완료!', {\n vertexLength: this.vertexShaderSource.length,\n fragmentLength: this.fragmentShaderSource.length\n });\n\n return {\n vertex: this.vertexShaderSource,\n fragment: this.fragmentShaderSource,\n };\n } catch (error) {\n console.error('[ShaderManager] 셰이더 로드 실패:', 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 { type EasingFunction } from '../types';\r\n\r\ntype EasingFunc = (t: number) => number;\r\n\r\n/**\r\n * 이징 함수 구현 맵\r\n */\r\nconst easingFunctions: Record = {\r\n linear: (t) => t,\r\n\r\n easeIn: (t) => t * t,\r\n easeOut: (t) => t * (2 - t),\r\n easeInOut: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),\r\n\r\n easeInQuad: (t) => t * t,\r\n easeOutQuad: (t) => t * (2 - t),\r\n};\r\n\r\n/**\r\n * 진행도에 이징 함수를 적용\r\n * @param progress 진행도 (0.0 - 1.0)\r\n * @param easingType 적용할 이징 함수 타입\r\n * @returns 이징이 적용된 진행도 (0.0 - 1.0)\r\n */\r\nexport const applyEasing = (\r\n progress: number,\r\n easingType: EasingFunction\r\n): number => {\r\n const clampedProgress = Math.max(0, Math.min(1, progress));\r\n return easingFunctions[easingType](clampedProgress);\r\n};","import type {MotionPreset, MotionPresetDefinition, Point, RotationPresetChecker} from '../types';\r\n\r\n/**\r\n * 프리셋 레지스트리 (내장 + 커스텀)\r\n */\r\nconst presetRegistry = new Map();\r\n\r\n/**\r\n * 회전 프리셋 목록\r\n */\r\nconst rotationPresets = new Set(['rotate-cw', 'rotate-ccw']);\r\n\r\n/**\r\n * 내장 프리셋 정의\r\n */\r\nconst BUILT_IN_PRESETS: Record = {\r\n\t'none': () => ({x: 0, y: 0}),\r\n\t'horizontal': (strength) => ({x: strength, y: 0}),\r\n\t'vertical': (strength) => ({x: 0, y: strength}),\r\n\t'rotate-cw': (strength) => ({x: strength, y: 0}),\r\n\t'rotate-ccw': (strength) => ({x: -strength, y: 0}),\r\n\t'pulse': (strength) => ({x: strength, y: strength}),\r\n\t'diagonal-1': (strength) => ({x: strength * 0.707, y: strength * 0.707}),\r\n\t'diagonal-2': (strength) => ({x: strength * 0.707, y: -strength * 0.707}),\r\n};\r\n\r\n// 내장 프리셋 등록\r\nObject.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => {\r\n\tpresetRegistry.set(name, definition);\r\n});\r\n\r\n/**\r\n * 커스텀 모션 프리셋 등록\r\n * @param name 프리셋 이름\r\n * @param definition 프리셋 정의 함수 (strength를 받아 Point 반환)\r\n * @param options 추가 옵션\r\n * @param options.isRotation 회전 애니메이션 여부 (true면 원운동)\r\n *\r\n * @example\r\n * // 좌우 진짜 왕복 (좌↔우)\r\n * registerMotionPreset('horizontal-full', (strength) => ({\r\n * x: strength * 2, // 진폭 2배\r\n * y: 0\r\n * }));\r\n *\r\n * // 8자 모양 운동 (회전)\r\n * registerMotionPreset('figure-8', (strength) => ({\r\n * x: strength,\r\n * y: strength * 0.5\r\n * }), { isRotation: true });\r\n */\r\nexport function registerMotionPreset(\r\n\tname: string,\r\n\tdefinition: MotionPresetDefinition,\r\n\toptions?: { isRotation?: boolean }\r\n): void {\r\n\tpresetRegistry.set(name, definition);\r\n\r\n\tif (options?.isRotation) {\r\n\t\trotationPresets.add(name);\r\n\t} else {\r\n\t\trotationPresets.delete(name);\r\n\t}\r\n}\r\n\r\n/**\r\n * 여러 프리셋을 한번에 등록\r\n * @param presets 프리셋 맵 (이름 → 정의)\r\n * @param rotationPresetNames 회전 프리셋 이름 목록\r\n *\r\n * @example\r\n * registerMotionPresets({\r\n * 'horizontal-full': (s) => ({x: s * 2, y: 0}),\r\n * 'wave': (s) => ({x: s, y: s * 0.3}),\r\n * }, ['wave']); // wave는 회전 애니메이션\r\n */\r\nexport function registerMotionPresets(\r\n\tpresets: Record,\r\n\trotationPresetNames?: string[]\r\n): void {\r\n\tObject.entries(presets).forEach(([name, definition]) => {\r\n\t\tpresetRegistry.set(name, definition);\r\n\t});\r\n\r\n\trotationPresetNames?.forEach(name => rotationPresets.add(name));\r\n}\r\n\r\n/**\r\n * 프리셋 등록 해제\r\n * @param name 프리셋 이름\r\n * @returns 해제 성공 여부\r\n */\r\nexport function unregisterMotionPreset(name: string): boolean {\r\n\trotationPresets.delete(name);\r\n\treturn presetRegistry.delete(name);\r\n}\r\n\r\n/**\r\n * 등록된 모든 프리셋 이름 조회\r\n * @returns 프리셋 이름 배열\r\n */\r\nexport function getRegisteredPresets(): string[] {\r\n\treturn Array.from(presetRegistry.keys());\r\n}\r\n\r\n/**\r\n * 프리셋 존재 여부 확인\r\n * @param name 프리셋 이름\r\n * @returns 존재 여부\r\n */\r\nexport function hasPreset(name: string): boolean {\r\n\treturn presetRegistry.has(name);\r\n}\r\n\r\n/**\r\n * 내장 프리셋으로 초기화 (커스텀 프리셋 모두 제거)\r\n */\r\nexport function resetToBuiltInPresets(): void {\r\n\tpresetRegistry.clear();\r\n\trotationPresets.clear();\r\n\r\n\tObject.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => {\r\n\t\tpresetRegistry.set(name, definition);\r\n\t});\r\n\trotationPresets.add('rotate-cw');\r\n\trotationPresets.add('rotate-ccw');\r\n}\r\n\r\n/**\r\n * 모션 프리셋을 벡터로 변환\r\n * @param preset 모션 프리셋\r\n * @param strength 모션 강도 (기본값: 0.1)\r\n * @returns 계산된 벡터 (vectorA)\r\n */\r\nexport function presetToVector(preset: MotionPreset, strength: number = 0.1): Point {\r\n\tconst definition = presetRegistry.get(preset);\r\n\r\n\tif (definition) {\r\n\t\treturn definition(strength);\r\n\t}\r\n\r\n\t// 등록되지 않은 프리셋은 none으로 처리\r\n\tconsole.warn(`Unknown motion preset: \"${preset}\". Falling back to \"none\".`);\r\n\treturn {x: 0, y: 0};\r\n}\r\n\r\n/**\r\n * 프리셋이 회전 타입인지 확인\r\n */\r\nexport function isRotationPreset(preset?: MotionPreset): boolean {\r\n\tif (!preset) return false;\r\n\treturn rotationPresets.has(preset);\r\n}\r\n\r\n/**\r\n * 커스텀 회전 프리셋 판별 함수 등록\r\n * @param checker 판별 함수\r\n * @deprecated isRotation 옵션을 registerMotionPreset에 전달하세요\r\n */\r\nexport function setRotationChecker(checker: RotationPresetChecker): void {\r\n\t// Legacy support - 기존 코드 호환성을 위해 유지\r\n\tconsole.warn('setRotationChecker is deprecated. Use registerMotionPreset with { isRotation: true } option instead.');\r\n}\r\n","import { applyEasing } from '../utils/easing';\r\nimport { presetToVector, isRotationPreset } from '../utils/motionPresets';\r\nimport type {DistortionArea, Point} from \"../types\";\r\n\r\n/**\r\n * 애니메이션 루프 관리 클래스\r\n */\r\nexport class AnimationLoop {\r\n /**\r\n * 영역들의 드래그 벡터를 현재 진행도에 따라 업데이트\r\n * @param areas 왜곡 영역 배열\r\n * @returns 업데이트된 영역 배열\r\n */\r\n public static updateAreaDragVectors(\r\n areas: DistortionArea[]\r\n ): DistortionArea[] {\r\n return areas.map((area) => {\r\n const { progress, movement } = area;\r\n\r\n // duration이 0이거나 프리셋이 'none'이면 애니메이션 없음\r\n if (movement.duration <= 0 || movement.preset === 'none') {\r\n return {\r\n ...area,\r\n dragVector: { x: 0, y: 0 },\r\n };\r\n }\r\n\r\n // 프리셋이 설정되어 있으면 프리셋 기반 벡터 사용\r\n let baseVector: Point;\r\n if (movement.preset) {\r\n const strength = movement.strength ?? 0.1;\r\n baseVector = presetToVector(movement.preset, strength);\r\n } else {\r\n // 프리셋 없으면 기존 vectorA 사용 (하위 호환성)\r\n baseVector = movement.vectorA;\r\n }\r\n\r\n // 이징 적용\r\n const easedProgress = applyEasing(progress, movement.easing);\r\n\r\n // 벡터 계산\r\n let dragVector: Point;\r\n\r\n // 회전 프리셋인 경우 원운동\r\n if (movement.preset && isRotationPreset(movement.preset)) {\r\n const angle = easedProgress * Math.PI * 2;\r\n const radius = Math.sqrt(baseVector.x * baseVector.x + baseVector.y * baseVector.y);\r\n const direction = movement.preset === 'rotate-cw' ? 1 : -1;\r\n dragVector = {\r\n x: Math.cos(angle * direction) * radius,\r\n y: Math.sin(angle * direction) * radius,\r\n };\r\n } else {\r\n // 일반 왕복 모션\r\n if (easedProgress < 0.5) {\r\n // 0.0 -> 0.5: 0에서 baseVector로 보간\r\n const t = easedProgress * 2;\r\n dragVector = {\r\n x: baseVector.x * t,\r\n y: baseVector.y * t,\r\n };\r\n } else {\r\n // 0.5 -> 1.0: baseVector에서 0으로 보간\r\n const t = (easedProgress - 0.5) * 2;\r\n dragVector = {\r\n x: baseVector.x * (1 - t),\r\n y: baseVector.y * (1 - t),\r\n };\r\n }\r\n }\r\n\r\n return {\r\n ...area,\r\n dragVector,\r\n };\r\n });\r\n }\r\n\r\n /**\r\n * 모든 영역의 진행도를 델타 타임만큼 업데이트\r\n * @param areas 왜곡 영역 배열\r\n * @param deltaTime 델타 타임 (초)\r\n * @returns 업데이트된 영역 배열\r\n */\r\n public static updateProgress(\r\n areas: DistortionArea[],\r\n deltaTime: number\r\n ): DistortionArea[] {\r\n return areas.map((area) => {\r\n // duration이 0이면 progress 업데이트 안 함\r\n if (area.movement.duration <= 0) {\r\n return area;\r\n }\r\n\r\n let newProgress = area.progress + deltaTime / area.movement.duration;\r\n newProgress %= 1.0; // 루프\r\n\r\n return {\r\n ...area,\r\n progress: newProgress,\r\n };\r\n });\r\n }\r\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};","import { useRef, useCallback, useState } from 'react';\r\nimport { useMouseVelocity } from './useMouseVelocity';\r\nimport { SpringPhysics } from '@/engine/SpringPhysics';\r\nimport { DistortionArea, Point } from '@/types';\r\nimport { MouseInteractionConfig } from '@/types/interaction';\r\n\r\n/**\r\n * 점이 사각형 내부에 있는지 확인\r\n */\r\nconst isPointInPolygon = (point: Point, polygon: Point[]): boolean => {\r\n\tlet inside = false;\r\n\tfor (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\r\n\t\tconst xi = polygon[i].x, yi = polygon[i].y;\r\n\t\tconst xj = polygon[j].x, yj = polygon[j].y;\r\n\t\tconst intersect = ((yi > point.y) !== (yj > point.y))\r\n\t\t\t&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);\r\n\t\tif (intersect) inside = !inside;\r\n\t}\r\n\treturn inside;\r\n};\r\n\r\n/**\r\n * 마우스 인터랙션 기반 기존 영역 제어 훅\r\n * 마우스가 지나가는 모든 영역에 효과 적용\r\n */\r\nexport const useMouseInteraction = (\r\n\tcontainerRef: React.RefObject,\r\n\tconfig: MouseInteractionConfig\r\n) => {\r\n\tconst { getState } = useMouseVelocity(containerRef);\r\n\tconst [interactingAreaIndices, setInteractingAreaIndices] = useState>(new Set());\r\n\tconst springPhysicsMapRef = useRef>(new Map());\r\n\r\n\t/**\r\n\t * 특정 영역의 스프링 물리 엔진 가져오기 (없으면 생성)\r\n\t */\r\n\tconst getSpringPhysics = useCallback((areaIndex: number, area?: DistortionArea): SpringPhysics => {\r\n\t\tif (!springPhysicsMapRef.current.has(areaIndex)) {\r\n\t\t\t// 영역별 물리 설정이 있으면 사용, 없으면 전역 설정 사용\r\n\t\t\tconst physicsConfig = area?.physics || config.physics;\r\n\t\t\tspringPhysicsMapRef.current.set(areaIndex, new SpringPhysics(physicsConfig));\r\n\t\t}\r\n\t\treturn springPhysicsMapRef.current.get(areaIndex)!;\r\n\t}, [config.physics]);\r\n\r\n\t/**\r\n\t * 기존 영역들의 dragVector를 마우스 인터랙션으로 업데이트\r\n\t */\r\n\tconst updateInteraction = useCallback((areas: DistortionArea[], deltaTime: number): DistortionArea[] => {\r\n\t\tif (!config.enabled) return areas;\r\n\r\n\t\t// 영역별 물리 설정이 변경되었을 수 있으므로 모든 스프링 업데이트\r\n\t\tareas.forEach((area, index) => {\r\n\t\t\tif (area.physics && springPhysicsMapRef.current.has(index)) {\r\n\t\t\t\tconst spring = springPhysicsMapRef.current.get(index)!;\r\n\t\t\t\tspring.setConfig(area.physics);\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tconst mouseState = getState();\r\n\r\n\t\t// 마우스 클릭/드래그 중이고 위치가 있으면\r\n\t\tif (mouseState.isDragging && mouseState.position) {\r\n\t\t\t// 현재 마우스 위치가 포함된 모든 영역 찾기\r\n\t\t\tconst currentlyInAreas = new Set();\r\n\t\t\tfor (let i = 0; i < areas.length; i++) {\r\n\t\t\t\tif (isPointInPolygon(mouseState.position, areas[i].basePoints)) {\r\n\t\t\t\t\tcurrentlyInAreas.add(i);\r\n\r\n\t\t\t\t\t// 새로 진입한 영역이면 스프링 리셋\r\n\t\t\t\t\tif (!interactingAreaIndices.has(i)) {\r\n\t\t\t\t\t\tgetSpringPhysics(i, areas[i]).reset();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// 이전에 인터랙션하던 영역에서 벗어났으면 평형으로 복귀\r\n\t\t\tinteractingAreaIndices.forEach((areaIndex) => {\r\n\t\t\t\tif (!currentlyInAreas.has(areaIndex)) {\r\n\t\t\t\t\tgetSpringPhysics(areaIndex, areas[areaIndex]).returnToEquilibrium();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\t// 인터랙션 영역 업데이트\r\n\t\t\tsetInteractingAreaIndices(currentlyInAreas);\r\n\r\n\t\t\t// 현재 위치의 모든 영역에 속도 적용\r\n\t\t\tconst velocityMult = config.velocityMultiplier || 1.0;\r\n\t\t\tconst velocityMag = Math.sqrt(\r\n\t\t\t\tmouseState.velocity.x ** 2 + mouseState.velocity.y ** 2\r\n\t\t\t);\r\n\t\t\tconst minVel = config.minVelocity || 0.05;\r\n\t\t\tconst maxVel = config.maxVelocity || 5.0;\r\n\r\n\t\t\t// 속도 클램핑\r\n\t\t\tlet clampedVelocity = mouseState.velocity;\r\n\t\t\tif (velocityMag > maxVel) {\r\n\t\t\t\tconst scale = maxVel / velocityMag;\r\n\t\t\t\tclampedVelocity = {\r\n\t\t\t\t\tx: mouseState.velocity.x * scale,\r\n\t\t\t\t\ty: mouseState.velocity.y * scale,\r\n\t\t\t\t};\r\n\t\t\t}\r\n\r\n\t\t\tcurrentlyInAreas.forEach((areaIndex) => {\r\n\t\t\t\tconst spring = getSpringPhysics(areaIndex, areas[areaIndex]);\r\n\r\n\t\t\t\tif (velocityMag >= minVel) {\r\n\t\t\t\t\t// 드래그 중: 마우스 속도를 목표로 설정\r\n\t\t\t\t\tspring.setTarget(clampedVelocity, velocityMult);\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// 드래그 중이지만 마우스가 멈춰있으면 평형으로 복귀\r\n\t\t\t\t\tspring.returnToEquilibrium();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\t// 마우스를 놓았으면 인터랙션 중이던 모든 영역에 튕김 효과\r\n\t\t\tif (interactingAreaIndices.size > 0) {\r\n\t\t\t\tconst velocityMult = config.velocityMultiplier || 1.0;\r\n\t\t\t\tconst maxVel = config.maxVelocity || 5.0;\r\n\r\n\t\t\t\t// 속도 클램핑\r\n\t\t\t\tconst velocityMag = Math.sqrt(\r\n\t\t\t\t\tmouseState.velocity.x ** 2 + mouseState.velocity.y ** 2\r\n\t\t\t\t);\r\n\t\t\t\tlet clampedVelocity = mouseState.velocity;\r\n\t\t\t\tif (velocityMag > maxVel) {\r\n\t\t\t\t\tconst scale = maxVel / velocityMag;\r\n\t\t\t\t\tclampedVelocity = {\r\n\t\t\t\t\t\tx: mouseState.velocity.x * scale,\r\n\t\t\t\t\t\ty: mouseState.velocity.y * scale,\r\n\t\t\t\t\t};\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// 모든 인터랙션 영역에 초기 속도 설정\r\n\t\t\t\tinteractingAreaIndices.forEach((areaIndex) => {\r\n\t\t\t\t\tconst spring = getSpringPhysics(areaIndex, areas[areaIndex]);\r\n\t\t\t\t\tspring.setInitialVelocity(clampedVelocity, velocityMult);\r\n\t\t\t\t});\r\n\r\n\t\t\t\tsetInteractingAreaIndices(new Set());\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// 모든 영역의 스프링 물리 업데이트\r\n\t\treturn areas.map((area, index) => {\r\n\t\t\tconst spring = springPhysicsMapRef.current.get(index);\r\n\t\t\tif (!spring) return area;\r\n\r\n\t\t\t// 현재 드래그 중인 영역이거나 스프링이 활성 상태일 때만 업데이트\r\n\t\t\tconst springVelocity = spring.getVelocity();\r\n\t\t\tconst springDisplacement = spring.getDisplacement();\r\n\t\t\tconst isSpringActive = Math.sqrt(springVelocity.x ** 2 + springVelocity.y ** 2) > 0.001 ||\r\n\t\t\t\tMath.sqrt(springDisplacement.x ** 2 + springDisplacement.y ** 2) > 0.001;\r\n\r\n\t\t\t// 드래그 중이 아니고 스프링도 비활성이면 업데이트 안 함\r\n\t\t\tif (!interactingAreaIndices.has(index) && !isSpringActive) {\r\n\t\t\t\treturn area;\r\n\t\t\t}\r\n\r\n\t\t\t// 스프링 물리 업데이트\r\n\t\t\tconst displacement = spring.update(deltaTime);\r\n\r\n\t\t\t// 변위가 거의 0이면 원래 dragVector 유지\r\n\t\t\tconst displacementMag = Math.sqrt(displacement.x ** 2 + displacement.y ** 2);\r\n\t\t\tif (displacementMag < 0.001) {\r\n\t\t\t\treturn area;\r\n\t\t\t}\r\n\r\n\t\t\t// 스프링 변위를 dragVector에 추가 (기존 애니메이션과 혼합)\r\n\t\t\t// dragVector는 텍스처 샘플링 좌표 이동이므로,\r\n\t\t\t// 마우스 드래그 방향과 반대로 적용해야 이미지가 드래그 방향으로 밀림\r\n\t\t\t// 예: 우→좌 드래그(velocity < 0) → dragVector > 0 → 이미지 왼쪽으로 밀림\r\n\t\t\treturn {\r\n\t\t\t\t...area,\r\n\t\t\t\tdragVector: {\r\n\t\t\t\t\tx: area.dragVector.x - displacement.x,\r\n\t\t\t\t\ty: area.dragVector.y - displacement.y,\r\n\t\t\t\t},\r\n\t\t\t};\r\n\t\t});\r\n\t}, [config, getState, interactingAreaIndices, getSpringPhysics]);\r\n\r\n\t/**\r\n\t * 물리 파라미터 업데이트\r\n\t */\r\n\tconst updateConfig = useCallback((newConfig: Partial) => {\r\n\t\tconst physicsConfig = newConfig.physics;\r\n\t\tif (physicsConfig) {\r\n\t\t\t// 모든 스프링 물리 엔진의 설정 업데이트\r\n\t\t\tspringPhysicsMapRef.current.forEach((spring) => {\r\n\t\t\t\tspring.setConfig(physicsConfig);\r\n\t\t\t});\r\n\t\t}\r\n\t}, []);\r\n\r\n\t/**\r\n\t * 모든 영역의 스프링 상태 리셋\r\n\t */\r\n\tconst reset = useCallback(() => {\r\n\t\tspringPhysicsMapRef.current.forEach((spring) => {\r\n\t\t\tspring.reset();\r\n\t\t});\r\n\t\tsetInteractingAreaIndices(new Set());\r\n\t}, []);\r\n\r\n\t/**\r\n\t * 현재 드래그 중인지 확인\r\n\t */\r\n\tconst isDragging = useCallback((): boolean => {\r\n\t\tconst mouseState = getState();\r\n\t\treturn mouseState.isDragging;\r\n\t}, [getState]);\r\n\r\n\t/**\r\n\t * 현재 인터랙션 중인 영역 인덱스 가져오기\r\n\t */\r\n\tconst getInteractingAreaIndices = useCallback((): Set => {\r\n\t\treturn interactingAreaIndices;\r\n\t}, [interactingAreaIndices]);\r\n\r\n\treturn {\r\n\t\tupdateInteraction,\r\n\t\tupdateConfig,\r\n\t\treset,\r\n\t\tisDragging,\r\n\t\tgetInteractingAreaIndices,\r\n\t};\r\n};\r\n","import { useRef, useCallback, useEffect } from 'react';\nimport { Point } from '@/types';\nimport { MouseState } from '@/types/interaction';\n\n/**\n * 마우스 위치, 속도, 가속도를 추적하는 훅\n */\nexport const useMouseVelocity = (containerRef: React.RefObject) => {\n\tconst mouseStateRef = useRef({\n\t\tposition: null,\n\t\tprevPosition: null,\n\t\tvelocity: { x: 0, y: 0 },\n\t\tacceleration: { x: 0, y: 0 },\n\t\tisHovering: false,\n\t\tisDragging: false,\n\t});\n\n\tconst lastUpdateTimeRef = useRef(Date.now());\n\tconst prevVelocityRef = useRef({ x: 0, y: 0 });\n\n\t/**\n\t * 픽셀 좌표를 정규화 좌표(0-1)로 변환\n\t */\n\tconst toNormalized = useCallback((clientX: number, clientY: number): Point | null => {\n\t\tif (!containerRef.current) return null;\n\t\tconst rect = containerRef.current.getBoundingClientRect();\n\t\treturn {\n\t\t\tx: (clientX - rect.left) / rect.width,\n\t\t\ty: (clientY - rect.top) / rect.height,\n\t\t};\n\t}, [containerRef]);\n\n\t/**\n\t * 위치 업데이트 (마우스/터치 공통 로직)\n\t */\n\tconst updatePosition = useCallback((clientX: number, clientY: number) => {\n\t\tconst now = Date.now();\n\t\tconst deltaTime = (now - lastUpdateTimeRef.current) / 1000; // 초 단위\n\t\tlastUpdateTimeRef.current = now;\n\n\t\tconst normalizedPos = toNormalized(clientX, clientY);\n\t\tif (!normalizedPos) return;\n\n\t\tconst state = mouseStateRef.current;\n\t\tconst prevPos = state.position;\n\n\t\t// 속도 계산 (변위 / 시간)\n\t\tlet velocity: Point = { x: 0, y: 0 };\n\t\tif (prevPos && deltaTime > 0) {\n\t\t\tvelocity = {\n\t\t\t\tx: (normalizedPos.x - prevPos.x) / deltaTime,\n\t\t\t\ty: (normalizedPos.y - prevPos.y) / deltaTime,\n\t\t\t};\n\t\t}\n\n\t\t// 가속도 계산 (속도 변화 / 시간)\n\t\tconst prevVel = prevVelocityRef.current;\n\t\tlet acceleration: Point = { x: 0, y: 0 };\n\t\tif (deltaTime > 0) {\n\t\t\tacceleration = {\n\t\t\t\tx: (velocity.x - prevVel.x) / deltaTime,\n\t\t\t\ty: (velocity.y - prevVel.y) / deltaTime,\n\t\t\t};\n\t\t}\n\n\t\t// 상태 업데이트\n\t\tmouseStateRef.current = {\n\t\t\tposition: normalizedPos,\n\t\t\tprevPosition: prevPos,\n\t\t\tvelocity,\n\t\t\tacceleration,\n\t\t\tisHovering: true,\n\t\t\tisDragging: state.isDragging,\n\t\t};\n\t\tprevVelocityRef.current = velocity;\n\t}, [toNormalized]);\n\n\t/**\n\t * 마우스 이동 핸들러\n\t */\n\tconst handleMouseMove = useCallback((e: MouseEvent) => {\n\t\tupdatePosition(e.clientX, e.clientY);\n\t}, [updatePosition]);\n\n\t/**\n\t * 마우스 진입\n\t */\n\tconst handleMouseEnter = useCallback(() => {\n\t\tmouseStateRef.current.isHovering = true;\n\t}, []);\n\n\t/**\n\t * 마우스 나감\n\t */\n\tconst handleMouseLeave = useCallback(() => {\n\t\tmouseStateRef.current = {\n\t\t\tposition: null,\n\t\t\tprevPosition: null,\n\t\t\tvelocity: { x: 0, y: 0 },\n\t\t\tacceleration: { x: 0, y: 0 },\n\t\t\tisHovering: false,\n\t\t\tisDragging: false,\n\t\t};\n\t\tprevVelocityRef.current = { x: 0, y: 0 };\n\t}, []);\n\n\t/**\n\t * 마우스 다운\n\t */\n\tconst handleMouseDown = useCallback(() => {\n\t\tmouseStateRef.current.isDragging = true;\n\t}, []);\n\n\t/**\n\t * 마우스 업\n\t */\n\tconst handleMouseUp = useCallback(() => {\n\t\tmouseStateRef.current.isDragging = false;\n\t}, []);\n\n\t/**\n\t * 터치 이동 핸들러\n\t */\n\tconst handleTouchMove = useCallback((e: TouchEvent) => {\n\t\te.preventDefault(); // 스크롤 방지\n\t\tif (e.touches.length > 0) {\n\t\t\tconst touch = e.touches[0];\n\t\t\tupdatePosition(touch.clientX, touch.clientY);\n\t\t}\n\t}, [updatePosition]);\n\n\t/**\n\t * 터치 시작 핸들러\n\t */\n\tconst handleTouchStart = useCallback((e: TouchEvent) => {\n\t\te.preventDefault(); // 스크롤 방지\n\t\tmouseStateRef.current.isDragging = true;\n\t\tmouseStateRef.current.isHovering = true;\n\t\tif (e.touches.length > 0) {\n\t\t\tconst touch = e.touches[0];\n\t\t\tupdatePosition(touch.clientX, touch.clientY);\n\t\t}\n\t}, [updatePosition]);\n\n\t/**\n\t * 터치 종료 핸들러\n\t */\n\tconst handleTouchEnd = useCallback(() => {\n\t\tmouseStateRef.current.isDragging = false;\n\t\tmouseStateRef.current.isHovering = false;\n\t\tmouseStateRef.current.position = null;\n\t\tmouseStateRef.current.prevPosition = null;\n\t\tmouseStateRef.current.velocity = { x: 0, y: 0 };\n\t\tmouseStateRef.current.acceleration = { x: 0, y: 0 };\n\t\tprevVelocityRef.current = { x: 0, y: 0 };\n\t}, []);\n\n\t/**\n\t * 이벤트 리스너 등록\n\t */\n\tuseEffect(() => {\n\t\tconst container = containerRef.current;\n\t\tif (!container) return;\n\n\t\t// 마우스 이벤트\n\t\tcontainer.addEventListener('mousemove', handleMouseMove);\n\t\tcontainer.addEventListener('mouseenter', handleMouseEnter);\n\t\tcontainer.addEventListener('mouseleave', handleMouseLeave);\n\t\tcontainer.addEventListener('mousedown', handleMouseDown);\n\t\twindow.addEventListener('mouseup', handleMouseUp);\n\n\t\t// 터치 이벤트 (passive: false로 스크롤 방지 가능)\n\t\tcontainer.addEventListener('touchmove', handleTouchMove, { passive: false });\n\t\tcontainer.addEventListener('touchstart', handleTouchStart, { passive: false });\n\t\tcontainer.addEventListener('touchend', handleTouchEnd);\n\t\tcontainer.addEventListener('touchcancel', handleTouchEnd);\n\n\t\treturn () => {\n\t\t\t// 마우스 이벤트 제거\n\t\t\tcontainer.removeEventListener('mousemove', handleMouseMove);\n\t\t\tcontainer.removeEventListener('mouseenter', handleMouseEnter);\n\t\t\tcontainer.removeEventListener('mouseleave', handleMouseLeave);\n\t\t\tcontainer.removeEventListener('mousedown', handleMouseDown);\n\t\t\twindow.removeEventListener('mouseup', handleMouseUp);\n\n\t\t\t// 터치 이벤트 제거\n\t\t\tcontainer.removeEventListener('touchmove', handleTouchMove);\n\t\t\tcontainer.removeEventListener('touchstart', handleTouchStart);\n\t\t\tcontainer.removeEventListener('touchend', handleTouchEnd);\n\t\t\tcontainer.removeEventListener('touchcancel', handleTouchEnd);\n\t\t};\n\t}, [containerRef, handleMouseMove, handleMouseEnter, handleMouseLeave, handleMouseDown, handleMouseUp, handleTouchMove, handleTouchStart, handleTouchEnd]);\n\n\t/**\n\t * 현재 마우스 상태 가져오기\n\t */\n\tconst getState = useCallback((): MouseState => {\n\t\treturn { ...mouseStateRef.current };\n\t}, []);\n\n\treturn {\n\t\tgetState,\n\t};\n};\n","import { Point } from '@/types';\r\nimport { SpringPhysicsConfig, SpringState } from '@/types/interaction';\r\n\r\n/**\r\n * 스프링 기반 물리 시뮬레이션 엔진\r\n * Hooke's Law와 감쇠를 적용한 스프링-댐퍼 시스템\r\n */\r\nexport class SpringPhysics {\r\n\tprivate config: SpringPhysicsConfig;\r\n\tprivate state: SpringState;\r\n\r\n\tconstructor(config: SpringPhysicsConfig) {\r\n\t\tthis.config = config;\r\n\t\tthis.state = {\r\n\t\t\tdisplacement: { x: 0, y: 0 },\r\n\t\t\tvelocity: { x: 0, y: 0 },\r\n\t\t\ttarget: { x: 0, y: 0 },\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * 물리 파라미터 업데이트\r\n\t */\r\n\tpublic setConfig(config: Partial) {\r\n\t\tthis.config = { ...this.config, ...config };\r\n\t}\r\n\r\n\t/**\r\n\t * 목표 위치 설정 (마우스 속도 기반)\r\n\t */\r\n\tpublic setTarget(velocity: Point, velocityMultiplier: number = 1.0) {\r\n\t\t// 속도에 승수를 곱해서 목표 변위로 설정\r\n\t\tthis.state.target = {\r\n\t\t\tx: velocity.x * velocityMultiplier,\r\n\t\t\ty: velocity.y * velocityMultiplier,\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * 초기 속도 설정 (드래그 방향과 속도를 즉시 반영)\r\n\t * 드래그 방향으로 즉시 튕기는 효과\r\n\t */\r\n\tpublic setInitialVelocity(velocity: Point, multiplier: number = 1.0) {\r\n\t\t// 현재 속도를 즉시 변경\r\n\t\tthis.state.velocity = {\r\n\t\t\tx: velocity.x * multiplier,\r\n\t\t\ty: velocity.y * multiplier,\r\n\t\t};\r\n\t\t// 목표는 0 (평형 상태로 돌아가도록)\r\n\t\tthis.state.target = { x: 0, y: 0 };\r\n\t}\r\n\r\n\t/**\r\n\t * 스프링 물리 업데이트 (Hooke's Law + Damping)\r\n\t * F = -k * x - c * v\r\n\t * a = F / m\r\n\t * v += a * dt\r\n\t * x += v * dt\r\n\t */\r\n\tpublic update(deltaTime: number): Point {\r\n\t\tconst { stiffness, damping, mass } = this.config;\r\n\t\tconst { displacement, velocity, target } = this.state;\r\n\r\n\t\t// 평형 위치로부터의 변위 (target은 마우스 속도에서 계산된 목표)\r\n\t\tconst dx = displacement.x - target.x;\r\n\t\tconst dy = displacement.y - target.y;\r\n\r\n\t\t// 스프링 힘: F = -k * x (복원력)\r\n\t\tconst springForceX = -stiffness * dx;\r\n\t\tconst springForceY = -stiffness * dy;\r\n\r\n\t\t// 감쇠 힘: F = -c * v (마찰력)\r\n\t\tconst dampingForceX = -damping * velocity.x;\r\n\t\tconst dampingForceY = -damping * velocity.y;\r\n\r\n\t\t// 총 힘\r\n\t\tconst totalForceX = springForceX + dampingForceX;\r\n\t\tconst totalForceY = springForceY + dampingForceY;\r\n\r\n\t\t// 가속도: a = F / m\r\n\t\tconst accelerationX = totalForceX / mass;\r\n\t\tconst accelerationY = totalForceY / mass;\r\n\r\n\t\t// 속도 업데이트: v += a * dt\r\n\t\tconst newVelocityX = velocity.x + accelerationX * deltaTime;\r\n\t\tconst newVelocityY = velocity.y + accelerationY * deltaTime;\r\n\r\n\t\t// 위치 업데이트: x += v * dt\r\n\t\tconst newDisplacementX = displacement.x + newVelocityX * deltaTime;\r\n\t\tconst newDisplacementY = displacement.y + newVelocityY * deltaTime;\r\n\r\n\t\t// 상태 저장\r\n\t\tthis.state = {\r\n\t\t\tdisplacement: { x: newDisplacementX, y: newDisplacementY },\r\n\t\t\tvelocity: { x: newVelocityX, y: newVelocityY },\r\n\t\t\ttarget,\r\n\t\t};\r\n\r\n\t\t// 매우 작은 움직임은 0으로 처리 (정지 판정)\r\n\t\tconst isNearlyZero = (val: number) => Math.abs(val) < 0.0001;\r\n\t\tif (isNearlyZero(this.state.displacement.x) && isNearlyZero(this.state.displacement.y) &&\r\n\t\t\tisNearlyZero(this.state.velocity.x) && isNearlyZero(this.state.velocity.y)) {\r\n\t\t\tthis.reset();\r\n\t\t}\r\n\r\n\t\treturn this.state.displacement;\r\n\t}\r\n\r\n\t/**\r\n\t * 즉시 충격 적용 (마우스 가속도 기반)\r\n\t */\r\n\tpublic applyImpulse(acceleration: Point, multiplier: number = 1.0) {\r\n\t\t// 가속도를 속도로 변환하여 즉시 적용\r\n\t\tthis.state.velocity.x += acceleration.x * multiplier;\r\n\t\tthis.state.velocity.y += acceleration.y * multiplier;\r\n\t}\r\n\r\n\t/**\r\n\t * 현재 변위 가져오기\r\n\t */\r\n\tpublic getDisplacement(): Point {\r\n\t\treturn { ...this.state.displacement };\r\n\t}\r\n\r\n\t/**\r\n\t * 현재 속도 가져오기\r\n\t */\r\n\tpublic getVelocity(): Point {\r\n\t\treturn { ...this.state.velocity };\r\n\t}\r\n\r\n\t/**\r\n\t * 상태 리셋\r\n\t */\r\n\tpublic reset() {\r\n\t\tthis.state = {\r\n\t\t\tdisplacement: { x: 0, y: 0 },\r\n\t\t\tvelocity: { x: 0, y: 0 },\r\n\t\t\ttarget: { x: 0, y: 0 },\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * 마우스가 멈췄을 때 목표를 0으로 설정 (평형 상태로 복귀)\r\n\t */\r\n\tpublic returnToEquilibrium() {\r\n\t\tthis.state.target = { x: 0, y: 0 };\r\n\t}\r\n}\r\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;","import React, {useRef, useEffect, useState, useCallback, useMemo} from 'react';\r\nimport {DistortionArea, Point} from '@/types';\r\nimport {ImageDistortion} from '@/components/ImageDistortion';\r\nimport {EditorCanvasStyle} from '../types';\r\nimport {DEFAULT_EDITOR_CANVAS_STYLE} from '@/editor';\r\n\r\nexport interface EditorCanvasProps {\r\n\tareas: DistortionArea[];\r\n\tselectedAreaId: string | null;\r\n\timageSrc: string;\r\n\twidth: number;\r\n\theight: number;\r\n\tonUpdatePoint: (areaId: string, pointIndex: number, point: Point) => void;\r\n\tonUpdateArea: (areaId: string, updates: Partial) => void;\r\n\tdraggingPointIndex: number | null;\r\n\tonStartDragging: (pointIndex: number) => void;\r\n\tonStopDragging: () => void;\r\n\t/** 에디터 캔버스 스타일 커스터마이징 */\r\n\tstyle?: EditorCanvasStyle;\r\n\t/** 에디터 UI 표시 여부 (기본값: true) */\r\n\tshowEditor?: boolean;\r\n}\r\n\r\nexport const EditorCanvas: React.FC = ({\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t areas,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t selectedAreaId,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t imageSrc,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t width,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t height,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onUpdatePoint,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onUpdateArea,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t draggingPointIndex,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onStartDragging,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onStopDragging,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t style: customStyle,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t showEditor = true,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t }) => {\r\n\tconst containerRef = useRef(null);\r\n\tconst [canvasSize, setCanvasSize] = useState({width: 0, height: 0});\r\n\tconst [isDraggingArea, setIsDraggingArea] = useState(false);\r\n\tconst [dragStartPos, setDragStartPos] = useState(null);\r\n\r\n\t// 스타일 병합 (커스텀 스타일 우선)\r\n\tconst editorStyle = useMemo(() => ({\r\n\t\t...DEFAULT_EDITOR_CANVAS_STYLE,\r\n\t\t...customStyle,\r\n\t\tcircleLevels: customStyle?.circleLevels || DEFAULT_EDITOR_CANVAS_STYLE.circleLevels,\r\n\t\tcenterPoint: {\r\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.centerPoint,\r\n\t\t\t...customStyle?.centerPoint,\r\n\t\t},\r\n\t\tpointHandle: {\r\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.pointHandle,\r\n\t\t\t...customStyle?.pointHandle,\r\n\t\t},\r\n\t\tareaOutline: {\r\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.areaOutline,\r\n\t\t\t...customStyle?.areaOutline,\r\n\t\t},\r\n\t}), [customStyle]);\r\n\r\n\t// 컨테이너 크기 측정\r\n\tuseEffect(() => {\r\n\t\tif (!containerRef.current) return;\r\n\t\tconst rect = containerRef.current.getBoundingClientRect();\r\n\t\tsetCanvasSize({width: rect.width, height: rect.height});\r\n\t}, [width, height]);\r\n\r\n\t// 선택된 영역 찾기\r\n\tconst selectedArea = areas.find((a) => a.id === selectedAreaId);\r\n\r\n\t// 점이 사각형 내부에 있는지 확인 (Point-in-Polygon test)\r\n\tconst isPointInPolygon = useCallback((point: Point, polygon: Point[]): boolean => {\r\n\t\tlet inside = false;\r\n\t\tfor (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\r\n\t\t\tconst xi = polygon[i].x, yi = polygon[i].y;\r\n\t\t\tconst xj = polygon[j].x, yj = polygon[j].y;\r\n\r\n\t\t\tconst intersect = ((yi > point.y) !== (yj > point.y))\r\n\t\t\t\t&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);\r\n\t\t\tif (intersect) inside = !inside;\r\n\t\t}\r\n\t\treturn inside;\r\n\t}, []);\r\n\r\n\t// 포인트 핸들 클릭/터치 핸들러\r\n\tconst handlePointDown = useCallback(\r\n\t\t(pointIndex: number) => (e: React.MouseEvent | React.TouchEvent) => {\r\n\t\t\te.preventDefault();\r\n\t\t\te.stopPropagation();\r\n\t\t\tonStartDragging(pointIndex);\r\n\t\t},\r\n\t\t[onStartDragging]\r\n\t);\r\n\r\n\t// 캔버스 다운 (마우스/터치 공통)\r\n\tconst handleCanvasDown = useCallback(\r\n\t\t(e: React.MouseEvent | React.TouchEvent) => {\r\n\t\t\t// 에디터가 숨겨진 상태면 동작하지 않음\r\n\t\t\tif (!showEditor || !selectedArea || !containerRef.current) return;\r\n\r\n\t\t\tconst rect = containerRef.current.getBoundingClientRect();\r\n\r\n\t\t\t// 마우스 또는 터치 좌표 추출\r\n\t\t\tlet clientX: number, clientY: number;\r\n\t\t\tif ('touches' in e) {\r\n\t\t\t\tif (e.touches.length === 0) return;\r\n\t\t\t\tclientX = e.touches[0].clientX;\r\n\t\t\t\tclientY = e.touches[0].clientY;\r\n\t\t\t} else {\r\n\t\t\t\tclientX = e.clientX;\r\n\t\t\t\tclientY = e.clientY;\r\n\t\t\t}\r\n\r\n\t\t\tconst x = (clientX - rect.left) / rect.width;\r\n\t\t\tconst y = (clientY - rect.top) / rect.height;\r\n\t\t\tconst clickPoint = { x, y };\r\n\r\n\t\t\t// 사각형 내부를 클릭했는지 확인\r\n\t\t\tif (isPointInPolygon(clickPoint, selectedArea.basePoints)) {\r\n\t\t\t\tsetIsDraggingArea(true);\r\n\t\t\t\tsetDragStartPos(clickPoint);\r\n\t\t\t\te.preventDefault();\r\n\t\t\t}\r\n\t\t},\r\n\t\t[showEditor, selectedArea, isPointInPolygon]\r\n\t);\r\n\r\n\t// 이동 (마우스/터치 공통)\r\n\tconst handleMove = useCallback(\r\n\t\t(e: React.MouseEvent | React.TouchEvent) => {\r\n\t\t\t// 에디터가 숨겨진 상태면 동작하지 않음\r\n\t\t\tif (!showEditor || !selectedArea || !containerRef.current) return;\r\n\r\n\t\t\t// 터치 이벤트면 스크롤 방지\r\n\t\t\tif ('touches' in e && (draggingPointIndex !== null || isDraggingArea)) {\r\n\t\t\t\te.preventDefault();\r\n\t\t\t}\r\n\r\n\t\t\tconst rect = containerRef.current.getBoundingClientRect();\r\n\r\n\t\t\t// 마우스 또는 터치 좌표 추출\r\n\t\t\tlet clientX: number, clientY: number;\r\n\t\t\tif ('touches' in e) {\r\n\t\t\t\tif (e.touches.length === 0) return;\r\n\t\t\t\tclientX = e.touches[0].clientX;\r\n\t\t\t\tclientY = e.touches[0].clientY;\r\n\t\t\t} else {\r\n\t\t\t\tclientX = e.clientX;\r\n\t\t\t\tclientY = e.clientY;\r\n\t\t\t}\r\n\r\n\t\t\tconst x = (clientX - rect.left) / rect.width;\r\n\t\t\tconst y = (clientY - rect.top) / rect.height;\r\n\r\n\t\t\t// 포인트 드래그 중\r\n\t\t\tif (draggingPointIndex !== null) {\r\n\t\t\t\tconst clampedX = Math.max(0, Math.min(1, x));\r\n\t\t\t\tconst clampedY = Math.max(0, Math.min(1, y));\r\n\t\t\t\tonUpdatePoint(selectedArea.id, draggingPointIndex, {x: clampedX, y: clampedY});\r\n\t\t\t}\r\n\t\t\t// 사각형 전체 드래그 중\r\n\t\t\telse if (isDraggingArea && dragStartPos) {\r\n\t\t\t\tconst deltaX = x - dragStartPos.x;\r\n\t\t\t\tconst deltaY = y - dragStartPos.y;\r\n\r\n\t\t\t\t// 모든 포인트를 delta만큼 이동\r\n\t\t\t\tconst newPoints = selectedArea.basePoints.map((point) => ({\r\n\t\t\t\t\tx: Math.max(0, Math.min(1, point.x + deltaX)),\r\n\t\t\t\t\ty: Math.max(0, Math.min(1, point.y + deltaY)),\r\n\t\t\t\t})) as [Point, Point, Point, Point];\r\n\r\n\t\t\t\tonUpdateArea(selectedArea.id, { basePoints: newPoints });\r\n\t\t\t\tsetDragStartPos({ x, y }); // 다음 프레임을 위해 시작 위치 업데이트\r\n\t\t\t}\r\n\t\t},\r\n\t\t[showEditor, draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]\r\n\t);\r\n\r\n\t// 업 (마우스/터치 공통)\r\n\tconst handleUp = useCallback(() => {\r\n\t\tif (draggingPointIndex !== null) {\r\n\t\t\tonStopDragging();\r\n\t\t}\r\n\t\tif (isDraggingArea) {\r\n\t\t\tsetIsDraggingArea(false);\r\n\t\t\tsetDragStartPos(null);\r\n\t\t}\r\n\t}, [draggingPointIndex, isDraggingArea, onStopDragging]);\r\n\r\n\t// 전역 업 이벤트 (마우스/터치)\r\n\tuseEffect(() => {\r\n\t\tif (draggingPointIndex !== null || isDraggingArea) {\r\n\t\t\twindow.addEventListener('mouseup', handleUp);\r\n\t\t\twindow.addEventListener('touchend', handleUp);\r\n\t\t\twindow.addEventListener('touchcancel', handleUp);\r\n\t\t\treturn () => {\r\n\t\t\t\twindow.removeEventListener('mouseup', handleUp);\r\n\t\t\t\twindow.removeEventListener('touchend', handleUp);\r\n\t\t\t\twindow.removeEventListener('touchcancel', handleUp);\r\n\t\t\t};\r\n\t\t}\r\n\t}, [draggingPointIndex, isDraggingArea, handleUp]);\r\n\r\n\t// UV 좌표를 픽셀 좌표로 변환 (셰이더와 동일한 bilinear interpolation)\r\n\tconst uvToPixel = (\r\n\t\tu: number,\r\n\t\tv: number,\r\n\t\tpoints: [Point, Point, Point, Point],\r\n\t\tcanvasWidth: number,\r\n\t\tcanvasHeight: number\r\n\t): { x: number; y: number } => {\r\n\t\t// p0=좌상, p1=우상, p2=우하, p3=좌하\r\n\t\tconst [p0, p1, p2, p3] = points;\r\n\r\n\t\t// 셰이더 computeUV와 동일한 순서로 bilinear interpolation\r\n\t\t// left = mix(p0, p1, u) -> 상단 가장자리\r\n\t\t// right = mix(p3, p2, u) -> 하단 가장자리\r\n\t\t// position = mix(left, right, v)\r\n\t\tconst leftX = p0.x * (1 - u) + p1.x * u;\r\n\t\tconst leftY = p0.y * (1 - u) + p1.y * u;\r\n\r\n\t\tconst rightX = p3.x * (1 - u) + p2.x * u;\r\n\t\tconst rightY = p3.y * (1 - u) + p2.y * u;\r\n\r\n\t\tconst posX = leftX * (1 - v) + rightX * v;\r\n\t\tconst posY = leftY * (1 - v) + rightY * v;\r\n\r\n\t\treturn {\r\n\t\t\tx: posX * canvasWidth,\r\n\t\t\ty: posY * canvasHeight,\r\n\t\t};\r\n\t};\r\n\r\n\t// UV 좌표계의 원을 정확히 그리기 (찌그러진 원 형태)\r\n\tconst drawDistortionCircle = useCallback((\r\n\t\tctx: CanvasRenderingContext2D,\r\n\t\tpoints: [Point, Point, Point, Point],\r\n\t\tcanvasWidth: number,\r\n\t\tcanvasHeight: number\r\n\t) => {\r\n\t\tconst segments = 128; // 원을 128개 세그먼트로 촘촘히 분할\r\n\t\tconst centerU = 0.5;\r\n\t\tconst centerV = 0.5;\r\n\r\n\t\tconst circleLevels = editorStyle.circleLevels || [];\r\n\r\n\t\t// 원 레벨별로 그리기 (외부 -> 내부 순)\r\n\t\tcircleLevels.forEach((level, index) => {\r\n\t\t\tconst levelPoints: { x: number; y: number }[] = [];\r\n\t\t\tfor (let i = 0; i <= segments; i++) {\r\n\t\t\t\tconst theta = (i / segments) * 2 * Math.PI;\r\n\t\t\t\tconst u = centerU - level.radius * Math.sin(theta);\r\n\t\t\t\tconst v = centerV + level.radius * Math.cos(theta);\r\n\t\t\t\tconst pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);\r\n\t\t\t\tlevelPoints.push(pixelPos);\r\n\t\t\t}\r\n\r\n\t\t\tctx.beginPath();\r\n\t\t\tctx.moveTo(levelPoints[0].x, levelPoints[0].y);\r\n\t\t\tfor (let i = 1; i < levelPoints.length; i++) {\r\n\t\t\t\tctx.lineTo(levelPoints[i].x, levelPoints[i].y);\r\n\t\t\t}\r\n\t\t\tctx.closePath();\r\n\r\n\t\t\t// 원 테두리\r\n\t\t\tconst baseColor = level.color || 'rgba(255, 200, 0, 1)';\r\n\t\t\t// baseColor에서 RGB 추출하고 opacity 적용\r\n\t\t\tconst colorWithOpacity = baseColor.replace(/rgba?\\(([^)]+)\\)/, (_, rgb) => {\r\n\t\t\t\tconst parts = rgb.split(',').map((p: string) => p.trim());\r\n\t\t\t\treturn `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${level.opacity})`;\r\n\t\t\t});\r\n\t\t\tctx.strokeStyle = colorWithOpacity;\r\n\t\t\tctx.lineWidth = level.lineWidth;\r\n\t\t\tif (level.dashPattern) {\r\n\t\t\t\tctx.setLineDash(level.dashPattern);\r\n\t\t\t}\r\n\t\t\tctx.stroke();\r\n\t\t\tctx.setLineDash([]);\r\n\r\n\t\t\t// 가장 외부 원만 내부 채우기\r\n\t\t\tif (index === 0 && editorStyle.circleFillColor) {\r\n\t\t\t\tctx.fillStyle = editorStyle.circleFillColor;\r\n\t\t\t\tctx.fill();\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// 중심점 표시\r\n\t\tconst centerPointStyle = editorStyle.centerPoint || {};\r\n\t\tconst centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight);\r\n\t\tctx.beginPath();\r\n\t\tctx.arc(centerPixel.x, centerPixel.y, centerPointStyle.radius || 5, 0, 2 * Math.PI);\r\n\t\tif (centerPointStyle.fillColor) {\r\n\t\t\tctx.fillStyle = centerPointStyle.fillColor;\r\n\t\t\tctx.fill();\r\n\t\t}\r\n\t\tif (centerPointStyle.strokeColor) {\r\n\t\t\tctx.strokeStyle = centerPointStyle.strokeColor;\r\n\t\t\tctx.lineWidth = centerPointStyle.strokeWidth || 2;\r\n\t\t\tctx.stroke();\r\n\t\t}\r\n\t}, [editorStyle]);\r\n\r\n\t// 커서 스타일 결정\r\n\tconst getCursorStyle = () => {\r\n\t\tif (draggingPointIndex !== null) return 'grabbing';\r\n\t\tif (isDraggingArea) return 'grabbing';\r\n\t\treturn 'default';\r\n\t};\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t{/* ImageDistortion 컴포넌트 */}\r\n\t\t\t\r\n\r\n\t\t\t{/* 오버레이 SVG - 에디터 모드일 때만 표시 */}\r\n\t\t\t{showEditor && (\r\n\t\t\t\t\r\n\t\t\t\t\t{/* 모든 영역의 사각형 표시 */}\r\n\t\t\t\t\t{areas.map((area) => {\r\n\t\t\t\t\tconst isSelected = area.id === selectedAreaId;\r\n\t\t\t\t\tconst points = area.basePoints;\r\n\t\t\t\t\tconst outlineStyle = editorStyle.areaOutline || {};\r\n\t\t\t\t\treturn (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{/* 사각형 배경 및 경계선 */}\r\n\t\t\t\t\t\t\t `${p.x * canvasSize.width},${p.y * canvasSize.height}`)\r\n\t\t\t\t\t\t\t\t\t.join(' ')}\r\n\t\t\t\t\t\t\t\tfill={isSelected ? (outlineStyle.selectedFillColor || 'rgba(0, 170, 255, 0.08)') : (outlineStyle.unselectedFillColor || 'rgba(136, 136, 136, 0.03)')}\r\n\t\t\t\t\t\t\t\tstroke={isSelected ? (outlineStyle.selectedColor || '#00aaff') : (outlineStyle.unselectedColor || '#888')}\r\n\t\t\t\t\t\t\t\tstrokeWidth={isSelected ? (outlineStyle.selectedWidth || 2) : (outlineStyle.unselectedWidth || 1)}\r\n\t\t\t\t\t\t\t\tstrokeDasharray={isSelected ? '0' : (outlineStyle.unselectedDashPattern?.join(',') || '5,5')}\r\n\t\t\t\t\t\t\t\topacity={isSelected ? 1 : 0.5}\r\n\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t);\r\n\t\t\t\t\t})}\r\n\t\t\t\t\r\n\t\t\t)}\r\n\r\n\t\t\t{/* 선택된 영역의 타원 가이드 (Canvas로 그리기) - 에디터 모드일 때만 표시 */}\r\n\t\t\t{showEditor && selectedArea && canvasSize.width > 0 && (\r\n\t\t\t\t {\r\n\t\t\t\t\t\tif (canvas) {\r\n\t\t\t\t\t\t\tconst ctx = canvas.getContext('2d');\r\n\t\t\t\t\t\t\tif (ctx) {\r\n\t\t\t\t\t\t\t\tctx.clearRect(0, 0, canvasSize.width, canvasSize.height);\r\n\t\t\t\t\t\t\t\tdrawDistortionCircle(ctx, selectedArea.basePoints, canvasSize.width, canvasSize.height);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}}\r\n\t\t\t\t/>\r\n\t\t\t)}\r\n\r\n\t\t\t{/* 선택된 영역의 포인트 핸들 - 에디터 모드일 때만 표시 */}\r\n\t\t\t{showEditor && selectedArea &&\r\n\t\t\t\tselectedArea.basePoints.map((point, index) => {\r\n\t\t\t\t\tconst handleStyle = editorStyle.pointHandle || {};\r\n\t\t\t\t\treturn (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\tP{index + 1}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t);\r\n\t\t\t\t})}\r\n\t\t\r\n\t);\r\n};\r\n","import React from 'react';\nimport { DistortionArea } from '../../types/area';\n\nexport interface AreaListProps {\n\tareas: DistortionArea[];\n\tselectedAreaId: string | null;\n\tonSelectArea: (areaId: string) => void;\n\tonRemoveArea: (areaId: string) => void;\n\tonAddArea: () => void;\n}\n\nexport const AreaList: React.FC = ({\n\tareas,\n\tselectedAreaId,\n\tonSelectArea,\n\tonRemoveArea,\n\tonAddArea,\n}) => {\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t

왜곡 영역

\n\t\t\t\t= 8}\n\t\t\t\t\tclassName=\"btn-add\"\n\t\t\t\t\ttitle={areas.length >= 8 ? '최대 8개 영역까지 지원' : '새 영역 추가'}\n\t\t\t\t>\n\t\t\t\t\t+ 추가\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t\t{areas.length === 0 ? (\n\t\t\t\t\t
영역이 없습니다. + 추가 버튼을 눌러주세요.
\n\t\t\t\t) : (\n\t\t\t\t\tareas.map((area, index) => (\n\t\t\t\t\t\t onSelectArea(area.id)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t영역 {index + 1}\n\t\t\t\t\t\t\t\t강도: {(area.distortionStrength * 100).toFixed(0)}%\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t\t\t\t\tonRemoveArea(area.id);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\tclassName=\"btn-remove\"\n\t\t\t\t\t\t\t\ttitle=\"영역 삭제\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t))\n\t\t\t\t)}\n\t\t\t
\n\t\t\n\t);\n};\n","import React from 'react';\nimport { DistortionArea, EasingFunction } from '../../types/area';\n\nexport interface ParameterPanelProps {\n\tarea: DistortionArea | null;\n\tonUpdateArea: (updates: Partial) => void;\n}\n\nconst EASING_OPTIONS: { value: EasingFunction; label: string }[] = [\n\t{ value: 'linear', label: '선형 (Linear)' },\n\t{ value: 'easeIn', label: '가속 (Ease In)' },\n\t{ value: 'easeOut', label: '감속 (Ease Out)' },\n\t{ value: 'easeInOut', label: '가감속 (Ease In Out)' },\n\t{ value: 'easeInQuad', label: '가속² (Ease In Quad)' },\n\t{ value: 'easeOutQuad', label: '감속² (Ease Out Quad)' },\n];\n\nexport const ParameterPanel: React.FC = ({ area, onUpdateArea }) => {\n\tif (!area) {\n\t\treturn (\n\t\t\t
\n\t\t\t\t
영역을 선택해주세요
\n\t\t\t
\n\t\t);\n\t}\n\n\treturn (\n\t\t
\n\t\t\t

파라미터 편집

\n\n\t\t\t{/* 왜곡 강도 */}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t onUpdateArea({ distortionStrength: parseFloat(e.target.value) })}\n\t\t\t\t\tclassName=\"slider\"\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{/* 애니메이션 지속 시간 */}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\tonUpdateArea({\n\t\t\t\t\t\t\tmovement: { ...area.movement, duration: parseFloat(e.target.value) },\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tclassName=\"input-number\"\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{/* 이징 함수 */}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\tonUpdateArea({\n\t\t\t\t\t\t\tmovement: { ...area.movement, easing: e.target.value as EasingFunction },\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tclassName=\"select\"\n\t\t\t\t>\n\t\t\t\t\t{EASING_OPTIONS.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t
\n\n\t\t\t{/* 포인트 좌표 (읽기 전용 표시) */}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t{area.basePoints.map((point, idx) => (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\tP{idx + 1}: ({point.x.toFixed(3)}, {point.y.toFixed(3)})\n\t\t\t\t\t\t
\n\t\t\t\t\t))}\n\t\t\t\t
\n\t\t\t
\n\t\t
\n\t);\n};\n","import { useState, useCallback } from 'react';\r\nimport { DistortionArea, Point } from '../../types/area';\r\nimport { EditorState } from '../types';\r\n\r\nexport const useDistortionEditor = (initialAreas: DistortionArea[] = []) => {\r\n\tconst [state, setState] = useState({\r\n\t\tselectedAreaId: initialAreas[0]?.id || null,\r\n\t\tareas: initialAreas,\r\n\t\teditMode: 'normal',\r\n\t\tdraggingPointIndex: null,\r\n\t});\r\n\r\n\t/** 영역 선택 */\r\n\tconst selectArea = useCallback((areaId: string | null) => {\r\n\t\tsetState((prev) => ({ ...prev, selectedAreaId: areaId }));\r\n\t}, []);\r\n\r\n\t/** 영역 추가 */\r\n\tconst addArea = useCallback((area: DistortionArea) => {\r\n\t\tsetState((prev) => ({\r\n\t\t\t...prev,\r\n\t\t\tareas: [...prev.areas, area],\r\n\t\t\tselectedAreaId: area.id,\r\n\t\t}));\r\n\t}, []);\r\n\r\n\t/** 영역 삭제 */\r\n\tconst removeArea = useCallback((areaId: string) => {\r\n\t\tsetState((prev) => {\r\n\t\t\tconst newAreas = prev.areas.filter((a) => a.id !== areaId);\r\n\t\t\treturn {\r\n\t\t\t\t...prev,\r\n\t\t\t\tareas: newAreas,\r\n\t\t\t\tselectedAreaId:\r\n\t\t\t\t\tprev.selectedAreaId === areaId ? newAreas[0]?.id || null : prev.selectedAreaId,\r\n\t\t\t};\r\n\t\t});\r\n\t}, []);\r\n\r\n\t/** 영역 업데이트 */\r\n\tconst updateArea = useCallback((areaId: string, updates: Partial) => {\r\n\t\tsetState((prev) => ({\r\n\t\t\t...prev,\r\n\t\t\tareas: prev.areas.map((area) => (area.id === areaId ? { ...area, ...updates } : area)),\r\n\t\t}));\r\n\t}, []);\r\n\r\n\t/** 포인트 업데이트 */\r\n\tconst updatePoint = useCallback((areaId: string, pointIndex: number, point: Point) => {\r\n\t\tsetState((prev) => ({\r\n\t\t\t...prev,\r\n\t\t\tareas: prev.areas.map((area) => {\r\n\t\t\t\tif (area.id === areaId) {\r\n\t\t\t\t\tconst newPoints = [...area.basePoints] as [Point, Point, Point, Point];\r\n\t\t\t\t\tnewPoints[pointIndex] = point;\r\n\t\t\t\t\treturn { ...area, basePoints: newPoints };\r\n\t\t\t\t}\r\n\t\t\t\treturn area;\r\n\t\t\t}),\r\n\t\t}));\r\n\t}, []);\r\n\r\n\t/** 드래그 시작 */\r\n\tconst startDragging = useCallback((pointIndex: number) => {\r\n\t\tsetState((prev) => ({ ...prev, draggingPointIndex: pointIndex }));\r\n\t}, []);\r\n\r\n\t/** 드래그 종료 */\r\n\tconst stopDragging = useCallback(() => {\r\n\t\tsetState((prev) => ({ ...prev, draggingPointIndex: null }));\r\n\t}, []);\r\n\r\n\t/** 편집 모드 변경 */\r\n\tconst setEditMode = useCallback((mode: EditorState['editMode']) => {\r\n\t\tsetState((prev) => ({ ...prev, editMode: mode }));\r\n\t}, []);\r\n\r\n\t/** 선택된 영역 가져오기 */\r\n\tconst getSelectedArea = useCallback(() => {\r\n\t\treturn state.areas.find((a) => a.id === state.selectedAreaId) || null;\r\n\t}, [state.areas, state.selectedAreaId]);\r\n\r\n\treturn {\r\n\t\tstate,\r\n\t\tselectArea,\r\n\t\taddArea,\r\n\t\tremoveArea,\r\n\t\tupdateArea,\r\n\t\tupdatePoint,\r\n\t\tstartDragging,\r\n\t\tstopDragging,\r\n\t\tsetEditMode,\r\n\t\tgetSelectedArea,\r\n\t};\r\n};\r\n","import { EditorCanvasStyle } from './types';\n\n/**\n * 기본 에디터 캔버스 스타일\n */\nexport const DEFAULT_EDITOR_CANVAS_STYLE: EditorCanvasStyle = {\n\t// 3단계 원 스타일 (외부 -> 내부)\n\tcircleLevels: [\n\t\t{\n\t\t\tradius: 0.5,\n\t\t\topacity: 0.3,\n\t\t\tlineWidth: 2,\n\t\t\tcolor: 'rgba(255, 200, 0, 1)',\n\t\t\tdashPattern: [8, 4],\n\t\t},\n\t\t{\n\t\t\tradius: 0.33,\n\t\t\topacity: 0.6,\n\t\t\tlineWidth: 2.5,\n\t\t\tcolor: 'rgba(255, 200, 0, 1)',\n\t\t\tdashPattern: [8, 4],\n\t\t},\n\t\t{\n\t\t\tradius: 0.167,\n\t\t\topacity: 0.9,\n\t\t\tlineWidth: 3,\n\t\t\tcolor: 'rgba(255, 200, 0, 1)',\n\t\t\tdashPattern: [8, 4],\n\t\t},\n\t],\n\t// 원 내부 채우기\n\tcircleFillColor: 'rgba(255, 200, 0, 0.08)',\n\t// 중심점\n\tcenterPoint: {\n\t\tradius: 5,\n\t\tfillColor: 'rgba(255, 200, 0, 1)',\n\t\tstrokeColor: 'rgba(255, 255, 255, 0.8)',\n\t\tstrokeWidth: 2,\n\t},\n\t// 포인트 핸들\n\tpointHandle: {\n\t\tsize: 16,\n\t\tfillColor: '#00aaff',\n\t\tstrokeColor: 'white',\n\t\tstrokeWidth: 2,\n\t\tlabelColor: '#00aaff',\n\t\tlabelFontSize: 11,\n\t},\n\t// 영역 외곽선\n\tareaOutline: {\n\t\tselectedColor: '#00aaff',\n\t\tunselectedColor: '#888',\n\t\tselectedWidth: 2,\n\t\tunselectedWidth: 1,\n\t\tunselectedDashPattern: [5, 5],\n\t\tselectedFillColor: 'rgba(0, 170, 255, 0.08)', // 선택된 영역 배경 (연한 파란색)\n\t\tunselectedFillColor: 'rgba(136, 136, 136, 0.03)', // 선택 안된 영역 배경 (연한 회색)\n\t},\n};\n"],"mappings":"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","names":["import_react","THREE","import_react","import_react","import_react","import_jsx_runtime","import_jsx_runtime","import_react","import_jsx_runtime","isPointInPolygon"]} \ No newline at end of file diff --git a/dist/index.mjs b/dist/index.mjs index b642310..1fefc42 100644 --- a/dist/index.mjs +++ b/dist/index.mjs @@ -196,31 +196,65 @@ var applyEasing = (progress, easingType) => { }; // src/utils/motionPresets.ts -function presetToVector(preset, strength = 0.1) { - switch (preset) { - case "none": - return { x: 0, y: 0 }; - case "horizontal": - return { x: strength, y: 0 }; - case "vertical": - return { x: 0, y: strength }; - case "rotate-cw": - return { x: strength, y: 0 }; - case "rotate-ccw": - return { x: -strength, y: 0 }; - case "pulse": - return { x: strength, y: strength }; - case "diagonal-1": - return { x: strength * 0.707, y: strength * 0.707 }; - // √2/2 ≈ 0.707 - case "diagonal-2": - return { x: strength * 0.707, y: -strength * 0.707 }; - default: - return { x: 0, y: 0 }; +var presetRegistry = /* @__PURE__ */ new Map(); +var rotationPresets = /* @__PURE__ */ new Set(["rotate-cw", "rotate-ccw"]); +var BUILT_IN_PRESETS = { + "none": () => ({ x: 0, y: 0 }), + "horizontal": (strength) => ({ x: strength, y: 0 }), + "vertical": (strength) => ({ x: 0, y: strength }), + "rotate-cw": (strength) => ({ x: strength, y: 0 }), + "rotate-ccw": (strength) => ({ x: -strength, y: 0 }), + "pulse": (strength) => ({ x: strength, y: strength }), + "diagonal-1": (strength) => ({ x: strength * 0.707, y: strength * 0.707 }), + "diagonal-2": (strength) => ({ x: strength * 0.707, y: -strength * 0.707 }) +}; +Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => { + presetRegistry.set(name, definition); +}); +function registerMotionPreset(name, definition, options) { + presetRegistry.set(name, definition); + if (options?.isRotation) { + rotationPresets.add(name); + } else { + rotationPresets.delete(name); } } +function registerMotionPresets(presets, rotationPresetNames) { + Object.entries(presets).forEach(([name, definition]) => { + presetRegistry.set(name, definition); + }); + rotationPresetNames?.forEach((name) => rotationPresets.add(name)); +} +function unregisterMotionPreset(name) { + rotationPresets.delete(name); + return presetRegistry.delete(name); +} +function getRegisteredPresets() { + return Array.from(presetRegistry.keys()); +} +function hasPreset(name) { + return presetRegistry.has(name); +} +function resetToBuiltInPresets() { + presetRegistry.clear(); + rotationPresets.clear(); + Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => { + presetRegistry.set(name, definition); + }); + rotationPresets.add("rotate-cw"); + rotationPresets.add("rotate-ccw"); +} +function presetToVector(preset, strength = 0.1) { + const definition = presetRegistry.get(preset); + if (definition) { + return definition(strength); + } + console.warn(`Unknown motion preset: "${preset}". Falling back to "none".`); + return { x: 0, y: 0 }; +} function isRotationPreset(preset) { - return preset === "rotate-cw" || preset === "rotate-ccw"; + if (!preset) return false; + return rotationPresets.has(preset); } // src/engine/AnimationLoop.ts @@ -1561,8 +1595,14 @@ export { SpringPhysics, ThreeScene, applyEasing, + getRegisteredPresets, + hasPreset, isRotationPreset, presetToVector, + registerMotionPreset, + registerMotionPresets, + resetToBuiltInPresets, + unregisterMotionPreset, useAnimationFrame, useDistortionEditor, useMouseInteraction, diff --git a/dist/index.mjs.map b/dist/index.mjs.map index 827bf05..3b37fa6 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/utils/motionPresets.ts","../src/engine/AnimationLoop.ts","../src/hooks/useAnimationFrame.ts","../src/hooks/useMouseInteraction.ts","../src/hooks/useMouseVelocity.ts","../src/engine/SpringPhysics.ts","../src/utils/constants.ts","../src/editor/components/EditorCanvas.tsx","../src/editor/components/AreaList.tsx","../src/editor/components/ParameterPanel.tsx","../src/editor/hooks/useDistortionEditor.ts","../src/editor/constants.ts"],"sourcesContent":["import React, { useEffect, useRef, useState, useCallback } from 'react';\r\nimport * as THREE from 'three';\r\nimport { type DistortionArea } from '@/types';\r\nimport { ThreeScene } from '@/engine/ThreeScene';\r\nimport { ShaderManager } from '@/engine/ShaderManager';\r\nimport { AnimationLoop } from '@/engine/AnimationLoop';\r\nimport { useAnimationFrame } from '@/hooks/useAnimationFrame';\r\nimport { useMouseInteraction } from '@/hooks/useMouseInteraction';\r\nimport { SHADER_CONFIG } from '@/utils/constants';\r\nimport { MouseInteractionConfig } from '@/types/interaction';\r\n\r\n/**\r\n * ImageDistortion 컴포넌트 Props\r\n */\r\nexport interface ImageDistortionProps {\r\n /** 이미지 소스 URL */\r\n imageSrc: string;\r\n /** 왜곡 영역 배열 */\r\n areas: DistortionArea[];\r\n /** 버텍스 셰이더 경로 (선택사항) */\r\n vertexShaderPath?: string;\r\n /** 프래그먼트 셰이더 경로 (선택사항) */\r\n fragmentShaderPath?: string;\r\n /** 애니메이션 재생 여부 */\r\n isPlaying?: boolean;\r\n /** 컨테이너 스타일 */\r\n style?: React.CSSProperties;\r\n /** 컨테이너 클래스명 */\r\n className?: string;\r\n /** 마우스 인터랙션 설정 */\r\n mouseInteraction?: MouseInteractionConfig;\r\n}\r\n\r\n/**\r\n * GPU 가속 이미지 왜곡 컴포넌트\r\n * Three.js와 GLSL 셰이더를 사용하여 실시간 이미지 왜곡 효과를 제공합니다.\r\n */\r\nexport const ImageDistortion: React.FC = ({\r\n imageSrc,\r\n areas,\r\n vertexShaderPath,\r\n fragmentShaderPath,\r\n isPlaying = true,\r\n style,\r\n className,\r\n mouseInteraction,\r\n}) => {\r\n const containerRef = useRef(null);\r\n const sceneRef = useRef(null);\r\n const shaderManagerRef = useRef(new ShaderManager());\r\n const textureRef = useRef(null);\r\n\r\n const [isReady, setIsReady] = useState(false);\r\n const [imageLoaded, setImageLoaded] = useState(false);\r\n const [currentAreas, setCurrentAreas] = useState(areas);\r\n\r\n // 마우스 인터랙션 훅\r\n const mouseInteractionHook = useMouseInteraction(\r\n containerRef,\r\n mouseInteraction || {\r\n enabled: false,\r\n physics: {\r\n stiffness: 100,\r\n damping: 10,\r\n mass: 1,\r\n influenceRadius: 0.2,\r\n maxStrength: 1.0,\r\n },\r\n }\r\n );\r\n\r\n // 영역 변경 시 상태 업데이트\r\n useEffect(() => {\r\n setCurrentAreas(areas);\r\n }, [areas]);\r\n\r\n // 마우스 인터랙션 설정 변경 시 업데이트\r\n useEffect(() => {\r\n if (mouseInteraction) {\r\n mouseInteractionHook.updateConfig(mouseInteraction);\r\n }\r\n }, [mouseInteraction, mouseInteractionHook]);\r\n\r\n // Three.js 씬 초기화\r\n useEffect(() => {\r\n console.log('[ImageDistortion] useEffect 실행, containerRef.current:', containerRef.current);\r\n\r\n if (!containerRef.current) {\r\n console.warn('[ImageDistortion] containerRef.current가 null입니다. 컴포넌트가 제대로 마운트되지 않았습니다.');\r\n return;\r\n }\r\n\r\n console.log('[ImageDistortion] 초기화 시작');\r\n const scene = new ThreeScene(containerRef.current);\r\n sceneRef.current = scene;\r\n\r\n // 셰이더 로드\r\n const vertPath = vertexShaderPath || '/shaders/distortion.vert.glsl';\r\n const fragPath = fragmentShaderPath || '/shaders/distortion.frag.glsl';\r\n\r\n console.log('[ImageDistortion] 셰이더 로드 시도:', { vertPath, fragPath });\r\n\r\n shaderManagerRef.current\r\n .loadShaders(vertPath, fragPath)\r\n .then(({ vertex, fragment }) => {\r\n console.log('[ImageDistortion] 셰이더 로드 성공');\r\n scene.setShaderMaterial(vertex, fragment);\r\n setIsReady(true);\r\n })\r\n .catch((error) => {\r\n console.error('[ImageDistortion] 셰이더 로드 실패:', error);\r\n });\r\n\r\n return () => {\r\n scene.dispose();\r\n if (textureRef.current) {\r\n textureRef.current.dispose();\r\n }\r\n };\r\n }, [vertexShaderPath, fragmentShaderPath]);\r\n\r\n // 이미지 텍스처 로드\r\n useEffect(() => {\r\n if (!imageSrc || !isReady) {\r\n console.log('[ImageDistortion] 이미지 로드 스킵:', { imageSrc, isReady });\r\n return;\r\n }\r\n\r\n console.log('[ImageDistortion] 이미지 로드 시작:', imageSrc);\r\n setImageLoaded(false);\r\n\r\n const loader = new THREE.TextureLoader();\r\n loader.load(\r\n imageSrc,\r\n (texture) => {\r\n console.log('[ImageDistortion] 이미지 로드 성공!', {\r\n width: texture.image.width,\r\n height: texture.image.height\r\n });\r\n textureRef.current = texture;\r\n setImageLoaded(true);\r\n if (sceneRef.current) {\r\n sceneRef.current.updateUniforms({\r\n u_texture: { value: texture },\r\n });\r\n sceneRef.current.render();\r\n console.log('[ImageDistortion] 텍스처 업데이트 및 렌더링 완료');\r\n }\r\n },\r\n (progress) => {\r\n console.log('[ImageDistortion] 이미지 로딩 중...',\r\n Math.round((progress.loaded / progress.total) * 100) + '%'\r\n );\r\n },\r\n (error) => {\r\n console.error('[ImageDistortion] 이미지 로드 실패:', error);\r\n setImageLoaded(false);\r\n }\r\n );\r\n\r\n return () => {\r\n if (textureRef.current) {\r\n textureRef.current.dispose();\r\n textureRef.current = null;\r\n }\r\n };\r\n }, [imageSrc, isReady]);\r\n\r\n // 셰이더 유니폼 업데이트\r\n useEffect(() => {\r\n if (!sceneRef.current || !isReady) return;\r\n\r\n // 현재 해상도 가져오기\r\n const resolution = sceneRef.current.getResolution();\r\n\r\n // 포인트 배열 생성\r\n // UI는 좌상단 (0,0), WebGL은 좌하단 (0,0)이므로 y 좌표를 반전\r\n const points = new Float32Array(SHADER_CONFIG.MAX_POINTS * 2);\r\n currentAreas.forEach((area, areaIndex) => {\r\n area.basePoints.forEach((point, pointIndex) => {\r\n const index = (areaIndex * 4 + pointIndex) * 2;\r\n points[index] = point.x;\r\n points[index + 1] = 1.0 - point.y; // y 좌표 반전\r\n });\r\n });\r\n\r\n // 드래그 벡터 배열 생성\r\n // dragVector도 y 좌표계를 맞춰야 하므로 y를 반전\r\n const dragVectors = new Float32Array(SHADER_CONFIG.MAX_DRAG_VECTORS * 2);\r\n currentAreas.forEach((area, index) => {\r\n const baseIndex = index * 2;\r\n dragVectors[baseIndex] = area.dragVector.x;\r\n dragVectors[baseIndex + 1] = -area.dragVector.y; // y 방향 반전\r\n });\r\n\r\n // 강도 배열 생성\r\n const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS);\r\n currentAreas.forEach((area, index) => {\r\n strengths[index] = area.distortionStrength;\r\n });\r\n\r\n sceneRef.current.updateUniforms({\r\n u_numAreas: { value: currentAreas.length },\r\n u_points: { value: points },\r\n u_dragVectors: { value: dragVectors },\r\n u_distortionStrengths: { value: strengths },\r\n });\r\n\r\n sceneRef.current.render();\r\n }, [currentAreas, isReady]);\r\n\r\n // 애니메이션 루프\r\n const animationCallback = useCallback((deltaTime: number) => {\r\n if (!isReady) return;\r\n\r\n setCurrentAreas((prevAreas) => {\r\n // 현재 인터랙션 중인 영역 인덱스 가져오기\r\n const interactingIndices = mouseInteractionHook.getInteractingAreaIndices?.() || new Set();\r\n\r\n // 1. 자동 애니메이션 업데이트\r\n let updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);\r\n updatedAreas = AnimationLoop.updateAreaDragVectors(updatedAreas);\r\n\r\n // 인터랙션 중인 영역만 dragVector를 0으로 설정\r\n if (interactingIndices.size > 0) {\r\n updatedAreas = updatedAreas.map((area, index) => {\r\n if (interactingIndices.has(index)) {\r\n return {\r\n ...area,\r\n dragVector: { x: 0, y: 0 }\r\n };\r\n }\r\n return area;\r\n });\r\n }\r\n\r\n // 2. 마우스 인터랙션 적용 (기존 dragVector에 스프링 변위 추가)\r\n if (mouseInteraction?.enabled) {\r\n updatedAreas = mouseInteractionHook.updateInteraction(updatedAreas, deltaTime);\r\n }\r\n\r\n return updatedAreas;\r\n });\r\n }, [isReady, mouseInteraction, mouseInteractionHook]);\r\n\r\n // 애니메이션은 항상 실행 (마우스 인터랙션 포함)\r\n useAnimationFrame(animationCallback, isPlaying || mouseInteraction?.enabled || false);\r\n\r\n return (\r\n \r\n {!imageLoaded && (\r\n \r\n 이미지 로딩 중...\r\n \r\n )}\r\n \r\n );\r\n};","import * as THREE from 'three';\nimport type { 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 console.log('[ThreeScene] setShaderMaterial 호출됨');\n console.log('[ThreeScene] vertexShader 길이:', vertexShader.length);\n console.log('[ThreeScene] fragmentShader 길이:', fragmentShader.length);\n\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 console.log('[ThreeScene] ShaderMaterial 생성됨');\n\n // 셰이더 컴파일 에러 확인\n const renderer = this.renderer;\n const testScene = new THREE.Scene();\n const testMesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), material);\n testScene.add(testMesh);\n\n try {\n renderer.compile(testScene, this.camera);\n console.log('[ThreeScene] 셰이더 컴파일 성공!');\n } catch (e) {\n console.error('[ThreeScene] 셰이더 컴파일 에러:', e);\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 console.log('[ThreeScene] 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 getResolution(): { x: number; y: number } {\n return {\n x: this.uniforms.u_resolution.value.x,\n y: this.uniforms.u_resolution.value.y,\n };\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 console.log('[ShaderManager] loadShaders 시작:', { vertexPath, fragmentPath });\n\n try {\n console.log('[ShaderManager] fetch 시작...');\n const [vertexResponse, fragmentResponse] = await Promise.all([\n fetch(vertexPath),\n fetch(fragmentPath),\n ]);\n\n console.log('[ShaderManager] fetch 완료:', {\n vertexStatus: vertexResponse.status,\n fragmentStatus: fragmentResponse.status\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 console.log('[ShaderManager] text() 변환 시작...');\n this.vertexShaderSource = await vertexResponse.text();\n this.fragmentShaderSource = await fragmentResponse.text();\n\n console.log('[ShaderManager] 셰이더 로드 완료!', {\n vertexLength: this.vertexShaderSource.length,\n fragmentLength: this.fragmentShaderSource.length\n });\n\n return {\n vertex: this.vertexShaderSource,\n fragment: this.fragmentShaderSource,\n };\n } catch (error) {\n console.error('[ShaderManager] 셰이더 로드 실패:', 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 { type EasingFunction } from '../types';\r\n\r\ntype EasingFunc = (t: number) => number;\r\n\r\n/**\r\n * 이징 함수 구현 맵\r\n */\r\nconst easingFunctions: Record = {\r\n linear: (t) => t,\r\n\r\n easeIn: (t) => t * t,\r\n easeOut: (t) => t * (2 - t),\r\n easeInOut: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),\r\n\r\n easeInQuad: (t) => t * t,\r\n easeOutQuad: (t) => t * (2 - t),\r\n};\r\n\r\n/**\r\n * 진행도에 이징 함수를 적용\r\n * @param progress 진행도 (0.0 - 1.0)\r\n * @param easingType 적용할 이징 함수 타입\r\n * @returns 이징이 적용된 진행도 (0.0 - 1.0)\r\n */\r\nexport const applyEasing = (\r\n progress: number,\r\n easingType: EasingFunction\r\n): number => {\r\n const clampedProgress = Math.max(0, Math.min(1, progress));\r\n return easingFunctions[easingType](clampedProgress);\r\n};","import type {MotionPreset, Point} from '../types';\r\n\r\n/**\r\n * 모션 프리셋을 벡터로 변환\r\n * @param preset 모션 프리셋\r\n * @param strength 모션 강도 (기본값: 0.1)\r\n * @returns 계산된 벡터 (vectorA)\r\n */\r\nexport function presetToVector(preset: MotionPreset, strength: number = 0.1): Point {\r\n\tswitch (preset) {\r\n\t\tcase 'none':\r\n\t\t\t// 애니메이션 없음\r\n\t\t\treturn {x: 0, y: 0};\r\n\r\n\t\tcase 'horizontal':\r\n\t\t\t// 좌우 왕복\r\n\t\t\treturn {x: strength, y: 0};\r\n\r\n\t\tcase 'vertical':\r\n\t\t\t// 상하 왕복\r\n\t\t\treturn {x: 0, y: strength};\r\n\r\n\t\tcase 'rotate-cw':\r\n\t\t\t// 시계방향 회전 (원운동의 시작점)\r\n\t\t\treturn {x: strength, y: 0};\r\n\r\n\t\tcase 'rotate-ccw':\r\n\t\t\t// 반시계방향 회전 (원운동의 시작점)\r\n\t\t\treturn {x: -strength, y: 0};\r\n\r\n\t\tcase 'pulse':\r\n\t\t\t// 펄스 (중심에서 바깥으로)\r\n\t\t\treturn {x: strength, y: strength};\r\n\r\n\t\tcase 'diagonal-1':\r\n\t\t\t// 대각선 (좌상→우하)\r\n\t\t\treturn {x: strength * 0.707, y: strength * 0.707}; // √2/2 ≈ 0.707\r\n\r\n\t\tcase 'diagonal-2':\r\n\t\t\t// 대각선 (우상→좌하)\r\n\t\t\treturn {x: strength * 0.707, y: -strength * 0.707};\r\n\r\n\t\tdefault:\r\n\t\t\treturn {x: 0, y: 0};\r\n\t}\r\n}\r\n\r\n/**\r\n * 프리셋이 회전 타입인지 확인\r\n */\r\nexport function isRotationPreset(preset?: MotionPreset): boolean {\r\n\treturn preset === 'rotate-cw' || preset === 'rotate-ccw';\r\n}\r\n","import { applyEasing } from '../utils/easing';\r\nimport { presetToVector, isRotationPreset } from '../utils/motionPresets';\r\nimport type {DistortionArea, Point} from \"../types\";\r\n\r\n/**\r\n * 애니메이션 루프 관리 클래스\r\n */\r\nexport class AnimationLoop {\r\n /**\r\n * 영역들의 드래그 벡터를 현재 진행도에 따라 업데이트\r\n * @param areas 왜곡 영역 배열\r\n * @returns 업데이트된 영역 배열\r\n */\r\n public static updateAreaDragVectors(\r\n areas: DistortionArea[]\r\n ): DistortionArea[] {\r\n return areas.map((area) => {\r\n const { progress, movement } = area;\r\n\r\n // duration이 0이거나 프리셋이 'none'이면 애니메이션 없음\r\n if (movement.duration <= 0 || movement.preset === 'none') {\r\n return {\r\n ...area,\r\n dragVector: { x: 0, y: 0 },\r\n };\r\n }\r\n\r\n // 프리셋이 설정되어 있으면 프리셋 기반 벡터 사용\r\n let baseVector: Point;\r\n if (movement.preset) {\r\n const strength = movement.strength ?? 0.1;\r\n baseVector = presetToVector(movement.preset, strength);\r\n } else {\r\n // 프리셋 없으면 기존 vectorA 사용 (하위 호환성)\r\n baseVector = movement.vectorA;\r\n }\r\n\r\n // 이징 적용\r\n const easedProgress = applyEasing(progress, movement.easing);\r\n\r\n // 벡터 계산\r\n let dragVector: Point;\r\n\r\n // 회전 프리셋인 경우 원운동\r\n if (movement.preset && isRotationPreset(movement.preset)) {\r\n const angle = easedProgress * Math.PI * 2;\r\n const radius = Math.sqrt(baseVector.x * baseVector.x + baseVector.y * baseVector.y);\r\n const direction = movement.preset === 'rotate-cw' ? 1 : -1;\r\n dragVector = {\r\n x: Math.cos(angle * direction) * radius,\r\n y: Math.sin(angle * direction) * radius,\r\n };\r\n } else {\r\n // 일반 왕복 모션\r\n if (easedProgress < 0.5) {\r\n // 0.0 -> 0.5: 0에서 baseVector로 보간\r\n const t = easedProgress * 2;\r\n dragVector = {\r\n x: baseVector.x * t,\r\n y: baseVector.y * t,\r\n };\r\n } else {\r\n // 0.5 -> 1.0: baseVector에서 0으로 보간\r\n const t = (easedProgress - 0.5) * 2;\r\n dragVector = {\r\n x: baseVector.x * (1 - t),\r\n y: baseVector.y * (1 - t),\r\n };\r\n }\r\n }\r\n\r\n return {\r\n ...area,\r\n dragVector,\r\n };\r\n });\r\n }\r\n\r\n /**\r\n * 모든 영역의 진행도를 델타 타임만큼 업데이트\r\n * @param areas 왜곡 영역 배열\r\n * @param deltaTime 델타 타임 (초)\r\n * @returns 업데이트된 영역 배열\r\n */\r\n public static updateProgress(\r\n areas: DistortionArea[],\r\n deltaTime: number\r\n ): DistortionArea[] {\r\n return areas.map((area) => {\r\n // duration이 0이면 progress 업데이트 안 함\r\n if (area.movement.duration <= 0) {\r\n return area;\r\n }\r\n\r\n let newProgress = area.progress + deltaTime / area.movement.duration;\r\n newProgress %= 1.0; // 루프\r\n\r\n return {\r\n ...area,\r\n progress: newProgress,\r\n };\r\n });\r\n }\r\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};","import { useRef, useCallback, useState } from 'react';\r\nimport { useMouseVelocity } from './useMouseVelocity';\r\nimport { SpringPhysics } from '@/engine/SpringPhysics';\r\nimport { DistortionArea, Point } from '@/types';\r\nimport { MouseInteractionConfig } from '@/types/interaction';\r\n\r\n/**\r\n * 점이 사각형 내부에 있는지 확인\r\n */\r\nconst isPointInPolygon = (point: Point, polygon: Point[]): boolean => {\r\n\tlet inside = false;\r\n\tfor (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\r\n\t\tconst xi = polygon[i].x, yi = polygon[i].y;\r\n\t\tconst xj = polygon[j].x, yj = polygon[j].y;\r\n\t\tconst intersect = ((yi > point.y) !== (yj > point.y))\r\n\t\t\t&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);\r\n\t\tif (intersect) inside = !inside;\r\n\t}\r\n\treturn inside;\r\n};\r\n\r\n/**\r\n * 마우스 인터랙션 기반 기존 영역 제어 훅\r\n * 마우스가 지나가는 모든 영역에 효과 적용\r\n */\r\nexport const useMouseInteraction = (\r\n\tcontainerRef: React.RefObject,\r\n\tconfig: MouseInteractionConfig\r\n) => {\r\n\tconst { getState } = useMouseVelocity(containerRef);\r\n\tconst [interactingAreaIndices, setInteractingAreaIndices] = useState>(new Set());\r\n\tconst springPhysicsMapRef = useRef>(new Map());\r\n\r\n\t/**\r\n\t * 특정 영역의 스프링 물리 엔진 가져오기 (없으면 생성)\r\n\t */\r\n\tconst getSpringPhysics = useCallback((areaIndex: number, area?: DistortionArea): SpringPhysics => {\r\n\t\tif (!springPhysicsMapRef.current.has(areaIndex)) {\r\n\t\t\t// 영역별 물리 설정이 있으면 사용, 없으면 전역 설정 사용\r\n\t\t\tconst physicsConfig = area?.physics || config.physics;\r\n\t\t\tspringPhysicsMapRef.current.set(areaIndex, new SpringPhysics(physicsConfig));\r\n\t\t}\r\n\t\treturn springPhysicsMapRef.current.get(areaIndex)!;\r\n\t}, [config.physics]);\r\n\r\n\t/**\r\n\t * 기존 영역들의 dragVector를 마우스 인터랙션으로 업데이트\r\n\t */\r\n\tconst updateInteraction = useCallback((areas: DistortionArea[], deltaTime: number): DistortionArea[] => {\r\n\t\tif (!config.enabled) return areas;\r\n\r\n\t\t// 영역별 물리 설정이 변경되었을 수 있으므로 모든 스프링 업데이트\r\n\t\tareas.forEach((area, index) => {\r\n\t\t\tif (area.physics && springPhysicsMapRef.current.has(index)) {\r\n\t\t\t\tconst spring = springPhysicsMapRef.current.get(index)!;\r\n\t\t\t\tspring.setConfig(area.physics);\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tconst mouseState = getState();\r\n\r\n\t\t// 마우스 클릭/드래그 중이고 위치가 있으면\r\n\t\tif (mouseState.isDragging && mouseState.position) {\r\n\t\t\t// 현재 마우스 위치가 포함된 모든 영역 찾기\r\n\t\t\tconst currentlyInAreas = new Set();\r\n\t\t\tfor (let i = 0; i < areas.length; i++) {\r\n\t\t\t\tif (isPointInPolygon(mouseState.position, areas[i].basePoints)) {\r\n\t\t\t\t\tcurrentlyInAreas.add(i);\r\n\r\n\t\t\t\t\t// 새로 진입한 영역이면 스프링 리셋\r\n\t\t\t\t\tif (!interactingAreaIndices.has(i)) {\r\n\t\t\t\t\t\tgetSpringPhysics(i, areas[i]).reset();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// 이전에 인터랙션하던 영역에서 벗어났으면 평형으로 복귀\r\n\t\t\tinteractingAreaIndices.forEach((areaIndex) => {\r\n\t\t\t\tif (!currentlyInAreas.has(areaIndex)) {\r\n\t\t\t\t\tgetSpringPhysics(areaIndex, areas[areaIndex]).returnToEquilibrium();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\t// 인터랙션 영역 업데이트\r\n\t\t\tsetInteractingAreaIndices(currentlyInAreas);\r\n\r\n\t\t\t// 현재 위치의 모든 영역에 속도 적용\r\n\t\t\tconst velocityMult = config.velocityMultiplier || 1.0;\r\n\t\t\tconst velocityMag = Math.sqrt(\r\n\t\t\t\tmouseState.velocity.x ** 2 + mouseState.velocity.y ** 2\r\n\t\t\t);\r\n\t\t\tconst minVel = config.minVelocity || 0.05;\r\n\t\t\tconst maxVel = config.maxVelocity || 5.0;\r\n\r\n\t\t\t// 속도 클램핑\r\n\t\t\tlet clampedVelocity = mouseState.velocity;\r\n\t\t\tif (velocityMag > maxVel) {\r\n\t\t\t\tconst scale = maxVel / velocityMag;\r\n\t\t\t\tclampedVelocity = {\r\n\t\t\t\t\tx: mouseState.velocity.x * scale,\r\n\t\t\t\t\ty: mouseState.velocity.y * scale,\r\n\t\t\t\t};\r\n\t\t\t}\r\n\r\n\t\t\tcurrentlyInAreas.forEach((areaIndex) => {\r\n\t\t\t\tconst spring = getSpringPhysics(areaIndex, areas[areaIndex]);\r\n\r\n\t\t\t\tif (velocityMag >= minVel) {\r\n\t\t\t\t\t// 드래그 중: 마우스 속도를 목표로 설정\r\n\t\t\t\t\tspring.setTarget(clampedVelocity, velocityMult);\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// 드래그 중이지만 마우스가 멈춰있으면 평형으로 복귀\r\n\t\t\t\t\tspring.returnToEquilibrium();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\t// 마우스를 놓았으면 인터랙션 중이던 모든 영역에 튕김 효과\r\n\t\t\tif (interactingAreaIndices.size > 0) {\r\n\t\t\t\tconst velocityMult = config.velocityMultiplier || 1.0;\r\n\t\t\t\tconst maxVel = config.maxVelocity || 5.0;\r\n\r\n\t\t\t\t// 속도 클램핑\r\n\t\t\t\tconst velocityMag = Math.sqrt(\r\n\t\t\t\t\tmouseState.velocity.x ** 2 + mouseState.velocity.y ** 2\r\n\t\t\t\t);\r\n\t\t\t\tlet clampedVelocity = mouseState.velocity;\r\n\t\t\t\tif (velocityMag > maxVel) {\r\n\t\t\t\t\tconst scale = maxVel / velocityMag;\r\n\t\t\t\t\tclampedVelocity = {\r\n\t\t\t\t\t\tx: mouseState.velocity.x * scale,\r\n\t\t\t\t\t\ty: mouseState.velocity.y * scale,\r\n\t\t\t\t\t};\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// 모든 인터랙션 영역에 초기 속도 설정\r\n\t\t\t\tinteractingAreaIndices.forEach((areaIndex) => {\r\n\t\t\t\t\tconst spring = getSpringPhysics(areaIndex, areas[areaIndex]);\r\n\t\t\t\t\tspring.setInitialVelocity(clampedVelocity, velocityMult);\r\n\t\t\t\t});\r\n\r\n\t\t\t\tsetInteractingAreaIndices(new Set());\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// 모든 영역의 스프링 물리 업데이트\r\n\t\treturn areas.map((area, index) => {\r\n\t\t\tconst spring = springPhysicsMapRef.current.get(index);\r\n\t\t\tif (!spring) return area;\r\n\r\n\t\t\t// 현재 드래그 중인 영역이거나 스프링이 활성 상태일 때만 업데이트\r\n\t\t\tconst springVelocity = spring.getVelocity();\r\n\t\t\tconst springDisplacement = spring.getDisplacement();\r\n\t\t\tconst isSpringActive = Math.sqrt(springVelocity.x ** 2 + springVelocity.y ** 2) > 0.001 ||\r\n\t\t\t\tMath.sqrt(springDisplacement.x ** 2 + springDisplacement.y ** 2) > 0.001;\r\n\r\n\t\t\t// 드래그 중이 아니고 스프링도 비활성이면 업데이트 안 함\r\n\t\t\tif (!interactingAreaIndices.has(index) && !isSpringActive) {\r\n\t\t\t\treturn area;\r\n\t\t\t}\r\n\r\n\t\t\t// 스프링 물리 업데이트\r\n\t\t\tconst displacement = spring.update(deltaTime);\r\n\r\n\t\t\t// 변위가 거의 0이면 원래 dragVector 유지\r\n\t\t\tconst displacementMag = Math.sqrt(displacement.x ** 2 + displacement.y ** 2);\r\n\t\t\tif (displacementMag < 0.001) {\r\n\t\t\t\treturn area;\r\n\t\t\t}\r\n\r\n\t\t\t// 스프링 변위를 dragVector에 추가 (기존 애니메이션과 혼합)\r\n\t\t\t// dragVector는 텍스처 샘플링 좌표 이동이므로,\r\n\t\t\t// 마우스 드래그 방향과 반대로 적용해야 이미지가 드래그 방향으로 밀림\r\n\t\t\t// 예: 우→좌 드래그(velocity < 0) → dragVector > 0 → 이미지 왼쪽으로 밀림\r\n\t\t\treturn {\r\n\t\t\t\t...area,\r\n\t\t\t\tdragVector: {\r\n\t\t\t\t\tx: area.dragVector.x - displacement.x,\r\n\t\t\t\t\ty: area.dragVector.y - displacement.y,\r\n\t\t\t\t},\r\n\t\t\t};\r\n\t\t});\r\n\t}, [config, getState, interactingAreaIndices, getSpringPhysics]);\r\n\r\n\t/**\r\n\t * 물리 파라미터 업데이트\r\n\t */\r\n\tconst updateConfig = useCallback((newConfig: Partial) => {\r\n\t\tconst physicsConfig = newConfig.physics;\r\n\t\tif (physicsConfig) {\r\n\t\t\t// 모든 스프링 물리 엔진의 설정 업데이트\r\n\t\t\tspringPhysicsMapRef.current.forEach((spring) => {\r\n\t\t\t\tspring.setConfig(physicsConfig);\r\n\t\t\t});\r\n\t\t}\r\n\t}, []);\r\n\r\n\t/**\r\n\t * 모든 영역의 스프링 상태 리셋\r\n\t */\r\n\tconst reset = useCallback(() => {\r\n\t\tspringPhysicsMapRef.current.forEach((spring) => {\r\n\t\t\tspring.reset();\r\n\t\t});\r\n\t\tsetInteractingAreaIndices(new Set());\r\n\t}, []);\r\n\r\n\t/**\r\n\t * 현재 드래그 중인지 확인\r\n\t */\r\n\tconst isDragging = useCallback((): boolean => {\r\n\t\tconst mouseState = getState();\r\n\t\treturn mouseState.isDragging;\r\n\t}, [getState]);\r\n\r\n\t/**\r\n\t * 현재 인터랙션 중인 영역 인덱스 가져오기\r\n\t */\r\n\tconst getInteractingAreaIndices = useCallback((): Set => {\r\n\t\treturn interactingAreaIndices;\r\n\t}, [interactingAreaIndices]);\r\n\r\n\treturn {\r\n\t\tupdateInteraction,\r\n\t\tupdateConfig,\r\n\t\treset,\r\n\t\tisDragging,\r\n\t\tgetInteractingAreaIndices,\r\n\t};\r\n};\r\n","import { useRef, useCallback, useEffect } from 'react';\nimport { Point } from '@/types';\nimport { MouseState } from '@/types/interaction';\n\n/**\n * 마우스 위치, 속도, 가속도를 추적하는 훅\n */\nexport const useMouseVelocity = (containerRef: React.RefObject) => {\n\tconst mouseStateRef = useRef({\n\t\tposition: null,\n\t\tprevPosition: null,\n\t\tvelocity: { x: 0, y: 0 },\n\t\tacceleration: { x: 0, y: 0 },\n\t\tisHovering: false,\n\t\tisDragging: false,\n\t});\n\n\tconst lastUpdateTimeRef = useRef(Date.now());\n\tconst prevVelocityRef = useRef({ x: 0, y: 0 });\n\n\t/**\n\t * 픽셀 좌표를 정규화 좌표(0-1)로 변환\n\t */\n\tconst toNormalized = useCallback((clientX: number, clientY: number): Point | null => {\n\t\tif (!containerRef.current) return null;\n\t\tconst rect = containerRef.current.getBoundingClientRect();\n\t\treturn {\n\t\t\tx: (clientX - rect.left) / rect.width,\n\t\t\ty: (clientY - rect.top) / rect.height,\n\t\t};\n\t}, [containerRef]);\n\n\t/**\n\t * 위치 업데이트 (마우스/터치 공통 로직)\n\t */\n\tconst updatePosition = useCallback((clientX: number, clientY: number) => {\n\t\tconst now = Date.now();\n\t\tconst deltaTime = (now - lastUpdateTimeRef.current) / 1000; // 초 단위\n\t\tlastUpdateTimeRef.current = now;\n\n\t\tconst normalizedPos = toNormalized(clientX, clientY);\n\t\tif (!normalizedPos) return;\n\n\t\tconst state = mouseStateRef.current;\n\t\tconst prevPos = state.position;\n\n\t\t// 속도 계산 (변위 / 시간)\n\t\tlet velocity: Point = { x: 0, y: 0 };\n\t\tif (prevPos && deltaTime > 0) {\n\t\t\tvelocity = {\n\t\t\t\tx: (normalizedPos.x - prevPos.x) / deltaTime,\n\t\t\t\ty: (normalizedPos.y - prevPos.y) / deltaTime,\n\t\t\t};\n\t\t}\n\n\t\t// 가속도 계산 (속도 변화 / 시간)\n\t\tconst prevVel = prevVelocityRef.current;\n\t\tlet acceleration: Point = { x: 0, y: 0 };\n\t\tif (deltaTime > 0) {\n\t\t\tacceleration = {\n\t\t\t\tx: (velocity.x - prevVel.x) / deltaTime,\n\t\t\t\ty: (velocity.y - prevVel.y) / deltaTime,\n\t\t\t};\n\t\t}\n\n\t\t// 상태 업데이트\n\t\tmouseStateRef.current = {\n\t\t\tposition: normalizedPos,\n\t\t\tprevPosition: prevPos,\n\t\t\tvelocity,\n\t\t\tacceleration,\n\t\t\tisHovering: true,\n\t\t\tisDragging: state.isDragging,\n\t\t};\n\t\tprevVelocityRef.current = velocity;\n\t}, [toNormalized]);\n\n\t/**\n\t * 마우스 이동 핸들러\n\t */\n\tconst handleMouseMove = useCallback((e: MouseEvent) => {\n\t\tupdatePosition(e.clientX, e.clientY);\n\t}, [updatePosition]);\n\n\t/**\n\t * 마우스 진입\n\t */\n\tconst handleMouseEnter = useCallback(() => {\n\t\tmouseStateRef.current.isHovering = true;\n\t}, []);\n\n\t/**\n\t * 마우스 나감\n\t */\n\tconst handleMouseLeave = useCallback(() => {\n\t\tmouseStateRef.current = {\n\t\t\tposition: null,\n\t\t\tprevPosition: null,\n\t\t\tvelocity: { x: 0, y: 0 },\n\t\t\tacceleration: { x: 0, y: 0 },\n\t\t\tisHovering: false,\n\t\t\tisDragging: false,\n\t\t};\n\t\tprevVelocityRef.current = { x: 0, y: 0 };\n\t}, []);\n\n\t/**\n\t * 마우스 다운\n\t */\n\tconst handleMouseDown = useCallback(() => {\n\t\tmouseStateRef.current.isDragging = true;\n\t}, []);\n\n\t/**\n\t * 마우스 업\n\t */\n\tconst handleMouseUp = useCallback(() => {\n\t\tmouseStateRef.current.isDragging = false;\n\t}, []);\n\n\t/**\n\t * 터치 이동 핸들러\n\t */\n\tconst handleTouchMove = useCallback((e: TouchEvent) => {\n\t\te.preventDefault(); // 스크롤 방지\n\t\tif (e.touches.length > 0) {\n\t\t\tconst touch = e.touches[0];\n\t\t\tupdatePosition(touch.clientX, touch.clientY);\n\t\t}\n\t}, [updatePosition]);\n\n\t/**\n\t * 터치 시작 핸들러\n\t */\n\tconst handleTouchStart = useCallback((e: TouchEvent) => {\n\t\te.preventDefault(); // 스크롤 방지\n\t\tmouseStateRef.current.isDragging = true;\n\t\tmouseStateRef.current.isHovering = true;\n\t\tif (e.touches.length > 0) {\n\t\t\tconst touch = e.touches[0];\n\t\t\tupdatePosition(touch.clientX, touch.clientY);\n\t\t}\n\t}, [updatePosition]);\n\n\t/**\n\t * 터치 종료 핸들러\n\t */\n\tconst handleTouchEnd = useCallback(() => {\n\t\tmouseStateRef.current.isDragging = false;\n\t\tmouseStateRef.current.isHovering = false;\n\t\tmouseStateRef.current.position = null;\n\t\tmouseStateRef.current.prevPosition = null;\n\t\tmouseStateRef.current.velocity = { x: 0, y: 0 };\n\t\tmouseStateRef.current.acceleration = { x: 0, y: 0 };\n\t\tprevVelocityRef.current = { x: 0, y: 0 };\n\t}, []);\n\n\t/**\n\t * 이벤트 리스너 등록\n\t */\n\tuseEffect(() => {\n\t\tconst container = containerRef.current;\n\t\tif (!container) return;\n\n\t\t// 마우스 이벤트\n\t\tcontainer.addEventListener('mousemove', handleMouseMove);\n\t\tcontainer.addEventListener('mouseenter', handleMouseEnter);\n\t\tcontainer.addEventListener('mouseleave', handleMouseLeave);\n\t\tcontainer.addEventListener('mousedown', handleMouseDown);\n\t\twindow.addEventListener('mouseup', handleMouseUp);\n\n\t\t// 터치 이벤트 (passive: false로 스크롤 방지 가능)\n\t\tcontainer.addEventListener('touchmove', handleTouchMove, { passive: false });\n\t\tcontainer.addEventListener('touchstart', handleTouchStart, { passive: false });\n\t\tcontainer.addEventListener('touchend', handleTouchEnd);\n\t\tcontainer.addEventListener('touchcancel', handleTouchEnd);\n\n\t\treturn () => {\n\t\t\t// 마우스 이벤트 제거\n\t\t\tcontainer.removeEventListener('mousemove', handleMouseMove);\n\t\t\tcontainer.removeEventListener('mouseenter', handleMouseEnter);\n\t\t\tcontainer.removeEventListener('mouseleave', handleMouseLeave);\n\t\t\tcontainer.removeEventListener('mousedown', handleMouseDown);\n\t\t\twindow.removeEventListener('mouseup', handleMouseUp);\n\n\t\t\t// 터치 이벤트 제거\n\t\t\tcontainer.removeEventListener('touchmove', handleTouchMove);\n\t\t\tcontainer.removeEventListener('touchstart', handleTouchStart);\n\t\t\tcontainer.removeEventListener('touchend', handleTouchEnd);\n\t\t\tcontainer.removeEventListener('touchcancel', handleTouchEnd);\n\t\t};\n\t}, [containerRef, handleMouseMove, handleMouseEnter, handleMouseLeave, handleMouseDown, handleMouseUp, handleTouchMove, handleTouchStart, handleTouchEnd]);\n\n\t/**\n\t * 현재 마우스 상태 가져오기\n\t */\n\tconst getState = useCallback((): MouseState => {\n\t\treturn { ...mouseStateRef.current };\n\t}, []);\n\n\treturn {\n\t\tgetState,\n\t};\n};\n","import { Point } from '@/types';\r\nimport { SpringPhysicsConfig, SpringState } from '@/types/interaction';\r\n\r\n/**\r\n * 스프링 기반 물리 시뮬레이션 엔진\r\n * Hooke's Law와 감쇠를 적용한 스프링-댐퍼 시스템\r\n */\r\nexport class SpringPhysics {\r\n\tprivate config: SpringPhysicsConfig;\r\n\tprivate state: SpringState;\r\n\r\n\tconstructor(config: SpringPhysicsConfig) {\r\n\t\tthis.config = config;\r\n\t\tthis.state = {\r\n\t\t\tdisplacement: { x: 0, y: 0 },\r\n\t\t\tvelocity: { x: 0, y: 0 },\r\n\t\t\ttarget: { x: 0, y: 0 },\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * 물리 파라미터 업데이트\r\n\t */\r\n\tpublic setConfig(config: Partial) {\r\n\t\tthis.config = { ...this.config, ...config };\r\n\t}\r\n\r\n\t/**\r\n\t * 목표 위치 설정 (마우스 속도 기반)\r\n\t */\r\n\tpublic setTarget(velocity: Point, velocityMultiplier: number = 1.0) {\r\n\t\t// 속도에 승수를 곱해서 목표 변위로 설정\r\n\t\tthis.state.target = {\r\n\t\t\tx: velocity.x * velocityMultiplier,\r\n\t\t\ty: velocity.y * velocityMultiplier,\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * 초기 속도 설정 (드래그 방향과 속도를 즉시 반영)\r\n\t * 드래그 방향으로 즉시 튕기는 효과\r\n\t */\r\n\tpublic setInitialVelocity(velocity: Point, multiplier: number = 1.0) {\r\n\t\t// 현재 속도를 즉시 변경\r\n\t\tthis.state.velocity = {\r\n\t\t\tx: velocity.x * multiplier,\r\n\t\t\ty: velocity.y * multiplier,\r\n\t\t};\r\n\t\t// 목표는 0 (평형 상태로 돌아가도록)\r\n\t\tthis.state.target = { x: 0, y: 0 };\r\n\t}\r\n\r\n\t/**\r\n\t * 스프링 물리 업데이트 (Hooke's Law + Damping)\r\n\t * F = -k * x - c * v\r\n\t * a = F / m\r\n\t * v += a * dt\r\n\t * x += v * dt\r\n\t */\r\n\tpublic update(deltaTime: number): Point {\r\n\t\tconst { stiffness, damping, mass } = this.config;\r\n\t\tconst { displacement, velocity, target } = this.state;\r\n\r\n\t\t// 평형 위치로부터의 변위 (target은 마우스 속도에서 계산된 목표)\r\n\t\tconst dx = displacement.x - target.x;\r\n\t\tconst dy = displacement.y - target.y;\r\n\r\n\t\t// 스프링 힘: F = -k * x (복원력)\r\n\t\tconst springForceX = -stiffness * dx;\r\n\t\tconst springForceY = -stiffness * dy;\r\n\r\n\t\t// 감쇠 힘: F = -c * v (마찰력)\r\n\t\tconst dampingForceX = -damping * velocity.x;\r\n\t\tconst dampingForceY = -damping * velocity.y;\r\n\r\n\t\t// 총 힘\r\n\t\tconst totalForceX = springForceX + dampingForceX;\r\n\t\tconst totalForceY = springForceY + dampingForceY;\r\n\r\n\t\t// 가속도: a = F / m\r\n\t\tconst accelerationX = totalForceX / mass;\r\n\t\tconst accelerationY = totalForceY / mass;\r\n\r\n\t\t// 속도 업데이트: v += a * dt\r\n\t\tconst newVelocityX = velocity.x + accelerationX * deltaTime;\r\n\t\tconst newVelocityY = velocity.y + accelerationY * deltaTime;\r\n\r\n\t\t// 위치 업데이트: x += v * dt\r\n\t\tconst newDisplacementX = displacement.x + newVelocityX * deltaTime;\r\n\t\tconst newDisplacementY = displacement.y + newVelocityY * deltaTime;\r\n\r\n\t\t// 상태 저장\r\n\t\tthis.state = {\r\n\t\t\tdisplacement: { x: newDisplacementX, y: newDisplacementY },\r\n\t\t\tvelocity: { x: newVelocityX, y: newVelocityY },\r\n\t\t\ttarget,\r\n\t\t};\r\n\r\n\t\t// 매우 작은 움직임은 0으로 처리 (정지 판정)\r\n\t\tconst isNearlyZero = (val: number) => Math.abs(val) < 0.0001;\r\n\t\tif (isNearlyZero(this.state.displacement.x) && isNearlyZero(this.state.displacement.y) &&\r\n\t\t\tisNearlyZero(this.state.velocity.x) && isNearlyZero(this.state.velocity.y)) {\r\n\t\t\tthis.reset();\r\n\t\t}\r\n\r\n\t\treturn this.state.displacement;\r\n\t}\r\n\r\n\t/**\r\n\t * 즉시 충격 적용 (마우스 가속도 기반)\r\n\t */\r\n\tpublic applyImpulse(acceleration: Point, multiplier: number = 1.0) {\r\n\t\t// 가속도를 속도로 변환하여 즉시 적용\r\n\t\tthis.state.velocity.x += acceleration.x * multiplier;\r\n\t\tthis.state.velocity.y += acceleration.y * multiplier;\r\n\t}\r\n\r\n\t/**\r\n\t * 현재 변위 가져오기\r\n\t */\r\n\tpublic getDisplacement(): Point {\r\n\t\treturn { ...this.state.displacement };\r\n\t}\r\n\r\n\t/**\r\n\t * 현재 속도 가져오기\r\n\t */\r\n\tpublic getVelocity(): Point {\r\n\t\treturn { ...this.state.velocity };\r\n\t}\r\n\r\n\t/**\r\n\t * 상태 리셋\r\n\t */\r\n\tpublic reset() {\r\n\t\tthis.state = {\r\n\t\t\tdisplacement: { x: 0, y: 0 },\r\n\t\t\tvelocity: { x: 0, y: 0 },\r\n\t\t\ttarget: { x: 0, y: 0 },\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * 마우스가 멈췄을 때 목표를 0으로 설정 (평형 상태로 복귀)\r\n\t */\r\n\tpublic returnToEquilibrium() {\r\n\t\tthis.state.target = { x: 0, y: 0 };\r\n\t}\r\n}\r\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;","import React, {useRef, useEffect, useState, useCallback, useMemo} from 'react';\r\nimport {DistortionArea, Point} from '@/types';\r\nimport {ImageDistortion} from '@/components/ImageDistortion';\r\nimport {EditorCanvasStyle} from '../types';\r\nimport {DEFAULT_EDITOR_CANVAS_STYLE} from '@/editor';\r\n\r\nexport interface EditorCanvasProps {\r\n\tareas: DistortionArea[];\r\n\tselectedAreaId: string | null;\r\n\timageSrc: string;\r\n\twidth: number;\r\n\theight: number;\r\n\tonUpdatePoint: (areaId: string, pointIndex: number, point: Point) => void;\r\n\tonUpdateArea: (areaId: string, updates: Partial) => void;\r\n\tdraggingPointIndex: number | null;\r\n\tonStartDragging: (pointIndex: number) => void;\r\n\tonStopDragging: () => void;\r\n\t/** 에디터 캔버스 스타일 커스터마이징 */\r\n\tstyle?: EditorCanvasStyle;\r\n\t/** 에디터 UI 표시 여부 (기본값: true) */\r\n\tshowEditor?: boolean;\r\n}\r\n\r\nexport const EditorCanvas: React.FC = ({\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t areas,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t selectedAreaId,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t imageSrc,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t width,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t height,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onUpdatePoint,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onUpdateArea,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t draggingPointIndex,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onStartDragging,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onStopDragging,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t style: customStyle,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t showEditor = true,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t }) => {\r\n\tconst containerRef = useRef(null);\r\n\tconst [canvasSize, setCanvasSize] = useState({width: 0, height: 0});\r\n\tconst [isDraggingArea, setIsDraggingArea] = useState(false);\r\n\tconst [dragStartPos, setDragStartPos] = useState(null);\r\n\r\n\t// 스타일 병합 (커스텀 스타일 우선)\r\n\tconst editorStyle = useMemo(() => ({\r\n\t\t...DEFAULT_EDITOR_CANVAS_STYLE,\r\n\t\t...customStyle,\r\n\t\tcircleLevels: customStyle?.circleLevels || DEFAULT_EDITOR_CANVAS_STYLE.circleLevels,\r\n\t\tcenterPoint: {\r\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.centerPoint,\r\n\t\t\t...customStyle?.centerPoint,\r\n\t\t},\r\n\t\tpointHandle: {\r\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.pointHandle,\r\n\t\t\t...customStyle?.pointHandle,\r\n\t\t},\r\n\t\tareaOutline: {\r\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.areaOutline,\r\n\t\t\t...customStyle?.areaOutline,\r\n\t\t},\r\n\t}), [customStyle]);\r\n\r\n\t// 컨테이너 크기 측정\r\n\tuseEffect(() => {\r\n\t\tif (!containerRef.current) return;\r\n\t\tconst rect = containerRef.current.getBoundingClientRect();\r\n\t\tsetCanvasSize({width: rect.width, height: rect.height});\r\n\t}, [width, height]);\r\n\r\n\t// 선택된 영역 찾기\r\n\tconst selectedArea = areas.find((a) => a.id === selectedAreaId);\r\n\r\n\t// 점이 사각형 내부에 있는지 확인 (Point-in-Polygon test)\r\n\tconst isPointInPolygon = useCallback((point: Point, polygon: Point[]): boolean => {\r\n\t\tlet inside = false;\r\n\t\tfor (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\r\n\t\t\tconst xi = polygon[i].x, yi = polygon[i].y;\r\n\t\t\tconst xj = polygon[j].x, yj = polygon[j].y;\r\n\r\n\t\t\tconst intersect = ((yi > point.y) !== (yj > point.y))\r\n\t\t\t\t&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);\r\n\t\t\tif (intersect) inside = !inside;\r\n\t\t}\r\n\t\treturn inside;\r\n\t}, []);\r\n\r\n\t// 포인트 핸들 클릭/터치 핸들러\r\n\tconst handlePointDown = useCallback(\r\n\t\t(pointIndex: number) => (e: React.MouseEvent | React.TouchEvent) => {\r\n\t\t\te.preventDefault();\r\n\t\t\te.stopPropagation();\r\n\t\t\tonStartDragging(pointIndex);\r\n\t\t},\r\n\t\t[onStartDragging]\r\n\t);\r\n\r\n\t// 캔버스 다운 (마우스/터치 공통)\r\n\tconst handleCanvasDown = useCallback(\r\n\t\t(e: React.MouseEvent | React.TouchEvent) => {\r\n\t\t\t// 에디터가 숨겨진 상태면 동작하지 않음\r\n\t\t\tif (!showEditor || !selectedArea || !containerRef.current) return;\r\n\r\n\t\t\tconst rect = containerRef.current.getBoundingClientRect();\r\n\r\n\t\t\t// 마우스 또는 터치 좌표 추출\r\n\t\t\tlet clientX: number, clientY: number;\r\n\t\t\tif ('touches' in e) {\r\n\t\t\t\tif (e.touches.length === 0) return;\r\n\t\t\t\tclientX = e.touches[0].clientX;\r\n\t\t\t\tclientY = e.touches[0].clientY;\r\n\t\t\t} else {\r\n\t\t\t\tclientX = e.clientX;\r\n\t\t\t\tclientY = e.clientY;\r\n\t\t\t}\r\n\r\n\t\t\tconst x = (clientX - rect.left) / rect.width;\r\n\t\t\tconst y = (clientY - rect.top) / rect.height;\r\n\t\t\tconst clickPoint = { x, y };\r\n\r\n\t\t\t// 사각형 내부를 클릭했는지 확인\r\n\t\t\tif (isPointInPolygon(clickPoint, selectedArea.basePoints)) {\r\n\t\t\t\tsetIsDraggingArea(true);\r\n\t\t\t\tsetDragStartPos(clickPoint);\r\n\t\t\t\te.preventDefault();\r\n\t\t\t}\r\n\t\t},\r\n\t\t[showEditor, selectedArea, isPointInPolygon]\r\n\t);\r\n\r\n\t// 이동 (마우스/터치 공통)\r\n\tconst handleMove = useCallback(\r\n\t\t(e: React.MouseEvent | React.TouchEvent) => {\r\n\t\t\t// 에디터가 숨겨진 상태면 동작하지 않음\r\n\t\t\tif (!showEditor || !selectedArea || !containerRef.current) return;\r\n\r\n\t\t\t// 터치 이벤트면 스크롤 방지\r\n\t\t\tif ('touches' in e && (draggingPointIndex !== null || isDraggingArea)) {\r\n\t\t\t\te.preventDefault();\r\n\t\t\t}\r\n\r\n\t\t\tconst rect = containerRef.current.getBoundingClientRect();\r\n\r\n\t\t\t// 마우스 또는 터치 좌표 추출\r\n\t\t\tlet clientX: number, clientY: number;\r\n\t\t\tif ('touches' in e) {\r\n\t\t\t\tif (e.touches.length === 0) return;\r\n\t\t\t\tclientX = e.touches[0].clientX;\r\n\t\t\t\tclientY = e.touches[0].clientY;\r\n\t\t\t} else {\r\n\t\t\t\tclientX = e.clientX;\r\n\t\t\t\tclientY = e.clientY;\r\n\t\t\t}\r\n\r\n\t\t\tconst x = (clientX - rect.left) / rect.width;\r\n\t\t\tconst y = (clientY - rect.top) / rect.height;\r\n\r\n\t\t\t// 포인트 드래그 중\r\n\t\t\tif (draggingPointIndex !== null) {\r\n\t\t\t\tconst clampedX = Math.max(0, Math.min(1, x));\r\n\t\t\t\tconst clampedY = Math.max(0, Math.min(1, y));\r\n\t\t\t\tonUpdatePoint(selectedArea.id, draggingPointIndex, {x: clampedX, y: clampedY});\r\n\t\t\t}\r\n\t\t\t// 사각형 전체 드래그 중\r\n\t\t\telse if (isDraggingArea && dragStartPos) {\r\n\t\t\t\tconst deltaX = x - dragStartPos.x;\r\n\t\t\t\tconst deltaY = y - dragStartPos.y;\r\n\r\n\t\t\t\t// 모든 포인트를 delta만큼 이동\r\n\t\t\t\tconst newPoints = selectedArea.basePoints.map((point) => ({\r\n\t\t\t\t\tx: Math.max(0, Math.min(1, point.x + deltaX)),\r\n\t\t\t\t\ty: Math.max(0, Math.min(1, point.y + deltaY)),\r\n\t\t\t\t})) as [Point, Point, Point, Point];\r\n\r\n\t\t\t\tonUpdateArea(selectedArea.id, { basePoints: newPoints });\r\n\t\t\t\tsetDragStartPos({ x, y }); // 다음 프레임을 위해 시작 위치 업데이트\r\n\t\t\t}\r\n\t\t},\r\n\t\t[showEditor, draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]\r\n\t);\r\n\r\n\t// 업 (마우스/터치 공통)\r\n\tconst handleUp = useCallback(() => {\r\n\t\tif (draggingPointIndex !== null) {\r\n\t\t\tonStopDragging();\r\n\t\t}\r\n\t\tif (isDraggingArea) {\r\n\t\t\tsetIsDraggingArea(false);\r\n\t\t\tsetDragStartPos(null);\r\n\t\t}\r\n\t}, [draggingPointIndex, isDraggingArea, onStopDragging]);\r\n\r\n\t// 전역 업 이벤트 (마우스/터치)\r\n\tuseEffect(() => {\r\n\t\tif (draggingPointIndex !== null || isDraggingArea) {\r\n\t\t\twindow.addEventListener('mouseup', handleUp);\r\n\t\t\twindow.addEventListener('touchend', handleUp);\r\n\t\t\twindow.addEventListener('touchcancel', handleUp);\r\n\t\t\treturn () => {\r\n\t\t\t\twindow.removeEventListener('mouseup', handleUp);\r\n\t\t\t\twindow.removeEventListener('touchend', handleUp);\r\n\t\t\t\twindow.removeEventListener('touchcancel', handleUp);\r\n\t\t\t};\r\n\t\t}\r\n\t}, [draggingPointIndex, isDraggingArea, handleUp]);\r\n\r\n\t// UV 좌표를 픽셀 좌표로 변환 (셰이더와 동일한 bilinear interpolation)\r\n\tconst uvToPixel = (\r\n\t\tu: number,\r\n\t\tv: number,\r\n\t\tpoints: [Point, Point, Point, Point],\r\n\t\tcanvasWidth: number,\r\n\t\tcanvasHeight: number\r\n\t): { x: number; y: number } => {\r\n\t\t// p0=좌상, p1=우상, p2=우하, p3=좌하\r\n\t\tconst [p0, p1, p2, p3] = points;\r\n\r\n\t\t// 셰이더 computeUV와 동일한 순서로 bilinear interpolation\r\n\t\t// left = mix(p0, p1, u) -> 상단 가장자리\r\n\t\t// right = mix(p3, p2, u) -> 하단 가장자리\r\n\t\t// position = mix(left, right, v)\r\n\t\tconst leftX = p0.x * (1 - u) + p1.x * u;\r\n\t\tconst leftY = p0.y * (1 - u) + p1.y * u;\r\n\r\n\t\tconst rightX = p3.x * (1 - u) + p2.x * u;\r\n\t\tconst rightY = p3.y * (1 - u) + p2.y * u;\r\n\r\n\t\tconst posX = leftX * (1 - v) + rightX * v;\r\n\t\tconst posY = leftY * (1 - v) + rightY * v;\r\n\r\n\t\treturn {\r\n\t\t\tx: posX * canvasWidth,\r\n\t\t\ty: posY * canvasHeight,\r\n\t\t};\r\n\t};\r\n\r\n\t// UV 좌표계의 원을 정확히 그리기 (찌그러진 원 형태)\r\n\tconst drawDistortionCircle = useCallback((\r\n\t\tctx: CanvasRenderingContext2D,\r\n\t\tpoints: [Point, Point, Point, Point],\r\n\t\tcanvasWidth: number,\r\n\t\tcanvasHeight: number\r\n\t) => {\r\n\t\tconst segments = 128; // 원을 128개 세그먼트로 촘촘히 분할\r\n\t\tconst centerU = 0.5;\r\n\t\tconst centerV = 0.5;\r\n\r\n\t\tconst circleLevels = editorStyle.circleLevels || [];\r\n\r\n\t\t// 원 레벨별로 그리기 (외부 -> 내부 순)\r\n\t\tcircleLevels.forEach((level, index) => {\r\n\t\t\tconst levelPoints: { x: number; y: number }[] = [];\r\n\t\t\tfor (let i = 0; i <= segments; i++) {\r\n\t\t\t\tconst theta = (i / segments) * 2 * Math.PI;\r\n\t\t\t\tconst u = centerU - level.radius * Math.sin(theta);\r\n\t\t\t\tconst v = centerV + level.radius * Math.cos(theta);\r\n\t\t\t\tconst pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);\r\n\t\t\t\tlevelPoints.push(pixelPos);\r\n\t\t\t}\r\n\r\n\t\t\tctx.beginPath();\r\n\t\t\tctx.moveTo(levelPoints[0].x, levelPoints[0].y);\r\n\t\t\tfor (let i = 1; i < levelPoints.length; i++) {\r\n\t\t\t\tctx.lineTo(levelPoints[i].x, levelPoints[i].y);\r\n\t\t\t}\r\n\t\t\tctx.closePath();\r\n\r\n\t\t\t// 원 테두리\r\n\t\t\tconst baseColor = level.color || 'rgba(255, 200, 0, 1)';\r\n\t\t\t// baseColor에서 RGB 추출하고 opacity 적용\r\n\t\t\tconst colorWithOpacity = baseColor.replace(/rgba?\\(([^)]+)\\)/, (_, rgb) => {\r\n\t\t\t\tconst parts = rgb.split(',').map((p: string) => p.trim());\r\n\t\t\t\treturn `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${level.opacity})`;\r\n\t\t\t});\r\n\t\t\tctx.strokeStyle = colorWithOpacity;\r\n\t\t\tctx.lineWidth = level.lineWidth;\r\n\t\t\tif (level.dashPattern) {\r\n\t\t\t\tctx.setLineDash(level.dashPattern);\r\n\t\t\t}\r\n\t\t\tctx.stroke();\r\n\t\t\tctx.setLineDash([]);\r\n\r\n\t\t\t// 가장 외부 원만 내부 채우기\r\n\t\t\tif (index === 0 && editorStyle.circleFillColor) {\r\n\t\t\t\tctx.fillStyle = editorStyle.circleFillColor;\r\n\t\t\t\tctx.fill();\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// 중심점 표시\r\n\t\tconst centerPointStyle = editorStyle.centerPoint || {};\r\n\t\tconst centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight);\r\n\t\tctx.beginPath();\r\n\t\tctx.arc(centerPixel.x, centerPixel.y, centerPointStyle.radius || 5, 0, 2 * Math.PI);\r\n\t\tif (centerPointStyle.fillColor) {\r\n\t\t\tctx.fillStyle = centerPointStyle.fillColor;\r\n\t\t\tctx.fill();\r\n\t\t}\r\n\t\tif (centerPointStyle.strokeColor) {\r\n\t\t\tctx.strokeStyle = centerPointStyle.strokeColor;\r\n\t\t\tctx.lineWidth = centerPointStyle.strokeWidth || 2;\r\n\t\t\tctx.stroke();\r\n\t\t}\r\n\t}, [editorStyle]);\r\n\r\n\t// 커서 스타일 결정\r\n\tconst getCursorStyle = () => {\r\n\t\tif (draggingPointIndex !== null) return 'grabbing';\r\n\t\tif (isDraggingArea) return 'grabbing';\r\n\t\treturn 'default';\r\n\t};\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t{/* ImageDistortion 컴포넌트 */}\r\n\t\t\t\r\n\r\n\t\t\t{/* 오버레이 SVG - 에디터 모드일 때만 표시 */}\r\n\t\t\t{showEditor && (\r\n\t\t\t\t\r\n\t\t\t\t\t{/* 모든 영역의 사각형 표시 */}\r\n\t\t\t\t\t{areas.map((area) => {\r\n\t\t\t\t\tconst isSelected = area.id === selectedAreaId;\r\n\t\t\t\t\tconst points = area.basePoints;\r\n\t\t\t\t\tconst outlineStyle = editorStyle.areaOutline || {};\r\n\t\t\t\t\treturn (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{/* 사각형 배경 및 경계선 */}\r\n\t\t\t\t\t\t\t `${p.x * canvasSize.width},${p.y * canvasSize.height}`)\r\n\t\t\t\t\t\t\t\t\t.join(' ')}\r\n\t\t\t\t\t\t\t\tfill={isSelected ? (outlineStyle.selectedFillColor || 'rgba(0, 170, 255, 0.08)') : (outlineStyle.unselectedFillColor || 'rgba(136, 136, 136, 0.03)')}\r\n\t\t\t\t\t\t\t\tstroke={isSelected ? (outlineStyle.selectedColor || '#00aaff') : (outlineStyle.unselectedColor || '#888')}\r\n\t\t\t\t\t\t\t\tstrokeWidth={isSelected ? (outlineStyle.selectedWidth || 2) : (outlineStyle.unselectedWidth || 1)}\r\n\t\t\t\t\t\t\t\tstrokeDasharray={isSelected ? '0' : (outlineStyle.unselectedDashPattern?.join(',') || '5,5')}\r\n\t\t\t\t\t\t\t\topacity={isSelected ? 1 : 0.5}\r\n\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t);\r\n\t\t\t\t\t})}\r\n\t\t\t\t\r\n\t\t\t)}\r\n\r\n\t\t\t{/* 선택된 영역의 타원 가이드 (Canvas로 그리기) - 에디터 모드일 때만 표시 */}\r\n\t\t\t{showEditor && selectedArea && canvasSize.width > 0 && (\r\n\t\t\t\t {\r\n\t\t\t\t\t\tif (canvas) {\r\n\t\t\t\t\t\t\tconst ctx = canvas.getContext('2d');\r\n\t\t\t\t\t\t\tif (ctx) {\r\n\t\t\t\t\t\t\t\tctx.clearRect(0, 0, canvasSize.width, canvasSize.height);\r\n\t\t\t\t\t\t\t\tdrawDistortionCircle(ctx, selectedArea.basePoints, canvasSize.width, canvasSize.height);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}}\r\n\t\t\t\t/>\r\n\t\t\t)}\r\n\r\n\t\t\t{/* 선택된 영역의 포인트 핸들 - 에디터 모드일 때만 표시 */}\r\n\t\t\t{showEditor && selectedArea &&\r\n\t\t\t\tselectedArea.basePoints.map((point, index) => {\r\n\t\t\t\t\tconst handleStyle = editorStyle.pointHandle || {};\r\n\t\t\t\t\treturn (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\tP{index + 1}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t);\r\n\t\t\t\t})}\r\n\t\t\r\n\t);\r\n};\r\n","import React from 'react';\nimport { DistortionArea } from '../../types/area';\n\nexport interface AreaListProps {\n\tareas: DistortionArea[];\n\tselectedAreaId: string | null;\n\tonSelectArea: (areaId: string) => void;\n\tonRemoveArea: (areaId: string) => void;\n\tonAddArea: () => void;\n}\n\nexport const AreaList: React.FC = ({\n\tareas,\n\tselectedAreaId,\n\tonSelectArea,\n\tonRemoveArea,\n\tonAddArea,\n}) => {\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t

왜곡 영역

\n\t\t\t\t= 8}\n\t\t\t\t\tclassName=\"btn-add\"\n\t\t\t\t\ttitle={areas.length >= 8 ? '최대 8개 영역까지 지원' : '새 영역 추가'}\n\t\t\t\t>\n\t\t\t\t\t+ 추가\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t\t{areas.length === 0 ? (\n\t\t\t\t\t
영역이 없습니다. + 추가 버튼을 눌러주세요.
\n\t\t\t\t) : (\n\t\t\t\t\tareas.map((area, index) => (\n\t\t\t\t\t\t onSelectArea(area.id)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t영역 {index + 1}\n\t\t\t\t\t\t\t\t강도: {(area.distortionStrength * 100).toFixed(0)}%\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t\t\t\t\tonRemoveArea(area.id);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\tclassName=\"btn-remove\"\n\t\t\t\t\t\t\t\ttitle=\"영역 삭제\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t))\n\t\t\t\t)}\n\t\t\t
\n\t\t\n\t);\n};\n","import React from 'react';\nimport { DistortionArea, EasingFunction } from '../../types/area';\n\nexport interface ParameterPanelProps {\n\tarea: DistortionArea | null;\n\tonUpdateArea: (updates: Partial) => void;\n}\n\nconst EASING_OPTIONS: { value: EasingFunction; label: string }[] = [\n\t{ value: 'linear', label: '선형 (Linear)' },\n\t{ value: 'easeIn', label: '가속 (Ease In)' },\n\t{ value: 'easeOut', label: '감속 (Ease Out)' },\n\t{ value: 'easeInOut', label: '가감속 (Ease In Out)' },\n\t{ value: 'easeInQuad', label: '가속² (Ease In Quad)' },\n\t{ value: 'easeOutQuad', label: '감속² (Ease Out Quad)' },\n];\n\nexport const ParameterPanel: React.FC = ({ area, onUpdateArea }) => {\n\tif (!area) {\n\t\treturn (\n\t\t\t
\n\t\t\t\t
영역을 선택해주세요
\n\t\t\t
\n\t\t);\n\t}\n\n\treturn (\n\t\t
\n\t\t\t

파라미터 편집

\n\n\t\t\t{/* 왜곡 강도 */}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t onUpdateArea({ distortionStrength: parseFloat(e.target.value) })}\n\t\t\t\t\tclassName=\"slider\"\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{/* 애니메이션 지속 시간 */}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\tonUpdateArea({\n\t\t\t\t\t\t\tmovement: { ...area.movement, duration: parseFloat(e.target.value) },\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tclassName=\"input-number\"\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{/* 이징 함수 */}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\tonUpdateArea({\n\t\t\t\t\t\t\tmovement: { ...area.movement, easing: e.target.value as EasingFunction },\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tclassName=\"select\"\n\t\t\t\t>\n\t\t\t\t\t{EASING_OPTIONS.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t
\n\n\t\t\t{/* 포인트 좌표 (읽기 전용 표시) */}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t{area.basePoints.map((point, idx) => (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\tP{idx + 1}: ({point.x.toFixed(3)}, {point.y.toFixed(3)})\n\t\t\t\t\t\t
\n\t\t\t\t\t))}\n\t\t\t\t
\n\t\t\t
\n\t\t
\n\t);\n};\n","import { useState, useCallback } from 'react';\r\nimport { DistortionArea, Point } from '../../types/area';\r\nimport { EditorState } from '../types';\r\n\r\nexport const useDistortionEditor = (initialAreas: DistortionArea[] = []) => {\r\n\tconst [state, setState] = useState({\r\n\t\tselectedAreaId: initialAreas[0]?.id || null,\r\n\t\tareas: initialAreas,\r\n\t\teditMode: 'normal',\r\n\t\tdraggingPointIndex: null,\r\n\t});\r\n\r\n\t/** 영역 선택 */\r\n\tconst selectArea = useCallback((areaId: string | null) => {\r\n\t\tsetState((prev) => ({ ...prev, selectedAreaId: areaId }));\r\n\t}, []);\r\n\r\n\t/** 영역 추가 */\r\n\tconst addArea = useCallback((area: DistortionArea) => {\r\n\t\tsetState((prev) => ({\r\n\t\t\t...prev,\r\n\t\t\tareas: [...prev.areas, area],\r\n\t\t\tselectedAreaId: area.id,\r\n\t\t}));\r\n\t}, []);\r\n\r\n\t/** 영역 삭제 */\r\n\tconst removeArea = useCallback((areaId: string) => {\r\n\t\tsetState((prev) => {\r\n\t\t\tconst newAreas = prev.areas.filter((a) => a.id !== areaId);\r\n\t\t\treturn {\r\n\t\t\t\t...prev,\r\n\t\t\t\tareas: newAreas,\r\n\t\t\t\tselectedAreaId:\r\n\t\t\t\t\tprev.selectedAreaId === areaId ? newAreas[0]?.id || null : prev.selectedAreaId,\r\n\t\t\t};\r\n\t\t});\r\n\t}, []);\r\n\r\n\t/** 영역 업데이트 */\r\n\tconst updateArea = useCallback((areaId: string, updates: Partial) => {\r\n\t\tsetState((prev) => ({\r\n\t\t\t...prev,\r\n\t\t\tareas: prev.areas.map((area) => (area.id === areaId ? { ...area, ...updates } : area)),\r\n\t\t}));\r\n\t}, []);\r\n\r\n\t/** 포인트 업데이트 */\r\n\tconst updatePoint = useCallback((areaId: string, pointIndex: number, point: Point) => {\r\n\t\tsetState((prev) => ({\r\n\t\t\t...prev,\r\n\t\t\tareas: prev.areas.map((area) => {\r\n\t\t\t\tif (area.id === areaId) {\r\n\t\t\t\t\tconst newPoints = [...area.basePoints] as [Point, Point, Point, Point];\r\n\t\t\t\t\tnewPoints[pointIndex] = point;\r\n\t\t\t\t\treturn { ...area, basePoints: newPoints };\r\n\t\t\t\t}\r\n\t\t\t\treturn area;\r\n\t\t\t}),\r\n\t\t}));\r\n\t}, []);\r\n\r\n\t/** 드래그 시작 */\r\n\tconst startDragging = useCallback((pointIndex: number) => {\r\n\t\tsetState((prev) => ({ ...prev, draggingPointIndex: pointIndex }));\r\n\t}, []);\r\n\r\n\t/** 드래그 종료 */\r\n\tconst stopDragging = useCallback(() => {\r\n\t\tsetState((prev) => ({ ...prev, draggingPointIndex: null }));\r\n\t}, []);\r\n\r\n\t/** 편집 모드 변경 */\r\n\tconst setEditMode = useCallback((mode: EditorState['editMode']) => {\r\n\t\tsetState((prev) => ({ ...prev, editMode: mode }));\r\n\t}, []);\r\n\r\n\t/** 선택된 영역 가져오기 */\r\n\tconst getSelectedArea = useCallback(() => {\r\n\t\treturn state.areas.find((a) => a.id === state.selectedAreaId) || null;\r\n\t}, [state.areas, state.selectedAreaId]);\r\n\r\n\treturn {\r\n\t\tstate,\r\n\t\tselectArea,\r\n\t\taddArea,\r\n\t\tremoveArea,\r\n\t\tupdateArea,\r\n\t\tupdatePoint,\r\n\t\tstartDragging,\r\n\t\tstopDragging,\r\n\t\tsetEditMode,\r\n\t\tgetSelectedArea,\r\n\t};\r\n};\r\n","import { EditorCanvasStyle } from './types';\n\n/**\n * 기본 에디터 캔버스 스타일\n */\nexport const DEFAULT_EDITOR_CANVAS_STYLE: EditorCanvasStyle = {\n\t// 3단계 원 스타일 (외부 -> 내부)\n\tcircleLevels: [\n\t\t{\n\t\t\tradius: 0.5,\n\t\t\topacity: 0.3,\n\t\t\tlineWidth: 2,\n\t\t\tcolor: 'rgba(255, 200, 0, 1)',\n\t\t\tdashPattern: [8, 4],\n\t\t},\n\t\t{\n\t\t\tradius: 0.33,\n\t\t\topacity: 0.6,\n\t\t\tlineWidth: 2.5,\n\t\t\tcolor: 'rgba(255, 200, 0, 1)',\n\t\t\tdashPattern: [8, 4],\n\t\t},\n\t\t{\n\t\t\tradius: 0.167,\n\t\t\topacity: 0.9,\n\t\t\tlineWidth: 3,\n\t\t\tcolor: 'rgba(255, 200, 0, 1)',\n\t\t\tdashPattern: [8, 4],\n\t\t},\n\t],\n\t// 원 내부 채우기\n\tcircleFillColor: 'rgba(255, 200, 0, 0.08)',\n\t// 중심점\n\tcenterPoint: {\n\t\tradius: 5,\n\t\tfillColor: 'rgba(255, 200, 0, 1)',\n\t\tstrokeColor: 'rgba(255, 255, 255, 0.8)',\n\t\tstrokeWidth: 2,\n\t},\n\t// 포인트 핸들\n\tpointHandle: {\n\t\tsize: 16,\n\t\tfillColor: '#00aaff',\n\t\tstrokeColor: 'white',\n\t\tstrokeWidth: 2,\n\t\tlabelColor: '#00aaff',\n\t\tlabelFontSize: 11,\n\t},\n\t// 영역 외곽선\n\tareaOutline: {\n\t\tselectedColor: '#00aaff',\n\t\tunselectedColor: '#888',\n\t\tselectedWidth: 2,\n\t\tunselectedWidth: 1,\n\t\tunselectedDashPattern: [5, 5],\n\t\tselectedFillColor: 'rgba(0, 170, 255, 0.08)', // 선택된 영역 배경 (연한 파란색)\n\t\tunselectedFillColor: 'rgba(136, 136, 136, 0.03)', // 선택 안된 영역 배경 (연한 회색)\n\t},\n};\n"],"mappings":"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","names":["useEffect","useRef","useState","useCallback","THREE","useRef","useCallback","useRef","useEffect","useRef","useCallback","useRef","useState","useEffect","useCallback","useRef","useEffect","useState","useCallback","jsx","jsx","jsxs","useState","useCallback","jsx","jsxs","useRef","useState","useEffect","isPointInPolygon","useCallback"]} \ 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/utils/motionPresets.ts","../src/engine/AnimationLoop.ts","../src/hooks/useAnimationFrame.ts","../src/hooks/useMouseInteraction.ts","../src/hooks/useMouseVelocity.ts","../src/engine/SpringPhysics.ts","../src/utils/constants.ts","../src/editor/components/EditorCanvas.tsx","../src/editor/components/AreaList.tsx","../src/editor/components/ParameterPanel.tsx","../src/editor/hooks/useDistortionEditor.ts","../src/editor/constants.ts"],"sourcesContent":["import React, { useEffect, useRef, useState, useCallback } from 'react';\r\nimport * as THREE from 'three';\r\nimport { type DistortionArea } from '@/types';\r\nimport { ThreeScene } from '@/engine/ThreeScene';\r\nimport { ShaderManager } from '@/engine/ShaderManager';\r\nimport { AnimationLoop } from '@/engine/AnimationLoop';\r\nimport { useAnimationFrame } from '@/hooks/useAnimationFrame';\r\nimport { useMouseInteraction } from '@/hooks/useMouseInteraction';\r\nimport { SHADER_CONFIG } from '@/utils/constants';\r\nimport { MouseInteractionConfig } from '@/types/interaction';\r\n\r\n/**\r\n * ImageDistortion 컴포넌트 Props\r\n */\r\nexport interface ImageDistortionProps {\r\n /** 이미지 소스 URL */\r\n imageSrc: string;\r\n /** 왜곡 영역 배열 */\r\n areas: DistortionArea[];\r\n /** 버텍스 셰이더 경로 (선택사항) */\r\n vertexShaderPath?: string;\r\n /** 프래그먼트 셰이더 경로 (선택사항) */\r\n fragmentShaderPath?: string;\r\n /** 애니메이션 재생 여부 */\r\n isPlaying?: boolean;\r\n /** 컨테이너 스타일 */\r\n style?: React.CSSProperties;\r\n /** 컨테이너 클래스명 */\r\n className?: string;\r\n /** 마우스 인터랙션 설정 */\r\n mouseInteraction?: MouseInteractionConfig;\r\n}\r\n\r\n/**\r\n * GPU 가속 이미지 왜곡 컴포넌트\r\n * Three.js와 GLSL 셰이더를 사용하여 실시간 이미지 왜곡 효과를 제공합니다.\r\n */\r\nexport const ImageDistortion: React.FC = ({\r\n imageSrc,\r\n areas,\r\n vertexShaderPath,\r\n fragmentShaderPath,\r\n isPlaying = true,\r\n style,\r\n className,\r\n mouseInteraction,\r\n}) => {\r\n const containerRef = useRef(null);\r\n const sceneRef = useRef(null);\r\n const shaderManagerRef = useRef(new ShaderManager());\r\n const textureRef = useRef(null);\r\n\r\n const [isReady, setIsReady] = useState(false);\r\n const [imageLoaded, setImageLoaded] = useState(false);\r\n const [currentAreas, setCurrentAreas] = useState(areas);\r\n\r\n // 마우스 인터랙션 훅\r\n const mouseInteractionHook = useMouseInteraction(\r\n containerRef,\r\n mouseInteraction || {\r\n enabled: false,\r\n physics: {\r\n stiffness: 100,\r\n damping: 10,\r\n mass: 1,\r\n influenceRadius: 0.2,\r\n maxStrength: 1.0,\r\n },\r\n }\r\n );\r\n\r\n // 영역 변경 시 상태 업데이트\r\n useEffect(() => {\r\n setCurrentAreas(areas);\r\n }, [areas]);\r\n\r\n // 마우스 인터랙션 설정 변경 시 업데이트\r\n useEffect(() => {\r\n if (mouseInteraction) {\r\n mouseInteractionHook.updateConfig(mouseInteraction);\r\n }\r\n }, [mouseInteraction, mouseInteractionHook]);\r\n\r\n // Three.js 씬 초기화\r\n useEffect(() => {\r\n console.log('[ImageDistortion] useEffect 실행, containerRef.current:', containerRef.current);\r\n\r\n if (!containerRef.current) {\r\n console.warn('[ImageDistortion] containerRef.current가 null입니다. 컴포넌트가 제대로 마운트되지 않았습니다.');\r\n return;\r\n }\r\n\r\n console.log('[ImageDistortion] 초기화 시작');\r\n const scene = new ThreeScene(containerRef.current);\r\n sceneRef.current = scene;\r\n\r\n // 셰이더 로드\r\n const vertPath = vertexShaderPath || '/shaders/distortion.vert.glsl';\r\n const fragPath = fragmentShaderPath || '/shaders/distortion.frag.glsl';\r\n\r\n console.log('[ImageDistortion] 셰이더 로드 시도:', { vertPath, fragPath });\r\n\r\n shaderManagerRef.current\r\n .loadShaders(vertPath, fragPath)\r\n .then(({ vertex, fragment }) => {\r\n console.log('[ImageDistortion] 셰이더 로드 성공');\r\n scene.setShaderMaterial(vertex, fragment);\r\n setIsReady(true);\r\n })\r\n .catch((error) => {\r\n console.error('[ImageDistortion] 셰이더 로드 실패:', error);\r\n });\r\n\r\n return () => {\r\n scene.dispose();\r\n if (textureRef.current) {\r\n textureRef.current.dispose();\r\n }\r\n };\r\n }, [vertexShaderPath, fragmentShaderPath]);\r\n\r\n // 이미지 텍스처 로드\r\n useEffect(() => {\r\n if (!imageSrc || !isReady) {\r\n console.log('[ImageDistortion] 이미지 로드 스킵:', { imageSrc, isReady });\r\n return;\r\n }\r\n\r\n console.log('[ImageDistortion] 이미지 로드 시작:', imageSrc);\r\n setImageLoaded(false);\r\n\r\n const loader = new THREE.TextureLoader();\r\n loader.load(\r\n imageSrc,\r\n (texture) => {\r\n console.log('[ImageDistortion] 이미지 로드 성공!', {\r\n width: texture.image.width,\r\n height: texture.image.height\r\n });\r\n textureRef.current = texture;\r\n setImageLoaded(true);\r\n if (sceneRef.current) {\r\n sceneRef.current.updateUniforms({\r\n u_texture: { value: texture },\r\n });\r\n sceneRef.current.render();\r\n console.log('[ImageDistortion] 텍스처 업데이트 및 렌더링 완료');\r\n }\r\n },\r\n (progress) => {\r\n console.log('[ImageDistortion] 이미지 로딩 중...',\r\n Math.round((progress.loaded / progress.total) * 100) + '%'\r\n );\r\n },\r\n (error) => {\r\n console.error('[ImageDistortion] 이미지 로드 실패:', error);\r\n setImageLoaded(false);\r\n }\r\n );\r\n\r\n return () => {\r\n if (textureRef.current) {\r\n textureRef.current.dispose();\r\n textureRef.current = null;\r\n }\r\n };\r\n }, [imageSrc, isReady]);\r\n\r\n // 셰이더 유니폼 업데이트\r\n useEffect(() => {\r\n if (!sceneRef.current || !isReady) return;\r\n\r\n // 현재 해상도 가져오기\r\n const resolution = sceneRef.current.getResolution();\r\n\r\n // 포인트 배열 생성\r\n // UI는 좌상단 (0,0), WebGL은 좌하단 (0,0)이므로 y 좌표를 반전\r\n const points = new Float32Array(SHADER_CONFIG.MAX_POINTS * 2);\r\n currentAreas.forEach((area, areaIndex) => {\r\n area.basePoints.forEach((point, pointIndex) => {\r\n const index = (areaIndex * 4 + pointIndex) * 2;\r\n points[index] = point.x;\r\n points[index + 1] = 1.0 - point.y; // y 좌표 반전\r\n });\r\n });\r\n\r\n // 드래그 벡터 배열 생성\r\n // dragVector도 y 좌표계를 맞춰야 하므로 y를 반전\r\n const dragVectors = new Float32Array(SHADER_CONFIG.MAX_DRAG_VECTORS * 2);\r\n currentAreas.forEach((area, index) => {\r\n const baseIndex = index * 2;\r\n dragVectors[baseIndex] = area.dragVector.x;\r\n dragVectors[baseIndex + 1] = -area.dragVector.y; // y 방향 반전\r\n });\r\n\r\n // 강도 배열 생성\r\n const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS);\r\n currentAreas.forEach((area, index) => {\r\n strengths[index] = area.distortionStrength;\r\n });\r\n\r\n sceneRef.current.updateUniforms({\r\n u_numAreas: { value: currentAreas.length },\r\n u_points: { value: points },\r\n u_dragVectors: { value: dragVectors },\r\n u_distortionStrengths: { value: strengths },\r\n });\r\n\r\n sceneRef.current.render();\r\n }, [currentAreas, isReady]);\r\n\r\n // 애니메이션 루프\r\n const animationCallback = useCallback((deltaTime: number) => {\r\n if (!isReady) return;\r\n\r\n setCurrentAreas((prevAreas) => {\r\n // 현재 인터랙션 중인 영역 인덱스 가져오기\r\n const interactingIndices = mouseInteractionHook.getInteractingAreaIndices?.() || new Set();\r\n\r\n // 1. 자동 애니메이션 업데이트\r\n let updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);\r\n updatedAreas = AnimationLoop.updateAreaDragVectors(updatedAreas);\r\n\r\n // 인터랙션 중인 영역만 dragVector를 0으로 설정\r\n if (interactingIndices.size > 0) {\r\n updatedAreas = updatedAreas.map((area, index) => {\r\n if (interactingIndices.has(index)) {\r\n return {\r\n ...area,\r\n dragVector: { x: 0, y: 0 }\r\n };\r\n }\r\n return area;\r\n });\r\n }\r\n\r\n // 2. 마우스 인터랙션 적용 (기존 dragVector에 스프링 변위 추가)\r\n if (mouseInteraction?.enabled) {\r\n updatedAreas = mouseInteractionHook.updateInteraction(updatedAreas, deltaTime);\r\n }\r\n\r\n return updatedAreas;\r\n });\r\n }, [isReady, mouseInteraction, mouseInteractionHook]);\r\n\r\n // 애니메이션은 항상 실행 (마우스 인터랙션 포함)\r\n useAnimationFrame(animationCallback, isPlaying || mouseInteraction?.enabled || false);\r\n\r\n return (\r\n \r\n {!imageLoaded && (\r\n \r\n 이미지 로딩 중...\r\n \r\n )}\r\n \r\n );\r\n};","import * as THREE from 'three';\nimport type { 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 console.log('[ThreeScene] setShaderMaterial 호출됨');\n console.log('[ThreeScene] vertexShader 길이:', vertexShader.length);\n console.log('[ThreeScene] fragmentShader 길이:', fragmentShader.length);\n\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 console.log('[ThreeScene] ShaderMaterial 생성됨');\n\n // 셰이더 컴파일 에러 확인\n const renderer = this.renderer;\n const testScene = new THREE.Scene();\n const testMesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), material);\n testScene.add(testMesh);\n\n try {\n renderer.compile(testScene, this.camera);\n console.log('[ThreeScene] 셰이더 컴파일 성공!');\n } catch (e) {\n console.error('[ThreeScene] 셰이더 컴파일 에러:', e);\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 console.log('[ThreeScene] 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 getResolution(): { x: number; y: number } {\n return {\n x: this.uniforms.u_resolution.value.x,\n y: this.uniforms.u_resolution.value.y,\n };\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 console.log('[ShaderManager] loadShaders 시작:', { vertexPath, fragmentPath });\n\n try {\n console.log('[ShaderManager] fetch 시작...');\n const [vertexResponse, fragmentResponse] = await Promise.all([\n fetch(vertexPath),\n fetch(fragmentPath),\n ]);\n\n console.log('[ShaderManager] fetch 완료:', {\n vertexStatus: vertexResponse.status,\n fragmentStatus: fragmentResponse.status\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 console.log('[ShaderManager] text() 변환 시작...');\n this.vertexShaderSource = await vertexResponse.text();\n this.fragmentShaderSource = await fragmentResponse.text();\n\n console.log('[ShaderManager] 셰이더 로드 완료!', {\n vertexLength: this.vertexShaderSource.length,\n fragmentLength: this.fragmentShaderSource.length\n });\n\n return {\n vertex: this.vertexShaderSource,\n fragment: this.fragmentShaderSource,\n };\n } catch (error) {\n console.error('[ShaderManager] 셰이더 로드 실패:', 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 { type EasingFunction } from '../types';\r\n\r\ntype EasingFunc = (t: number) => number;\r\n\r\n/**\r\n * 이징 함수 구현 맵\r\n */\r\nconst easingFunctions: Record = {\r\n linear: (t) => t,\r\n\r\n easeIn: (t) => t * t,\r\n easeOut: (t) => t * (2 - t),\r\n easeInOut: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),\r\n\r\n easeInQuad: (t) => t * t,\r\n easeOutQuad: (t) => t * (2 - t),\r\n};\r\n\r\n/**\r\n * 진행도에 이징 함수를 적용\r\n * @param progress 진행도 (0.0 - 1.0)\r\n * @param easingType 적용할 이징 함수 타입\r\n * @returns 이징이 적용된 진행도 (0.0 - 1.0)\r\n */\r\nexport const applyEasing = (\r\n progress: number,\r\n easingType: EasingFunction\r\n): number => {\r\n const clampedProgress = Math.max(0, Math.min(1, progress));\r\n return easingFunctions[easingType](clampedProgress);\r\n};","import type {MotionPreset, MotionPresetDefinition, Point, RotationPresetChecker} from '../types';\r\n\r\n/**\r\n * 프리셋 레지스트리 (내장 + 커스텀)\r\n */\r\nconst presetRegistry = new Map();\r\n\r\n/**\r\n * 회전 프리셋 목록\r\n */\r\nconst rotationPresets = new Set(['rotate-cw', 'rotate-ccw']);\r\n\r\n/**\r\n * 내장 프리셋 정의\r\n */\r\nconst BUILT_IN_PRESETS: Record = {\r\n\t'none': () => ({x: 0, y: 0}),\r\n\t'horizontal': (strength) => ({x: strength, y: 0}),\r\n\t'vertical': (strength) => ({x: 0, y: strength}),\r\n\t'rotate-cw': (strength) => ({x: strength, y: 0}),\r\n\t'rotate-ccw': (strength) => ({x: -strength, y: 0}),\r\n\t'pulse': (strength) => ({x: strength, y: strength}),\r\n\t'diagonal-1': (strength) => ({x: strength * 0.707, y: strength * 0.707}),\r\n\t'diagonal-2': (strength) => ({x: strength * 0.707, y: -strength * 0.707}),\r\n};\r\n\r\n// 내장 프리셋 등록\r\nObject.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => {\r\n\tpresetRegistry.set(name, definition);\r\n});\r\n\r\n/**\r\n * 커스텀 모션 프리셋 등록\r\n * @param name 프리셋 이름\r\n * @param definition 프리셋 정의 함수 (strength를 받아 Point 반환)\r\n * @param options 추가 옵션\r\n * @param options.isRotation 회전 애니메이션 여부 (true면 원운동)\r\n *\r\n * @example\r\n * // 좌우 진짜 왕복 (좌↔우)\r\n * registerMotionPreset('horizontal-full', (strength) => ({\r\n * x: strength * 2, // 진폭 2배\r\n * y: 0\r\n * }));\r\n *\r\n * // 8자 모양 운동 (회전)\r\n * registerMotionPreset('figure-8', (strength) => ({\r\n * x: strength,\r\n * y: strength * 0.5\r\n * }), { isRotation: true });\r\n */\r\nexport function registerMotionPreset(\r\n\tname: string,\r\n\tdefinition: MotionPresetDefinition,\r\n\toptions?: { isRotation?: boolean }\r\n): void {\r\n\tpresetRegistry.set(name, definition);\r\n\r\n\tif (options?.isRotation) {\r\n\t\trotationPresets.add(name);\r\n\t} else {\r\n\t\trotationPresets.delete(name);\r\n\t}\r\n}\r\n\r\n/**\r\n * 여러 프리셋을 한번에 등록\r\n * @param presets 프리셋 맵 (이름 → 정의)\r\n * @param rotationPresetNames 회전 프리셋 이름 목록\r\n *\r\n * @example\r\n * registerMotionPresets({\r\n * 'horizontal-full': (s) => ({x: s * 2, y: 0}),\r\n * 'wave': (s) => ({x: s, y: s * 0.3}),\r\n * }, ['wave']); // wave는 회전 애니메이션\r\n */\r\nexport function registerMotionPresets(\r\n\tpresets: Record,\r\n\trotationPresetNames?: string[]\r\n): void {\r\n\tObject.entries(presets).forEach(([name, definition]) => {\r\n\t\tpresetRegistry.set(name, definition);\r\n\t});\r\n\r\n\trotationPresetNames?.forEach(name => rotationPresets.add(name));\r\n}\r\n\r\n/**\r\n * 프리셋 등록 해제\r\n * @param name 프리셋 이름\r\n * @returns 해제 성공 여부\r\n */\r\nexport function unregisterMotionPreset(name: string): boolean {\r\n\trotationPresets.delete(name);\r\n\treturn presetRegistry.delete(name);\r\n}\r\n\r\n/**\r\n * 등록된 모든 프리셋 이름 조회\r\n * @returns 프리셋 이름 배열\r\n */\r\nexport function getRegisteredPresets(): string[] {\r\n\treturn Array.from(presetRegistry.keys());\r\n}\r\n\r\n/**\r\n * 프리셋 존재 여부 확인\r\n * @param name 프리셋 이름\r\n * @returns 존재 여부\r\n */\r\nexport function hasPreset(name: string): boolean {\r\n\treturn presetRegistry.has(name);\r\n}\r\n\r\n/**\r\n * 내장 프리셋으로 초기화 (커스텀 프리셋 모두 제거)\r\n */\r\nexport function resetToBuiltInPresets(): void {\r\n\tpresetRegistry.clear();\r\n\trotationPresets.clear();\r\n\r\n\tObject.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => {\r\n\t\tpresetRegistry.set(name, definition);\r\n\t});\r\n\trotationPresets.add('rotate-cw');\r\n\trotationPresets.add('rotate-ccw');\r\n}\r\n\r\n/**\r\n * 모션 프리셋을 벡터로 변환\r\n * @param preset 모션 프리셋\r\n * @param strength 모션 강도 (기본값: 0.1)\r\n * @returns 계산된 벡터 (vectorA)\r\n */\r\nexport function presetToVector(preset: MotionPreset, strength: number = 0.1): Point {\r\n\tconst definition = presetRegistry.get(preset);\r\n\r\n\tif (definition) {\r\n\t\treturn definition(strength);\r\n\t}\r\n\r\n\t// 등록되지 않은 프리셋은 none으로 처리\r\n\tconsole.warn(`Unknown motion preset: \"${preset}\". Falling back to \"none\".`);\r\n\treturn {x: 0, y: 0};\r\n}\r\n\r\n/**\r\n * 프리셋이 회전 타입인지 확인\r\n */\r\nexport function isRotationPreset(preset?: MotionPreset): boolean {\r\n\tif (!preset) return false;\r\n\treturn rotationPresets.has(preset);\r\n}\r\n\r\n/**\r\n * 커스텀 회전 프리셋 판별 함수 등록\r\n * @param checker 판별 함수\r\n * @deprecated isRotation 옵션을 registerMotionPreset에 전달하세요\r\n */\r\nexport function setRotationChecker(checker: RotationPresetChecker): void {\r\n\t// Legacy support - 기존 코드 호환성을 위해 유지\r\n\tconsole.warn('setRotationChecker is deprecated. Use registerMotionPreset with { isRotation: true } option instead.');\r\n}\r\n","import { applyEasing } from '../utils/easing';\r\nimport { presetToVector, isRotationPreset } from '../utils/motionPresets';\r\nimport type {DistortionArea, Point} from \"../types\";\r\n\r\n/**\r\n * 애니메이션 루프 관리 클래스\r\n */\r\nexport class AnimationLoop {\r\n /**\r\n * 영역들의 드래그 벡터를 현재 진행도에 따라 업데이트\r\n * @param areas 왜곡 영역 배열\r\n * @returns 업데이트된 영역 배열\r\n */\r\n public static updateAreaDragVectors(\r\n areas: DistortionArea[]\r\n ): DistortionArea[] {\r\n return areas.map((area) => {\r\n const { progress, movement } = area;\r\n\r\n // duration이 0이거나 프리셋이 'none'이면 애니메이션 없음\r\n if (movement.duration <= 0 || movement.preset === 'none') {\r\n return {\r\n ...area,\r\n dragVector: { x: 0, y: 0 },\r\n };\r\n }\r\n\r\n // 프리셋이 설정되어 있으면 프리셋 기반 벡터 사용\r\n let baseVector: Point;\r\n if (movement.preset) {\r\n const strength = movement.strength ?? 0.1;\r\n baseVector = presetToVector(movement.preset, strength);\r\n } else {\r\n // 프리셋 없으면 기존 vectorA 사용 (하위 호환성)\r\n baseVector = movement.vectorA;\r\n }\r\n\r\n // 이징 적용\r\n const easedProgress = applyEasing(progress, movement.easing);\r\n\r\n // 벡터 계산\r\n let dragVector: Point;\r\n\r\n // 회전 프리셋인 경우 원운동\r\n if (movement.preset && isRotationPreset(movement.preset)) {\r\n const angle = easedProgress * Math.PI * 2;\r\n const radius = Math.sqrt(baseVector.x * baseVector.x + baseVector.y * baseVector.y);\r\n const direction = movement.preset === 'rotate-cw' ? 1 : -1;\r\n dragVector = {\r\n x: Math.cos(angle * direction) * radius,\r\n y: Math.sin(angle * direction) * radius,\r\n };\r\n } else {\r\n // 일반 왕복 모션\r\n if (easedProgress < 0.5) {\r\n // 0.0 -> 0.5: 0에서 baseVector로 보간\r\n const t = easedProgress * 2;\r\n dragVector = {\r\n x: baseVector.x * t,\r\n y: baseVector.y * t,\r\n };\r\n } else {\r\n // 0.5 -> 1.0: baseVector에서 0으로 보간\r\n const t = (easedProgress - 0.5) * 2;\r\n dragVector = {\r\n x: baseVector.x * (1 - t),\r\n y: baseVector.y * (1 - t),\r\n };\r\n }\r\n }\r\n\r\n return {\r\n ...area,\r\n dragVector,\r\n };\r\n });\r\n }\r\n\r\n /**\r\n * 모든 영역의 진행도를 델타 타임만큼 업데이트\r\n * @param areas 왜곡 영역 배열\r\n * @param deltaTime 델타 타임 (초)\r\n * @returns 업데이트된 영역 배열\r\n */\r\n public static updateProgress(\r\n areas: DistortionArea[],\r\n deltaTime: number\r\n ): DistortionArea[] {\r\n return areas.map((area) => {\r\n // duration이 0이면 progress 업데이트 안 함\r\n if (area.movement.duration <= 0) {\r\n return area;\r\n }\r\n\r\n let newProgress = area.progress + deltaTime / area.movement.duration;\r\n newProgress %= 1.0; // 루프\r\n\r\n return {\r\n ...area,\r\n progress: newProgress,\r\n };\r\n });\r\n }\r\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};","import { useRef, useCallback, useState } from 'react';\r\nimport { useMouseVelocity } from './useMouseVelocity';\r\nimport { SpringPhysics } from '@/engine/SpringPhysics';\r\nimport { DistortionArea, Point } from '@/types';\r\nimport { MouseInteractionConfig } from '@/types/interaction';\r\n\r\n/**\r\n * 점이 사각형 내부에 있는지 확인\r\n */\r\nconst isPointInPolygon = (point: Point, polygon: Point[]): boolean => {\r\n\tlet inside = false;\r\n\tfor (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\r\n\t\tconst xi = polygon[i].x, yi = polygon[i].y;\r\n\t\tconst xj = polygon[j].x, yj = polygon[j].y;\r\n\t\tconst intersect = ((yi > point.y) !== (yj > point.y))\r\n\t\t\t&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);\r\n\t\tif (intersect) inside = !inside;\r\n\t}\r\n\treturn inside;\r\n};\r\n\r\n/**\r\n * 마우스 인터랙션 기반 기존 영역 제어 훅\r\n * 마우스가 지나가는 모든 영역에 효과 적용\r\n */\r\nexport const useMouseInteraction = (\r\n\tcontainerRef: React.RefObject,\r\n\tconfig: MouseInteractionConfig\r\n) => {\r\n\tconst { getState } = useMouseVelocity(containerRef);\r\n\tconst [interactingAreaIndices, setInteractingAreaIndices] = useState>(new Set());\r\n\tconst springPhysicsMapRef = useRef>(new Map());\r\n\r\n\t/**\r\n\t * 특정 영역의 스프링 물리 엔진 가져오기 (없으면 생성)\r\n\t */\r\n\tconst getSpringPhysics = useCallback((areaIndex: number, area?: DistortionArea): SpringPhysics => {\r\n\t\tif (!springPhysicsMapRef.current.has(areaIndex)) {\r\n\t\t\t// 영역별 물리 설정이 있으면 사용, 없으면 전역 설정 사용\r\n\t\t\tconst physicsConfig = area?.physics || config.physics;\r\n\t\t\tspringPhysicsMapRef.current.set(areaIndex, new SpringPhysics(physicsConfig));\r\n\t\t}\r\n\t\treturn springPhysicsMapRef.current.get(areaIndex)!;\r\n\t}, [config.physics]);\r\n\r\n\t/**\r\n\t * 기존 영역들의 dragVector를 마우스 인터랙션으로 업데이트\r\n\t */\r\n\tconst updateInteraction = useCallback((areas: DistortionArea[], deltaTime: number): DistortionArea[] => {\r\n\t\tif (!config.enabled) return areas;\r\n\r\n\t\t// 영역별 물리 설정이 변경되었을 수 있으므로 모든 스프링 업데이트\r\n\t\tareas.forEach((area, index) => {\r\n\t\t\tif (area.physics && springPhysicsMapRef.current.has(index)) {\r\n\t\t\t\tconst spring = springPhysicsMapRef.current.get(index)!;\r\n\t\t\t\tspring.setConfig(area.physics);\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tconst mouseState = getState();\r\n\r\n\t\t// 마우스 클릭/드래그 중이고 위치가 있으면\r\n\t\tif (mouseState.isDragging && mouseState.position) {\r\n\t\t\t// 현재 마우스 위치가 포함된 모든 영역 찾기\r\n\t\t\tconst currentlyInAreas = new Set();\r\n\t\t\tfor (let i = 0; i < areas.length; i++) {\r\n\t\t\t\tif (isPointInPolygon(mouseState.position, areas[i].basePoints)) {\r\n\t\t\t\t\tcurrentlyInAreas.add(i);\r\n\r\n\t\t\t\t\t// 새로 진입한 영역이면 스프링 리셋\r\n\t\t\t\t\tif (!interactingAreaIndices.has(i)) {\r\n\t\t\t\t\t\tgetSpringPhysics(i, areas[i]).reset();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// 이전에 인터랙션하던 영역에서 벗어났으면 평형으로 복귀\r\n\t\t\tinteractingAreaIndices.forEach((areaIndex) => {\r\n\t\t\t\tif (!currentlyInAreas.has(areaIndex)) {\r\n\t\t\t\t\tgetSpringPhysics(areaIndex, areas[areaIndex]).returnToEquilibrium();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\t// 인터랙션 영역 업데이트\r\n\t\t\tsetInteractingAreaIndices(currentlyInAreas);\r\n\r\n\t\t\t// 현재 위치의 모든 영역에 속도 적용\r\n\t\t\tconst velocityMult = config.velocityMultiplier || 1.0;\r\n\t\t\tconst velocityMag = Math.sqrt(\r\n\t\t\t\tmouseState.velocity.x ** 2 + mouseState.velocity.y ** 2\r\n\t\t\t);\r\n\t\t\tconst minVel = config.minVelocity || 0.05;\r\n\t\t\tconst maxVel = config.maxVelocity || 5.0;\r\n\r\n\t\t\t// 속도 클램핑\r\n\t\t\tlet clampedVelocity = mouseState.velocity;\r\n\t\t\tif (velocityMag > maxVel) {\r\n\t\t\t\tconst scale = maxVel / velocityMag;\r\n\t\t\t\tclampedVelocity = {\r\n\t\t\t\t\tx: mouseState.velocity.x * scale,\r\n\t\t\t\t\ty: mouseState.velocity.y * scale,\r\n\t\t\t\t};\r\n\t\t\t}\r\n\r\n\t\t\tcurrentlyInAreas.forEach((areaIndex) => {\r\n\t\t\t\tconst spring = getSpringPhysics(areaIndex, areas[areaIndex]);\r\n\r\n\t\t\t\tif (velocityMag >= minVel) {\r\n\t\t\t\t\t// 드래그 중: 마우스 속도를 목표로 설정\r\n\t\t\t\t\tspring.setTarget(clampedVelocity, velocityMult);\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// 드래그 중이지만 마우스가 멈춰있으면 평형으로 복귀\r\n\t\t\t\t\tspring.returnToEquilibrium();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\t// 마우스를 놓았으면 인터랙션 중이던 모든 영역에 튕김 효과\r\n\t\t\tif (interactingAreaIndices.size > 0) {\r\n\t\t\t\tconst velocityMult = config.velocityMultiplier || 1.0;\r\n\t\t\t\tconst maxVel = config.maxVelocity || 5.0;\r\n\r\n\t\t\t\t// 속도 클램핑\r\n\t\t\t\tconst velocityMag = Math.sqrt(\r\n\t\t\t\t\tmouseState.velocity.x ** 2 + mouseState.velocity.y ** 2\r\n\t\t\t\t);\r\n\t\t\t\tlet clampedVelocity = mouseState.velocity;\r\n\t\t\t\tif (velocityMag > maxVel) {\r\n\t\t\t\t\tconst scale = maxVel / velocityMag;\r\n\t\t\t\t\tclampedVelocity = {\r\n\t\t\t\t\t\tx: mouseState.velocity.x * scale,\r\n\t\t\t\t\t\ty: mouseState.velocity.y * scale,\r\n\t\t\t\t\t};\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// 모든 인터랙션 영역에 초기 속도 설정\r\n\t\t\t\tinteractingAreaIndices.forEach((areaIndex) => {\r\n\t\t\t\t\tconst spring = getSpringPhysics(areaIndex, areas[areaIndex]);\r\n\t\t\t\t\tspring.setInitialVelocity(clampedVelocity, velocityMult);\r\n\t\t\t\t});\r\n\r\n\t\t\t\tsetInteractingAreaIndices(new Set());\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// 모든 영역의 스프링 물리 업데이트\r\n\t\treturn areas.map((area, index) => {\r\n\t\t\tconst spring = springPhysicsMapRef.current.get(index);\r\n\t\t\tif (!spring) return area;\r\n\r\n\t\t\t// 현재 드래그 중인 영역이거나 스프링이 활성 상태일 때만 업데이트\r\n\t\t\tconst springVelocity = spring.getVelocity();\r\n\t\t\tconst springDisplacement = spring.getDisplacement();\r\n\t\t\tconst isSpringActive = Math.sqrt(springVelocity.x ** 2 + springVelocity.y ** 2) > 0.001 ||\r\n\t\t\t\tMath.sqrt(springDisplacement.x ** 2 + springDisplacement.y ** 2) > 0.001;\r\n\r\n\t\t\t// 드래그 중이 아니고 스프링도 비활성이면 업데이트 안 함\r\n\t\t\tif (!interactingAreaIndices.has(index) && !isSpringActive) {\r\n\t\t\t\treturn area;\r\n\t\t\t}\r\n\r\n\t\t\t// 스프링 물리 업데이트\r\n\t\t\tconst displacement = spring.update(deltaTime);\r\n\r\n\t\t\t// 변위가 거의 0이면 원래 dragVector 유지\r\n\t\t\tconst displacementMag = Math.sqrt(displacement.x ** 2 + displacement.y ** 2);\r\n\t\t\tif (displacementMag < 0.001) {\r\n\t\t\t\treturn area;\r\n\t\t\t}\r\n\r\n\t\t\t// 스프링 변위를 dragVector에 추가 (기존 애니메이션과 혼합)\r\n\t\t\t// dragVector는 텍스처 샘플링 좌표 이동이므로,\r\n\t\t\t// 마우스 드래그 방향과 반대로 적용해야 이미지가 드래그 방향으로 밀림\r\n\t\t\t// 예: 우→좌 드래그(velocity < 0) → dragVector > 0 → 이미지 왼쪽으로 밀림\r\n\t\t\treturn {\r\n\t\t\t\t...area,\r\n\t\t\t\tdragVector: {\r\n\t\t\t\t\tx: area.dragVector.x - displacement.x,\r\n\t\t\t\t\ty: area.dragVector.y - displacement.y,\r\n\t\t\t\t},\r\n\t\t\t};\r\n\t\t});\r\n\t}, [config, getState, interactingAreaIndices, getSpringPhysics]);\r\n\r\n\t/**\r\n\t * 물리 파라미터 업데이트\r\n\t */\r\n\tconst updateConfig = useCallback((newConfig: Partial) => {\r\n\t\tconst physicsConfig = newConfig.physics;\r\n\t\tif (physicsConfig) {\r\n\t\t\t// 모든 스프링 물리 엔진의 설정 업데이트\r\n\t\t\tspringPhysicsMapRef.current.forEach((spring) => {\r\n\t\t\t\tspring.setConfig(physicsConfig);\r\n\t\t\t});\r\n\t\t}\r\n\t}, []);\r\n\r\n\t/**\r\n\t * 모든 영역의 스프링 상태 리셋\r\n\t */\r\n\tconst reset = useCallback(() => {\r\n\t\tspringPhysicsMapRef.current.forEach((spring) => {\r\n\t\t\tspring.reset();\r\n\t\t});\r\n\t\tsetInteractingAreaIndices(new Set());\r\n\t}, []);\r\n\r\n\t/**\r\n\t * 현재 드래그 중인지 확인\r\n\t */\r\n\tconst isDragging = useCallback((): boolean => {\r\n\t\tconst mouseState = getState();\r\n\t\treturn mouseState.isDragging;\r\n\t}, [getState]);\r\n\r\n\t/**\r\n\t * 현재 인터랙션 중인 영역 인덱스 가져오기\r\n\t */\r\n\tconst getInteractingAreaIndices = useCallback((): Set => {\r\n\t\treturn interactingAreaIndices;\r\n\t}, [interactingAreaIndices]);\r\n\r\n\treturn {\r\n\t\tupdateInteraction,\r\n\t\tupdateConfig,\r\n\t\treset,\r\n\t\tisDragging,\r\n\t\tgetInteractingAreaIndices,\r\n\t};\r\n};\r\n","import { useRef, useCallback, useEffect } from 'react';\nimport { Point } from '@/types';\nimport { MouseState } from '@/types/interaction';\n\n/**\n * 마우스 위치, 속도, 가속도를 추적하는 훅\n */\nexport const useMouseVelocity = (containerRef: React.RefObject) => {\n\tconst mouseStateRef = useRef({\n\t\tposition: null,\n\t\tprevPosition: null,\n\t\tvelocity: { x: 0, y: 0 },\n\t\tacceleration: { x: 0, y: 0 },\n\t\tisHovering: false,\n\t\tisDragging: false,\n\t});\n\n\tconst lastUpdateTimeRef = useRef(Date.now());\n\tconst prevVelocityRef = useRef({ x: 0, y: 0 });\n\n\t/**\n\t * 픽셀 좌표를 정규화 좌표(0-1)로 변환\n\t */\n\tconst toNormalized = useCallback((clientX: number, clientY: number): Point | null => {\n\t\tif (!containerRef.current) return null;\n\t\tconst rect = containerRef.current.getBoundingClientRect();\n\t\treturn {\n\t\t\tx: (clientX - rect.left) / rect.width,\n\t\t\ty: (clientY - rect.top) / rect.height,\n\t\t};\n\t}, [containerRef]);\n\n\t/**\n\t * 위치 업데이트 (마우스/터치 공통 로직)\n\t */\n\tconst updatePosition = useCallback((clientX: number, clientY: number) => {\n\t\tconst now = Date.now();\n\t\tconst deltaTime = (now - lastUpdateTimeRef.current) / 1000; // 초 단위\n\t\tlastUpdateTimeRef.current = now;\n\n\t\tconst normalizedPos = toNormalized(clientX, clientY);\n\t\tif (!normalizedPos) return;\n\n\t\tconst state = mouseStateRef.current;\n\t\tconst prevPos = state.position;\n\n\t\t// 속도 계산 (변위 / 시간)\n\t\tlet velocity: Point = { x: 0, y: 0 };\n\t\tif (prevPos && deltaTime > 0) {\n\t\t\tvelocity = {\n\t\t\t\tx: (normalizedPos.x - prevPos.x) / deltaTime,\n\t\t\t\ty: (normalizedPos.y - prevPos.y) / deltaTime,\n\t\t\t};\n\t\t}\n\n\t\t// 가속도 계산 (속도 변화 / 시간)\n\t\tconst prevVel = prevVelocityRef.current;\n\t\tlet acceleration: Point = { x: 0, y: 0 };\n\t\tif (deltaTime > 0) {\n\t\t\tacceleration = {\n\t\t\t\tx: (velocity.x - prevVel.x) / deltaTime,\n\t\t\t\ty: (velocity.y - prevVel.y) / deltaTime,\n\t\t\t};\n\t\t}\n\n\t\t// 상태 업데이트\n\t\tmouseStateRef.current = {\n\t\t\tposition: normalizedPos,\n\t\t\tprevPosition: prevPos,\n\t\t\tvelocity,\n\t\t\tacceleration,\n\t\t\tisHovering: true,\n\t\t\tisDragging: state.isDragging,\n\t\t};\n\t\tprevVelocityRef.current = velocity;\n\t}, [toNormalized]);\n\n\t/**\n\t * 마우스 이동 핸들러\n\t */\n\tconst handleMouseMove = useCallback((e: MouseEvent) => {\n\t\tupdatePosition(e.clientX, e.clientY);\n\t}, [updatePosition]);\n\n\t/**\n\t * 마우스 진입\n\t */\n\tconst handleMouseEnter = useCallback(() => {\n\t\tmouseStateRef.current.isHovering = true;\n\t}, []);\n\n\t/**\n\t * 마우스 나감\n\t */\n\tconst handleMouseLeave = useCallback(() => {\n\t\tmouseStateRef.current = {\n\t\t\tposition: null,\n\t\t\tprevPosition: null,\n\t\t\tvelocity: { x: 0, y: 0 },\n\t\t\tacceleration: { x: 0, y: 0 },\n\t\t\tisHovering: false,\n\t\t\tisDragging: false,\n\t\t};\n\t\tprevVelocityRef.current = { x: 0, y: 0 };\n\t}, []);\n\n\t/**\n\t * 마우스 다운\n\t */\n\tconst handleMouseDown = useCallback(() => {\n\t\tmouseStateRef.current.isDragging = true;\n\t}, []);\n\n\t/**\n\t * 마우스 업\n\t */\n\tconst handleMouseUp = useCallback(() => {\n\t\tmouseStateRef.current.isDragging = false;\n\t}, []);\n\n\t/**\n\t * 터치 이동 핸들러\n\t */\n\tconst handleTouchMove = useCallback((e: TouchEvent) => {\n\t\te.preventDefault(); // 스크롤 방지\n\t\tif (e.touches.length > 0) {\n\t\t\tconst touch = e.touches[0];\n\t\t\tupdatePosition(touch.clientX, touch.clientY);\n\t\t}\n\t}, [updatePosition]);\n\n\t/**\n\t * 터치 시작 핸들러\n\t */\n\tconst handleTouchStart = useCallback((e: TouchEvent) => {\n\t\te.preventDefault(); // 스크롤 방지\n\t\tmouseStateRef.current.isDragging = true;\n\t\tmouseStateRef.current.isHovering = true;\n\t\tif (e.touches.length > 0) {\n\t\t\tconst touch = e.touches[0];\n\t\t\tupdatePosition(touch.clientX, touch.clientY);\n\t\t}\n\t}, [updatePosition]);\n\n\t/**\n\t * 터치 종료 핸들러\n\t */\n\tconst handleTouchEnd = useCallback(() => {\n\t\tmouseStateRef.current.isDragging = false;\n\t\tmouseStateRef.current.isHovering = false;\n\t\tmouseStateRef.current.position = null;\n\t\tmouseStateRef.current.prevPosition = null;\n\t\tmouseStateRef.current.velocity = { x: 0, y: 0 };\n\t\tmouseStateRef.current.acceleration = { x: 0, y: 0 };\n\t\tprevVelocityRef.current = { x: 0, y: 0 };\n\t}, []);\n\n\t/**\n\t * 이벤트 리스너 등록\n\t */\n\tuseEffect(() => {\n\t\tconst container = containerRef.current;\n\t\tif (!container) return;\n\n\t\t// 마우스 이벤트\n\t\tcontainer.addEventListener('mousemove', handleMouseMove);\n\t\tcontainer.addEventListener('mouseenter', handleMouseEnter);\n\t\tcontainer.addEventListener('mouseleave', handleMouseLeave);\n\t\tcontainer.addEventListener('mousedown', handleMouseDown);\n\t\twindow.addEventListener('mouseup', handleMouseUp);\n\n\t\t// 터치 이벤트 (passive: false로 스크롤 방지 가능)\n\t\tcontainer.addEventListener('touchmove', handleTouchMove, { passive: false });\n\t\tcontainer.addEventListener('touchstart', handleTouchStart, { passive: false });\n\t\tcontainer.addEventListener('touchend', handleTouchEnd);\n\t\tcontainer.addEventListener('touchcancel', handleTouchEnd);\n\n\t\treturn () => {\n\t\t\t// 마우스 이벤트 제거\n\t\t\tcontainer.removeEventListener('mousemove', handleMouseMove);\n\t\t\tcontainer.removeEventListener('mouseenter', handleMouseEnter);\n\t\t\tcontainer.removeEventListener('mouseleave', handleMouseLeave);\n\t\t\tcontainer.removeEventListener('mousedown', handleMouseDown);\n\t\t\twindow.removeEventListener('mouseup', handleMouseUp);\n\n\t\t\t// 터치 이벤트 제거\n\t\t\tcontainer.removeEventListener('touchmove', handleTouchMove);\n\t\t\tcontainer.removeEventListener('touchstart', handleTouchStart);\n\t\t\tcontainer.removeEventListener('touchend', handleTouchEnd);\n\t\t\tcontainer.removeEventListener('touchcancel', handleTouchEnd);\n\t\t};\n\t}, [containerRef, handleMouseMove, handleMouseEnter, handleMouseLeave, handleMouseDown, handleMouseUp, handleTouchMove, handleTouchStart, handleTouchEnd]);\n\n\t/**\n\t * 현재 마우스 상태 가져오기\n\t */\n\tconst getState = useCallback((): MouseState => {\n\t\treturn { ...mouseStateRef.current };\n\t}, []);\n\n\treturn {\n\t\tgetState,\n\t};\n};\n","import { Point } from '@/types';\r\nimport { SpringPhysicsConfig, SpringState } from '@/types/interaction';\r\n\r\n/**\r\n * 스프링 기반 물리 시뮬레이션 엔진\r\n * Hooke's Law와 감쇠를 적용한 스프링-댐퍼 시스템\r\n */\r\nexport class SpringPhysics {\r\n\tprivate config: SpringPhysicsConfig;\r\n\tprivate state: SpringState;\r\n\r\n\tconstructor(config: SpringPhysicsConfig) {\r\n\t\tthis.config = config;\r\n\t\tthis.state = {\r\n\t\t\tdisplacement: { x: 0, y: 0 },\r\n\t\t\tvelocity: { x: 0, y: 0 },\r\n\t\t\ttarget: { x: 0, y: 0 },\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * 물리 파라미터 업데이트\r\n\t */\r\n\tpublic setConfig(config: Partial) {\r\n\t\tthis.config = { ...this.config, ...config };\r\n\t}\r\n\r\n\t/**\r\n\t * 목표 위치 설정 (마우스 속도 기반)\r\n\t */\r\n\tpublic setTarget(velocity: Point, velocityMultiplier: number = 1.0) {\r\n\t\t// 속도에 승수를 곱해서 목표 변위로 설정\r\n\t\tthis.state.target = {\r\n\t\t\tx: velocity.x * velocityMultiplier,\r\n\t\t\ty: velocity.y * velocityMultiplier,\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * 초기 속도 설정 (드래그 방향과 속도를 즉시 반영)\r\n\t * 드래그 방향으로 즉시 튕기는 효과\r\n\t */\r\n\tpublic setInitialVelocity(velocity: Point, multiplier: number = 1.0) {\r\n\t\t// 현재 속도를 즉시 변경\r\n\t\tthis.state.velocity = {\r\n\t\t\tx: velocity.x * multiplier,\r\n\t\t\ty: velocity.y * multiplier,\r\n\t\t};\r\n\t\t// 목표는 0 (평형 상태로 돌아가도록)\r\n\t\tthis.state.target = { x: 0, y: 0 };\r\n\t}\r\n\r\n\t/**\r\n\t * 스프링 물리 업데이트 (Hooke's Law + Damping)\r\n\t * F = -k * x - c * v\r\n\t * a = F / m\r\n\t * v += a * dt\r\n\t * x += v * dt\r\n\t */\r\n\tpublic update(deltaTime: number): Point {\r\n\t\tconst { stiffness, damping, mass } = this.config;\r\n\t\tconst { displacement, velocity, target } = this.state;\r\n\r\n\t\t// 평형 위치로부터의 변위 (target은 마우스 속도에서 계산된 목표)\r\n\t\tconst dx = displacement.x - target.x;\r\n\t\tconst dy = displacement.y - target.y;\r\n\r\n\t\t// 스프링 힘: F = -k * x (복원력)\r\n\t\tconst springForceX = -stiffness * dx;\r\n\t\tconst springForceY = -stiffness * dy;\r\n\r\n\t\t// 감쇠 힘: F = -c * v (마찰력)\r\n\t\tconst dampingForceX = -damping * velocity.x;\r\n\t\tconst dampingForceY = -damping * velocity.y;\r\n\r\n\t\t// 총 힘\r\n\t\tconst totalForceX = springForceX + dampingForceX;\r\n\t\tconst totalForceY = springForceY + dampingForceY;\r\n\r\n\t\t// 가속도: a = F / m\r\n\t\tconst accelerationX = totalForceX / mass;\r\n\t\tconst accelerationY = totalForceY / mass;\r\n\r\n\t\t// 속도 업데이트: v += a * dt\r\n\t\tconst newVelocityX = velocity.x + accelerationX * deltaTime;\r\n\t\tconst newVelocityY = velocity.y + accelerationY * deltaTime;\r\n\r\n\t\t// 위치 업데이트: x += v * dt\r\n\t\tconst newDisplacementX = displacement.x + newVelocityX * deltaTime;\r\n\t\tconst newDisplacementY = displacement.y + newVelocityY * deltaTime;\r\n\r\n\t\t// 상태 저장\r\n\t\tthis.state = {\r\n\t\t\tdisplacement: { x: newDisplacementX, y: newDisplacementY },\r\n\t\t\tvelocity: { x: newVelocityX, y: newVelocityY },\r\n\t\t\ttarget,\r\n\t\t};\r\n\r\n\t\t// 매우 작은 움직임은 0으로 처리 (정지 판정)\r\n\t\tconst isNearlyZero = (val: number) => Math.abs(val) < 0.0001;\r\n\t\tif (isNearlyZero(this.state.displacement.x) && isNearlyZero(this.state.displacement.y) &&\r\n\t\t\tisNearlyZero(this.state.velocity.x) && isNearlyZero(this.state.velocity.y)) {\r\n\t\t\tthis.reset();\r\n\t\t}\r\n\r\n\t\treturn this.state.displacement;\r\n\t}\r\n\r\n\t/**\r\n\t * 즉시 충격 적용 (마우스 가속도 기반)\r\n\t */\r\n\tpublic applyImpulse(acceleration: Point, multiplier: number = 1.0) {\r\n\t\t// 가속도를 속도로 변환하여 즉시 적용\r\n\t\tthis.state.velocity.x += acceleration.x * multiplier;\r\n\t\tthis.state.velocity.y += acceleration.y * multiplier;\r\n\t}\r\n\r\n\t/**\r\n\t * 현재 변위 가져오기\r\n\t */\r\n\tpublic getDisplacement(): Point {\r\n\t\treturn { ...this.state.displacement };\r\n\t}\r\n\r\n\t/**\r\n\t * 현재 속도 가져오기\r\n\t */\r\n\tpublic getVelocity(): Point {\r\n\t\treturn { ...this.state.velocity };\r\n\t}\r\n\r\n\t/**\r\n\t * 상태 리셋\r\n\t */\r\n\tpublic reset() {\r\n\t\tthis.state = {\r\n\t\t\tdisplacement: { x: 0, y: 0 },\r\n\t\t\tvelocity: { x: 0, y: 0 },\r\n\t\t\ttarget: { x: 0, y: 0 },\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * 마우스가 멈췄을 때 목표를 0으로 설정 (평형 상태로 복귀)\r\n\t */\r\n\tpublic returnToEquilibrium() {\r\n\t\tthis.state.target = { x: 0, y: 0 };\r\n\t}\r\n}\r\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;","import React, {useRef, useEffect, useState, useCallback, useMemo} from 'react';\r\nimport {DistortionArea, Point} from '@/types';\r\nimport {ImageDistortion} from '@/components/ImageDistortion';\r\nimport {EditorCanvasStyle} from '../types';\r\nimport {DEFAULT_EDITOR_CANVAS_STYLE} from '@/editor';\r\n\r\nexport interface EditorCanvasProps {\r\n\tareas: DistortionArea[];\r\n\tselectedAreaId: string | null;\r\n\timageSrc: string;\r\n\twidth: number;\r\n\theight: number;\r\n\tonUpdatePoint: (areaId: string, pointIndex: number, point: Point) => void;\r\n\tonUpdateArea: (areaId: string, updates: Partial) => void;\r\n\tdraggingPointIndex: number | null;\r\n\tonStartDragging: (pointIndex: number) => void;\r\n\tonStopDragging: () => void;\r\n\t/** 에디터 캔버스 스타일 커스터마이징 */\r\n\tstyle?: EditorCanvasStyle;\r\n\t/** 에디터 UI 표시 여부 (기본값: true) */\r\n\tshowEditor?: boolean;\r\n}\r\n\r\nexport const EditorCanvas: React.FC = ({\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t areas,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t selectedAreaId,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t imageSrc,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t width,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t height,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onUpdatePoint,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onUpdateArea,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t draggingPointIndex,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onStartDragging,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onStopDragging,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t style: customStyle,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t showEditor = true,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t }) => {\r\n\tconst containerRef = useRef(null);\r\n\tconst [canvasSize, setCanvasSize] = useState({width: 0, height: 0});\r\n\tconst [isDraggingArea, setIsDraggingArea] = useState(false);\r\n\tconst [dragStartPos, setDragStartPos] = useState(null);\r\n\r\n\t// 스타일 병합 (커스텀 스타일 우선)\r\n\tconst editorStyle = useMemo(() => ({\r\n\t\t...DEFAULT_EDITOR_CANVAS_STYLE,\r\n\t\t...customStyle,\r\n\t\tcircleLevels: customStyle?.circleLevels || DEFAULT_EDITOR_CANVAS_STYLE.circleLevels,\r\n\t\tcenterPoint: {\r\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.centerPoint,\r\n\t\t\t...customStyle?.centerPoint,\r\n\t\t},\r\n\t\tpointHandle: {\r\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.pointHandle,\r\n\t\t\t...customStyle?.pointHandle,\r\n\t\t},\r\n\t\tareaOutline: {\r\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.areaOutline,\r\n\t\t\t...customStyle?.areaOutline,\r\n\t\t},\r\n\t}), [customStyle]);\r\n\r\n\t// 컨테이너 크기 측정\r\n\tuseEffect(() => {\r\n\t\tif (!containerRef.current) return;\r\n\t\tconst rect = containerRef.current.getBoundingClientRect();\r\n\t\tsetCanvasSize({width: rect.width, height: rect.height});\r\n\t}, [width, height]);\r\n\r\n\t// 선택된 영역 찾기\r\n\tconst selectedArea = areas.find((a) => a.id === selectedAreaId);\r\n\r\n\t// 점이 사각형 내부에 있는지 확인 (Point-in-Polygon test)\r\n\tconst isPointInPolygon = useCallback((point: Point, polygon: Point[]): boolean => {\r\n\t\tlet inside = false;\r\n\t\tfor (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\r\n\t\t\tconst xi = polygon[i].x, yi = polygon[i].y;\r\n\t\t\tconst xj = polygon[j].x, yj = polygon[j].y;\r\n\r\n\t\t\tconst intersect = ((yi > point.y) !== (yj > point.y))\r\n\t\t\t\t&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);\r\n\t\t\tif (intersect) inside = !inside;\r\n\t\t}\r\n\t\treturn inside;\r\n\t}, []);\r\n\r\n\t// 포인트 핸들 클릭/터치 핸들러\r\n\tconst handlePointDown = useCallback(\r\n\t\t(pointIndex: number) => (e: React.MouseEvent | React.TouchEvent) => {\r\n\t\t\te.preventDefault();\r\n\t\t\te.stopPropagation();\r\n\t\t\tonStartDragging(pointIndex);\r\n\t\t},\r\n\t\t[onStartDragging]\r\n\t);\r\n\r\n\t// 캔버스 다운 (마우스/터치 공통)\r\n\tconst handleCanvasDown = useCallback(\r\n\t\t(e: React.MouseEvent | React.TouchEvent) => {\r\n\t\t\t// 에디터가 숨겨진 상태면 동작하지 않음\r\n\t\t\tif (!showEditor || !selectedArea || !containerRef.current) return;\r\n\r\n\t\t\tconst rect = containerRef.current.getBoundingClientRect();\r\n\r\n\t\t\t// 마우스 또는 터치 좌표 추출\r\n\t\t\tlet clientX: number, clientY: number;\r\n\t\t\tif ('touches' in e) {\r\n\t\t\t\tif (e.touches.length === 0) return;\r\n\t\t\t\tclientX = e.touches[0].clientX;\r\n\t\t\t\tclientY = e.touches[0].clientY;\r\n\t\t\t} else {\r\n\t\t\t\tclientX = e.clientX;\r\n\t\t\t\tclientY = e.clientY;\r\n\t\t\t}\r\n\r\n\t\t\tconst x = (clientX - rect.left) / rect.width;\r\n\t\t\tconst y = (clientY - rect.top) / rect.height;\r\n\t\t\tconst clickPoint = { x, y };\r\n\r\n\t\t\t// 사각형 내부를 클릭했는지 확인\r\n\t\t\tif (isPointInPolygon(clickPoint, selectedArea.basePoints)) {\r\n\t\t\t\tsetIsDraggingArea(true);\r\n\t\t\t\tsetDragStartPos(clickPoint);\r\n\t\t\t\te.preventDefault();\r\n\t\t\t}\r\n\t\t},\r\n\t\t[showEditor, selectedArea, isPointInPolygon]\r\n\t);\r\n\r\n\t// 이동 (마우스/터치 공통)\r\n\tconst handleMove = useCallback(\r\n\t\t(e: React.MouseEvent | React.TouchEvent) => {\r\n\t\t\t// 에디터가 숨겨진 상태면 동작하지 않음\r\n\t\t\tif (!showEditor || !selectedArea || !containerRef.current) return;\r\n\r\n\t\t\t// 터치 이벤트면 스크롤 방지\r\n\t\t\tif ('touches' in e && (draggingPointIndex !== null || isDraggingArea)) {\r\n\t\t\t\te.preventDefault();\r\n\t\t\t}\r\n\r\n\t\t\tconst rect = containerRef.current.getBoundingClientRect();\r\n\r\n\t\t\t// 마우스 또는 터치 좌표 추출\r\n\t\t\tlet clientX: number, clientY: number;\r\n\t\t\tif ('touches' in e) {\r\n\t\t\t\tif (e.touches.length === 0) return;\r\n\t\t\t\tclientX = e.touches[0].clientX;\r\n\t\t\t\tclientY = e.touches[0].clientY;\r\n\t\t\t} else {\r\n\t\t\t\tclientX = e.clientX;\r\n\t\t\t\tclientY = e.clientY;\r\n\t\t\t}\r\n\r\n\t\t\tconst x = (clientX - rect.left) / rect.width;\r\n\t\t\tconst y = (clientY - rect.top) / rect.height;\r\n\r\n\t\t\t// 포인트 드래그 중\r\n\t\t\tif (draggingPointIndex !== null) {\r\n\t\t\t\tconst clampedX = Math.max(0, Math.min(1, x));\r\n\t\t\t\tconst clampedY = Math.max(0, Math.min(1, y));\r\n\t\t\t\tonUpdatePoint(selectedArea.id, draggingPointIndex, {x: clampedX, y: clampedY});\r\n\t\t\t}\r\n\t\t\t// 사각형 전체 드래그 중\r\n\t\t\telse if (isDraggingArea && dragStartPos) {\r\n\t\t\t\tconst deltaX = x - dragStartPos.x;\r\n\t\t\t\tconst deltaY = y - dragStartPos.y;\r\n\r\n\t\t\t\t// 모든 포인트를 delta만큼 이동\r\n\t\t\t\tconst newPoints = selectedArea.basePoints.map((point) => ({\r\n\t\t\t\t\tx: Math.max(0, Math.min(1, point.x + deltaX)),\r\n\t\t\t\t\ty: Math.max(0, Math.min(1, point.y + deltaY)),\r\n\t\t\t\t})) as [Point, Point, Point, Point];\r\n\r\n\t\t\t\tonUpdateArea(selectedArea.id, { basePoints: newPoints });\r\n\t\t\t\tsetDragStartPos({ x, y }); // 다음 프레임을 위해 시작 위치 업데이트\r\n\t\t\t}\r\n\t\t},\r\n\t\t[showEditor, draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]\r\n\t);\r\n\r\n\t// 업 (마우스/터치 공통)\r\n\tconst handleUp = useCallback(() => {\r\n\t\tif (draggingPointIndex !== null) {\r\n\t\t\tonStopDragging();\r\n\t\t}\r\n\t\tif (isDraggingArea) {\r\n\t\t\tsetIsDraggingArea(false);\r\n\t\t\tsetDragStartPos(null);\r\n\t\t}\r\n\t}, [draggingPointIndex, isDraggingArea, onStopDragging]);\r\n\r\n\t// 전역 업 이벤트 (마우스/터치)\r\n\tuseEffect(() => {\r\n\t\tif (draggingPointIndex !== null || isDraggingArea) {\r\n\t\t\twindow.addEventListener('mouseup', handleUp);\r\n\t\t\twindow.addEventListener('touchend', handleUp);\r\n\t\t\twindow.addEventListener('touchcancel', handleUp);\r\n\t\t\treturn () => {\r\n\t\t\t\twindow.removeEventListener('mouseup', handleUp);\r\n\t\t\t\twindow.removeEventListener('touchend', handleUp);\r\n\t\t\t\twindow.removeEventListener('touchcancel', handleUp);\r\n\t\t\t};\r\n\t\t}\r\n\t}, [draggingPointIndex, isDraggingArea, handleUp]);\r\n\r\n\t// UV 좌표를 픽셀 좌표로 변환 (셰이더와 동일한 bilinear interpolation)\r\n\tconst uvToPixel = (\r\n\t\tu: number,\r\n\t\tv: number,\r\n\t\tpoints: [Point, Point, Point, Point],\r\n\t\tcanvasWidth: number,\r\n\t\tcanvasHeight: number\r\n\t): { x: number; y: number } => {\r\n\t\t// p0=좌상, p1=우상, p2=우하, p3=좌하\r\n\t\tconst [p0, p1, p2, p3] = points;\r\n\r\n\t\t// 셰이더 computeUV와 동일한 순서로 bilinear interpolation\r\n\t\t// left = mix(p0, p1, u) -> 상단 가장자리\r\n\t\t// right = mix(p3, p2, u) -> 하단 가장자리\r\n\t\t// position = mix(left, right, v)\r\n\t\tconst leftX = p0.x * (1 - u) + p1.x * u;\r\n\t\tconst leftY = p0.y * (1 - u) + p1.y * u;\r\n\r\n\t\tconst rightX = p3.x * (1 - u) + p2.x * u;\r\n\t\tconst rightY = p3.y * (1 - u) + p2.y * u;\r\n\r\n\t\tconst posX = leftX * (1 - v) + rightX * v;\r\n\t\tconst posY = leftY * (1 - v) + rightY * v;\r\n\r\n\t\treturn {\r\n\t\t\tx: posX * canvasWidth,\r\n\t\t\ty: posY * canvasHeight,\r\n\t\t};\r\n\t};\r\n\r\n\t// UV 좌표계의 원을 정확히 그리기 (찌그러진 원 형태)\r\n\tconst drawDistortionCircle = useCallback((\r\n\t\tctx: CanvasRenderingContext2D,\r\n\t\tpoints: [Point, Point, Point, Point],\r\n\t\tcanvasWidth: number,\r\n\t\tcanvasHeight: number\r\n\t) => {\r\n\t\tconst segments = 128; // 원을 128개 세그먼트로 촘촘히 분할\r\n\t\tconst centerU = 0.5;\r\n\t\tconst centerV = 0.5;\r\n\r\n\t\tconst circleLevels = editorStyle.circleLevels || [];\r\n\r\n\t\t// 원 레벨별로 그리기 (외부 -> 내부 순)\r\n\t\tcircleLevels.forEach((level, index) => {\r\n\t\t\tconst levelPoints: { x: number; y: number }[] = [];\r\n\t\t\tfor (let i = 0; i <= segments; i++) {\r\n\t\t\t\tconst theta = (i / segments) * 2 * Math.PI;\r\n\t\t\t\tconst u = centerU - level.radius * Math.sin(theta);\r\n\t\t\t\tconst v = centerV + level.radius * Math.cos(theta);\r\n\t\t\t\tconst pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);\r\n\t\t\t\tlevelPoints.push(pixelPos);\r\n\t\t\t}\r\n\r\n\t\t\tctx.beginPath();\r\n\t\t\tctx.moveTo(levelPoints[0].x, levelPoints[0].y);\r\n\t\t\tfor (let i = 1; i < levelPoints.length; i++) {\r\n\t\t\t\tctx.lineTo(levelPoints[i].x, levelPoints[i].y);\r\n\t\t\t}\r\n\t\t\tctx.closePath();\r\n\r\n\t\t\t// 원 테두리\r\n\t\t\tconst baseColor = level.color || 'rgba(255, 200, 0, 1)';\r\n\t\t\t// baseColor에서 RGB 추출하고 opacity 적용\r\n\t\t\tconst colorWithOpacity = baseColor.replace(/rgba?\\(([^)]+)\\)/, (_, rgb) => {\r\n\t\t\t\tconst parts = rgb.split(',').map((p: string) => p.trim());\r\n\t\t\t\treturn `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${level.opacity})`;\r\n\t\t\t});\r\n\t\t\tctx.strokeStyle = colorWithOpacity;\r\n\t\t\tctx.lineWidth = level.lineWidth;\r\n\t\t\tif (level.dashPattern) {\r\n\t\t\t\tctx.setLineDash(level.dashPattern);\r\n\t\t\t}\r\n\t\t\tctx.stroke();\r\n\t\t\tctx.setLineDash([]);\r\n\r\n\t\t\t// 가장 외부 원만 내부 채우기\r\n\t\t\tif (index === 0 && editorStyle.circleFillColor) {\r\n\t\t\t\tctx.fillStyle = editorStyle.circleFillColor;\r\n\t\t\t\tctx.fill();\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// 중심점 표시\r\n\t\tconst centerPointStyle = editorStyle.centerPoint || {};\r\n\t\tconst centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight);\r\n\t\tctx.beginPath();\r\n\t\tctx.arc(centerPixel.x, centerPixel.y, centerPointStyle.radius || 5, 0, 2 * Math.PI);\r\n\t\tif (centerPointStyle.fillColor) {\r\n\t\t\tctx.fillStyle = centerPointStyle.fillColor;\r\n\t\t\tctx.fill();\r\n\t\t}\r\n\t\tif (centerPointStyle.strokeColor) {\r\n\t\t\tctx.strokeStyle = centerPointStyle.strokeColor;\r\n\t\t\tctx.lineWidth = centerPointStyle.strokeWidth || 2;\r\n\t\t\tctx.stroke();\r\n\t\t}\r\n\t}, [editorStyle]);\r\n\r\n\t// 커서 스타일 결정\r\n\tconst getCursorStyle = () => {\r\n\t\tif (draggingPointIndex !== null) return 'grabbing';\r\n\t\tif (isDraggingArea) return 'grabbing';\r\n\t\treturn 'default';\r\n\t};\r\n\r\n\treturn (\r\n\t\t\r\n\t\t\t{/* ImageDistortion 컴포넌트 */}\r\n\t\t\t\r\n\r\n\t\t\t{/* 오버레이 SVG - 에디터 모드일 때만 표시 */}\r\n\t\t\t{showEditor && (\r\n\t\t\t\t\r\n\t\t\t\t\t{/* 모든 영역의 사각형 표시 */}\r\n\t\t\t\t\t{areas.map((area) => {\r\n\t\t\t\t\tconst isSelected = area.id === selectedAreaId;\r\n\t\t\t\t\tconst points = area.basePoints;\r\n\t\t\t\t\tconst outlineStyle = editorStyle.areaOutline || {};\r\n\t\t\t\t\treturn (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t{/* 사각형 배경 및 경계선 */}\r\n\t\t\t\t\t\t\t `${p.x * canvasSize.width},${p.y * canvasSize.height}`)\r\n\t\t\t\t\t\t\t\t\t.join(' ')}\r\n\t\t\t\t\t\t\t\tfill={isSelected ? (outlineStyle.selectedFillColor || 'rgba(0, 170, 255, 0.08)') : (outlineStyle.unselectedFillColor || 'rgba(136, 136, 136, 0.03)')}\r\n\t\t\t\t\t\t\t\tstroke={isSelected ? (outlineStyle.selectedColor || '#00aaff') : (outlineStyle.unselectedColor || '#888')}\r\n\t\t\t\t\t\t\t\tstrokeWidth={isSelected ? (outlineStyle.selectedWidth || 2) : (outlineStyle.unselectedWidth || 1)}\r\n\t\t\t\t\t\t\t\tstrokeDasharray={isSelected ? '0' : (outlineStyle.unselectedDashPattern?.join(',') || '5,5')}\r\n\t\t\t\t\t\t\t\topacity={isSelected ? 1 : 0.5}\r\n\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t);\r\n\t\t\t\t\t})}\r\n\t\t\t\t\r\n\t\t\t)}\r\n\r\n\t\t\t{/* 선택된 영역의 타원 가이드 (Canvas로 그리기) - 에디터 모드일 때만 표시 */}\r\n\t\t\t{showEditor && selectedArea && canvasSize.width > 0 && (\r\n\t\t\t\t {\r\n\t\t\t\t\t\tif (canvas) {\r\n\t\t\t\t\t\t\tconst ctx = canvas.getContext('2d');\r\n\t\t\t\t\t\t\tif (ctx) {\r\n\t\t\t\t\t\t\t\tctx.clearRect(0, 0, canvasSize.width, canvasSize.height);\r\n\t\t\t\t\t\t\t\tdrawDistortionCircle(ctx, selectedArea.basePoints, canvasSize.width, canvasSize.height);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}}\r\n\t\t\t\t/>\r\n\t\t\t)}\r\n\r\n\t\t\t{/* 선택된 영역의 포인트 핸들 - 에디터 모드일 때만 표시 */}\r\n\t\t\t{showEditor && selectedArea &&\r\n\t\t\t\tselectedArea.basePoints.map((point, index) => {\r\n\t\t\t\t\tconst handleStyle = editorStyle.pointHandle || {};\r\n\t\t\t\t\treturn (\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\t\tP{index + 1}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t);\r\n\t\t\t\t})}\r\n\t\t\r\n\t);\r\n};\r\n","import React from 'react';\nimport { DistortionArea } from '../../types/area';\n\nexport interface AreaListProps {\n\tareas: DistortionArea[];\n\tselectedAreaId: string | null;\n\tonSelectArea: (areaId: string) => void;\n\tonRemoveArea: (areaId: string) => void;\n\tonAddArea: () => void;\n}\n\nexport const AreaList: React.FC = ({\n\tareas,\n\tselectedAreaId,\n\tonSelectArea,\n\tonRemoveArea,\n\tonAddArea,\n}) => {\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t

왜곡 영역

\n\t\t\t\t= 8}\n\t\t\t\t\tclassName=\"btn-add\"\n\t\t\t\t\ttitle={areas.length >= 8 ? '최대 8개 영역까지 지원' : '새 영역 추가'}\n\t\t\t\t>\n\t\t\t\t\t+ 추가\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t\t{areas.length === 0 ? (\n\t\t\t\t\t
영역이 없습니다. + 추가 버튼을 눌러주세요.
\n\t\t\t\t) : (\n\t\t\t\t\tareas.map((area, index) => (\n\t\t\t\t\t\t onSelectArea(area.id)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t영역 {index + 1}\n\t\t\t\t\t\t\t\t강도: {(area.distortionStrength * 100).toFixed(0)}%\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\te.stopPropagation();\n\t\t\t\t\t\t\t\t\tonRemoveArea(area.id);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\tclassName=\"btn-remove\"\n\t\t\t\t\t\t\t\ttitle=\"영역 삭제\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t))\n\t\t\t\t)}\n\t\t\t
\n\t\t\n\t);\n};\n","import React from 'react';\nimport { DistortionArea, EasingFunction } from '../../types/area';\n\nexport interface ParameterPanelProps {\n\tarea: DistortionArea | null;\n\tonUpdateArea: (updates: Partial) => void;\n}\n\nconst EASING_OPTIONS: { value: EasingFunction; label: string }[] = [\n\t{ value: 'linear', label: '선형 (Linear)' },\n\t{ value: 'easeIn', label: '가속 (Ease In)' },\n\t{ value: 'easeOut', label: '감속 (Ease Out)' },\n\t{ value: 'easeInOut', label: '가감속 (Ease In Out)' },\n\t{ value: 'easeInQuad', label: '가속² (Ease In Quad)' },\n\t{ value: 'easeOutQuad', label: '감속² (Ease Out Quad)' },\n];\n\nexport const ParameterPanel: React.FC = ({ area, onUpdateArea }) => {\n\tif (!area) {\n\t\treturn (\n\t\t\t
\n\t\t\t\t
영역을 선택해주세요
\n\t\t\t
\n\t\t);\n\t}\n\n\treturn (\n\t\t
\n\t\t\t

파라미터 편집

\n\n\t\t\t{/* 왜곡 강도 */}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t onUpdateArea({ distortionStrength: parseFloat(e.target.value) })}\n\t\t\t\t\tclassName=\"slider\"\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{/* 애니메이션 지속 시간 */}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\tonUpdateArea({\n\t\t\t\t\t\t\tmovement: { ...area.movement, duration: parseFloat(e.target.value) },\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tclassName=\"input-number\"\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{/* 이징 함수 */}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\tonUpdateArea({\n\t\t\t\t\t\t\tmovement: { ...area.movement, easing: e.target.value as EasingFunction },\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t\tclassName=\"select\"\n\t\t\t\t>\n\t\t\t\t\t{EASING_OPTIONS.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t
\n\n\t\t\t{/* 포인트 좌표 (읽기 전용 표시) */}\n\t\t\t
\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t{area.basePoints.map((point, idx) => (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\tP{idx + 1}: ({point.x.toFixed(3)}, {point.y.toFixed(3)})\n\t\t\t\t\t\t
\n\t\t\t\t\t))}\n\t\t\t\t
\n\t\t\t
\n\t\t
\n\t);\n};\n","import { useState, useCallback } from 'react';\r\nimport { DistortionArea, Point } from '../../types/area';\r\nimport { EditorState } from '../types';\r\n\r\nexport const useDistortionEditor = (initialAreas: DistortionArea[] = []) => {\r\n\tconst [state, setState] = useState({\r\n\t\tselectedAreaId: initialAreas[0]?.id || null,\r\n\t\tareas: initialAreas,\r\n\t\teditMode: 'normal',\r\n\t\tdraggingPointIndex: null,\r\n\t});\r\n\r\n\t/** 영역 선택 */\r\n\tconst selectArea = useCallback((areaId: string | null) => {\r\n\t\tsetState((prev) => ({ ...prev, selectedAreaId: areaId }));\r\n\t}, []);\r\n\r\n\t/** 영역 추가 */\r\n\tconst addArea = useCallback((area: DistortionArea) => {\r\n\t\tsetState((prev) => ({\r\n\t\t\t...prev,\r\n\t\t\tareas: [...prev.areas, area],\r\n\t\t\tselectedAreaId: area.id,\r\n\t\t}));\r\n\t}, []);\r\n\r\n\t/** 영역 삭제 */\r\n\tconst removeArea = useCallback((areaId: string) => {\r\n\t\tsetState((prev) => {\r\n\t\t\tconst newAreas = prev.areas.filter((a) => a.id !== areaId);\r\n\t\t\treturn {\r\n\t\t\t\t...prev,\r\n\t\t\t\tareas: newAreas,\r\n\t\t\t\tselectedAreaId:\r\n\t\t\t\t\tprev.selectedAreaId === areaId ? newAreas[0]?.id || null : prev.selectedAreaId,\r\n\t\t\t};\r\n\t\t});\r\n\t}, []);\r\n\r\n\t/** 영역 업데이트 */\r\n\tconst updateArea = useCallback((areaId: string, updates: Partial) => {\r\n\t\tsetState((prev) => ({\r\n\t\t\t...prev,\r\n\t\t\tareas: prev.areas.map((area) => (area.id === areaId ? { ...area, ...updates } : area)),\r\n\t\t}));\r\n\t}, []);\r\n\r\n\t/** 포인트 업데이트 */\r\n\tconst updatePoint = useCallback((areaId: string, pointIndex: number, point: Point) => {\r\n\t\tsetState((prev) => ({\r\n\t\t\t...prev,\r\n\t\t\tareas: prev.areas.map((area) => {\r\n\t\t\t\tif (area.id === areaId) {\r\n\t\t\t\t\tconst newPoints = [...area.basePoints] as [Point, Point, Point, Point];\r\n\t\t\t\t\tnewPoints[pointIndex] = point;\r\n\t\t\t\t\treturn { ...area, basePoints: newPoints };\r\n\t\t\t\t}\r\n\t\t\t\treturn area;\r\n\t\t\t}),\r\n\t\t}));\r\n\t}, []);\r\n\r\n\t/** 드래그 시작 */\r\n\tconst startDragging = useCallback((pointIndex: number) => {\r\n\t\tsetState((prev) => ({ ...prev, draggingPointIndex: pointIndex }));\r\n\t}, []);\r\n\r\n\t/** 드래그 종료 */\r\n\tconst stopDragging = useCallback(() => {\r\n\t\tsetState((prev) => ({ ...prev, draggingPointIndex: null }));\r\n\t}, []);\r\n\r\n\t/** 편집 모드 변경 */\r\n\tconst setEditMode = useCallback((mode: EditorState['editMode']) => {\r\n\t\tsetState((prev) => ({ ...prev, editMode: mode }));\r\n\t}, []);\r\n\r\n\t/** 선택된 영역 가져오기 */\r\n\tconst getSelectedArea = useCallback(() => {\r\n\t\treturn state.areas.find((a) => a.id === state.selectedAreaId) || null;\r\n\t}, [state.areas, state.selectedAreaId]);\r\n\r\n\treturn {\r\n\t\tstate,\r\n\t\tselectArea,\r\n\t\taddArea,\r\n\t\tremoveArea,\r\n\t\tupdateArea,\r\n\t\tupdatePoint,\r\n\t\tstartDragging,\r\n\t\tstopDragging,\r\n\t\tsetEditMode,\r\n\t\tgetSelectedArea,\r\n\t};\r\n};\r\n","import { EditorCanvasStyle } from './types';\n\n/**\n * 기본 에디터 캔버스 스타일\n */\nexport const DEFAULT_EDITOR_CANVAS_STYLE: EditorCanvasStyle = {\n\t// 3단계 원 스타일 (외부 -> 내부)\n\tcircleLevels: [\n\t\t{\n\t\t\tradius: 0.5,\n\t\t\topacity: 0.3,\n\t\t\tlineWidth: 2,\n\t\t\tcolor: 'rgba(255, 200, 0, 1)',\n\t\t\tdashPattern: [8, 4],\n\t\t},\n\t\t{\n\t\t\tradius: 0.33,\n\t\t\topacity: 0.6,\n\t\t\tlineWidth: 2.5,\n\t\t\tcolor: 'rgba(255, 200, 0, 1)',\n\t\t\tdashPattern: [8, 4],\n\t\t},\n\t\t{\n\t\t\tradius: 0.167,\n\t\t\topacity: 0.9,\n\t\t\tlineWidth: 3,\n\t\t\tcolor: 'rgba(255, 200, 0, 1)',\n\t\t\tdashPattern: [8, 4],\n\t\t},\n\t],\n\t// 원 내부 채우기\n\tcircleFillColor: 'rgba(255, 200, 0, 0.08)',\n\t// 중심점\n\tcenterPoint: {\n\t\tradius: 5,\n\t\tfillColor: 'rgba(255, 200, 0, 1)',\n\t\tstrokeColor: 'rgba(255, 255, 255, 0.8)',\n\t\tstrokeWidth: 2,\n\t},\n\t// 포인트 핸들\n\tpointHandle: {\n\t\tsize: 16,\n\t\tfillColor: '#00aaff',\n\t\tstrokeColor: 'white',\n\t\tstrokeWidth: 2,\n\t\tlabelColor: '#00aaff',\n\t\tlabelFontSize: 11,\n\t},\n\t// 영역 외곽선\n\tareaOutline: {\n\t\tselectedColor: '#00aaff',\n\t\tunselectedColor: '#888',\n\t\tselectedWidth: 2,\n\t\tunselectedWidth: 1,\n\t\tunselectedDashPattern: [5, 5],\n\t\tselectedFillColor: 'rgba(0, 170, 255, 0.08)', // 선택된 영역 배경 (연한 파란색)\n\t\tunselectedFillColor: 'rgba(136, 136, 136, 0.03)', // 선택 안된 영역 배경 (연한 회색)\n\t},\n};\n"],"mappings":"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","names":["useEffect","useRef","useState","useCallback","THREE","useRef","useCallback","useRef","useEffect","useRef","useCallback","useRef","useState","useEffect","useCallback","useRef","useEffect","useState","useCallback","jsx","jsx","jsxs","useState","useCallback","jsx","jsxs","useRef","useState","useEffect","isPointInPolygon","useCallback"]} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c3281e8..b93fcb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,13 @@ { - "name": "responsive-image-canvas", - "version": "1.0.0", + "name": "@baekryang/responsive-image-canvas", + "version": "1.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "responsive-image-canvas", - "version": "1.0.0", + "name": "@baekryang/responsive-image-canvas", + "version": "1.0.5", + "license": "MIT", "devDependencies": { "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", @@ -16,6 +17,11 @@ "three": "^0.181.0", "tsup": "^8.5.0", "typescript": "^5.5.3" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "three": ">=0.150.0" } }, "node_modules/@dimforge/rapier3d-compat": { diff --git a/package.json b/package.json index 553c272..441b687 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@baekryang/responsive-image-canvas", - "version": "1.0.5", + "version": "1.1.0", "publishConfig": { "registry": "https://git.bnovalab.com/api/packages/baekryang/npm/" }, diff --git a/src/index.ts b/src/index.ts index fa65a18..0846850 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,7 +31,9 @@ export { DEFAULT_EDITOR_CANVAS_STYLE } from './editor/constants'; export type { Point, EasingFunction, + BuiltInMotionPreset, MotionPreset, + MotionPresetDefinition, DistortionMovement, DistortionArea, AreaBounds, @@ -52,7 +54,17 @@ export type { // 유틸리티 함수 export { applyEasing } from './utils/easing'; export { SHADER_CONFIG, ANIMATION_CONFIG, DEFAULT_AREA } from './utils/constants'; -export { presetToVector, isRotationPreset } from './utils/motionPresets'; +export { + presetToVector, + isRotationPreset, + // 프리셋 레지스트리 API + registerMotionPreset, + registerMotionPresets, + unregisterMotionPreset, + getRegisteredPresets, + hasPreset, + resetToBuiltInPresets, +} from './utils/motionPresets'; // 엔진 클래스 (고급 사용자용) export { ThreeScene } from './engine/ThreeScene'; diff --git a/src/types/area.ts b/src/types/area.ts index 315db05..bb1d99a 100644 --- a/src/types/area.ts +++ b/src/types/area.ts @@ -18,9 +18,9 @@ export type EasingFunction = | 'easeOutQuad'; /** - * 모션 프리셋 타입 + * 내장 모션 프리셋 타입 */ -export type MotionPreset = +export type BuiltInMotionPreset = | 'none' // 없음 (애니메이션 없음) | 'horizontal' // 좌우 왕복 | 'vertical' // 상하 왕복 @@ -30,6 +30,24 @@ export type MotionPreset = | 'diagonal-1' // 대각선 (좌상→우하) | 'diagonal-2'; // 대각선 (우상→좌하) +/** + * 모션 프리셋 타입 (내장 + 커스텀) + * 커스텀 프리셋은 registerMotionPreset()으로 등록 후 사용 + */ +export type MotionPreset = BuiltInMotionPreset | (string & {}); + +/** + * 모션 프리셋 정의 + * @param strength 모션 강도 (기본값: 0.1) + * @returns x, y 벡터값 + */ +export type MotionPresetDefinition = (strength: number) => Point; + +/** + * 회전 프리셋 판별 함수 + */ +export type RotationPresetChecker = (preset: MotionPreset) => boolean; + /** * 왜곡 애니메이션 움직임 설정 */ diff --git a/src/utils/motionPresets.ts b/src/utils/motionPresets.ts index f4a9eb3..580d9a1 100644 --- a/src/utils/motionPresets.ts +++ b/src/utils/motionPresets.ts @@ -1,4 +1,130 @@ -import type {MotionPreset, Point} from '../types'; +import type {MotionPreset, MotionPresetDefinition, Point, RotationPresetChecker} from '../types'; + +/** + * 프리셋 레지스트리 (내장 + 커스텀) + */ +const presetRegistry = new Map(); + +/** + * 회전 프리셋 목록 + */ +const rotationPresets = new Set(['rotate-cw', 'rotate-ccw']); + +/** + * 내장 프리셋 정의 + */ +const BUILT_IN_PRESETS: Record = { + 'none': () => ({x: 0, y: 0}), + 'horizontal': (strength) => ({x: strength, y: 0}), + 'vertical': (strength) => ({x: 0, y: strength}), + 'rotate-cw': (strength) => ({x: strength, y: 0}), + 'rotate-ccw': (strength) => ({x: -strength, y: 0}), + 'pulse': (strength) => ({x: strength, y: strength}), + 'diagonal-1': (strength) => ({x: strength * 0.707, y: strength * 0.707}), + 'diagonal-2': (strength) => ({x: strength * 0.707, y: -strength * 0.707}), +}; + +// 내장 프리셋 등록 +Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => { + presetRegistry.set(name, definition); +}); + +/** + * 커스텀 모션 프리셋 등록 + * @param name 프리셋 이름 + * @param definition 프리셋 정의 함수 (strength를 받아 Point 반환) + * @param options 추가 옵션 + * @param options.isRotation 회전 애니메이션 여부 (true면 원운동) + * + * @example + * // 좌우 진짜 왕복 (좌↔우) + * registerMotionPreset('horizontal-full', (strength) => ({ + * x: strength * 2, // 진폭 2배 + * y: 0 + * })); + * + * // 8자 모양 운동 (회전) + * registerMotionPreset('figure-8', (strength) => ({ + * x: strength, + * y: strength * 0.5 + * }), { isRotation: true }); + */ +export function registerMotionPreset( + name: string, + definition: MotionPresetDefinition, + options?: { isRotation?: boolean } +): void { + presetRegistry.set(name, definition); + + if (options?.isRotation) { + rotationPresets.add(name); + } else { + rotationPresets.delete(name); + } +} + +/** + * 여러 프리셋을 한번에 등록 + * @param presets 프리셋 맵 (이름 → 정의) + * @param rotationPresetNames 회전 프리셋 이름 목록 + * + * @example + * registerMotionPresets({ + * 'horizontal-full': (s) => ({x: s * 2, y: 0}), + * 'wave': (s) => ({x: s, y: s * 0.3}), + * }, ['wave']); // wave는 회전 애니메이션 + */ +export function registerMotionPresets( + presets: Record, + rotationPresetNames?: string[] +): void { + Object.entries(presets).forEach(([name, definition]) => { + presetRegistry.set(name, definition); + }); + + rotationPresetNames?.forEach(name => rotationPresets.add(name)); +} + +/** + * 프리셋 등록 해제 + * @param name 프리셋 이름 + * @returns 해제 성공 여부 + */ +export function unregisterMotionPreset(name: string): boolean { + rotationPresets.delete(name); + return presetRegistry.delete(name); +} + +/** + * 등록된 모든 프리셋 이름 조회 + * @returns 프리셋 이름 배열 + */ +export function getRegisteredPresets(): string[] { + return Array.from(presetRegistry.keys()); +} + +/** + * 프리셋 존재 여부 확인 + * @param name 프리셋 이름 + * @returns 존재 여부 + */ +export function hasPreset(name: string): boolean { + return presetRegistry.has(name); +} + +/** + * 내장 프리셋으로 초기화 (커스텀 프리셋 모두 제거) + */ +export function resetToBuiltInPresets(): void { + presetRegistry.clear(); + rotationPresets.clear(); + + Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => { + presetRegistry.set(name, definition); + }); + rotationPresets.add('rotate-cw'); + rotationPresets.add('rotate-ccw'); +} /** * 모션 프리셋을 벡터로 변환 @@ -7,47 +133,31 @@ import type {MotionPreset, Point} from '../types'; * @returns 계산된 벡터 (vectorA) */ export function presetToVector(preset: MotionPreset, strength: number = 0.1): Point { - switch (preset) { - case 'none': - // 애니메이션 없음 - return {x: 0, y: 0}; + const definition = presetRegistry.get(preset); - case 'horizontal': - // 좌우 왕복 - return {x: strength, y: 0}; - - case 'vertical': - // 상하 왕복 - return {x: 0, y: strength}; - - case 'rotate-cw': - // 시계방향 회전 (원운동의 시작점) - return {x: strength, y: 0}; - - case 'rotate-ccw': - // 반시계방향 회전 (원운동의 시작점) - return {x: -strength, y: 0}; - - case 'pulse': - // 펄스 (중심에서 바깥으로) - return {x: strength, y: strength}; - - case 'diagonal-1': - // 대각선 (좌상→우하) - return {x: strength * 0.707, y: strength * 0.707}; // √2/2 ≈ 0.707 - - case 'diagonal-2': - // 대각선 (우상→좌하) - return {x: strength * 0.707, y: -strength * 0.707}; - - default: - return {x: 0, y: 0}; + if (definition) { + return definition(strength); } + + // 등록되지 않은 프리셋은 none으로 처리 + console.warn(`Unknown motion preset: "${preset}". Falling back to "none".`); + return {x: 0, y: 0}; } /** * 프리셋이 회전 타입인지 확인 */ export function isRotationPreset(preset?: MotionPreset): boolean { - return preset === 'rotate-cw' || preset === 'rotate-ccw'; + if (!preset) return false; + return rotationPresets.has(preset); +} + +/** + * 커스텀 회전 프리셋 판별 함수 등록 + * @param checker 판별 함수 + * @deprecated isRotation 옵션을 registerMotionPreset에 전달하세요 + */ +export function setRotationChecker(checker: RotationPresetChecker): void { + // Legacy support - 기존 코드 호환성을 위해 유지 + console.warn('setRotationChecker is deprecated. Use registerMotionPreset with { isRotation: true } option instead.'); }