From 0c3c0b606e066ae4b680e898e7a2db256228781d Mon Sep 17 00:00:00 2001 From: BaekRyang Date: Wed, 5 Nov 2025 11:48:05 +0900 Subject: [PATCH] feat: Add canvas style customization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 왜곡 영역 원 레벨 스타일, 중심점, 포인트 핸들, 영역 외곽선 등 캔버스 스타일을 커스터마이징할 수 있도록 `EditorCanvasStyle` 타입을 추가했습니다. - `DistortionEditorProps`에 `canvasStyle` prop을 추가하여 외부에서 캔버스 스타일을 전달받을 수 있도록 했습니다. - `EditorCanvas` 컴포넌트에서 `useMemo`를 사용하여 기본 스타일과 사용자 정의 스타일을 병합하고, 이를 렌더링에 반영하도록 수정했습니다. --- dist/index.d.mts | 81 ++++++++ dist/index.d.ts | 81 ++++++++ dist/index.js | 262 +++++++++++++++--------- dist/index.js.map | 2 +- dist/index.mjs | 264 ++++++++++++++++--------- dist/index.mjs.map | 2 +- src/editor/DistortionEditor.tsx | 2 + src/editor/components/EditorCanvas.tsx | 200 ++++++++++--------- src/editor/index.ts | 12 +- src/editor/types.ts | 86 ++++++++ 10 files changed, 713 insertions(+), 279 deletions(-) diff --git a/dist/index.d.mts b/dist/index.d.mts index c2f32ee..83c3a71 100644 --- a/dist/index.d.mts +++ b/dist/index.d.mts @@ -149,6 +149,85 @@ interface EditorState { /** 드래그 중인 포인트 인덱스 (0-3) */ draggingPointIndex: number | null; } +/** + * 왜곡 영역 원 레벨 스타일 + */ +interface CircleLevelStyle { + /** 반지름 (0.0 - 1.0, UV 좌표) */ + radius: number; + /** 투명도 (0.0 - 1.0) */ + opacity: number; + /** 선 두께 (픽셀) */ + lineWidth: number; + /** 선 색상 (CSS color) */ + color?: string; + /** 대시 패턴 [dash, gap] */ + dashPattern?: [number, number]; +} +/** + * 중심점 스타일 + */ +interface CenterPointStyle { + /** 반지름 (픽셀) */ + radius?: number; + /** 채우기 색상 */ + fillColor?: string; + /** 테두리 색상 */ + strokeColor?: string; + /** 테두리 두께 */ + strokeWidth?: number; +} +/** + * 포인트 핸들 스타일 + */ +interface PointHandleStyle { + /** 핸들 크기 (픽셀) */ + size?: number; + /** 채우기 색상 */ + fillColor?: string; + /** 테두리 색상 */ + strokeColor?: string; + /** 테두리 두께 */ + strokeWidth?: number; + /** 레이블 색상 */ + labelColor?: string; + /** 레이블 폰트 크기 */ + labelFontSize?: number; +} +/** + * 영역 외곽선 스타일 + */ +interface AreaOutlineStyle { + /** 선택된 영역 색상 */ + selectedColor?: string; + /** 선택되지 않은 영역 색상 */ + unselectedColor?: string; + /** 선택된 영역 선 두께 */ + selectedWidth?: number; + /** 선택되지 않은 영역 선 두께 */ + unselectedWidth?: number; + /** 선택되지 않은 영역 대시 패턴 */ + unselectedDashPattern?: [number, number]; + /** 선택된 영역 배경 채우기 색상 */ + selectedFillColor?: string; + /** 선택되지 않은 영역 배경 채우기 색상 */ + unselectedFillColor?: string; +} +/** + * 에디터 캔버스 스타일 + */ +interface EditorCanvasStyle { + /** 왜곡 영역 원 레벨 스타일 배열 (외부 -> 내부 순) */ + circleLevels?: CircleLevelStyle[]; + /** 왜곡 영역 내부 채우기 색상 */ + circleFillColor?: string; + /** 중심점 스타일 */ + centerPoint?: CenterPointStyle; + /** 포인트 핸들 스타일 */ + pointHandle?: PointHandleStyle; + /** 영역 외곽선 스타일 */ + areaOutline?: AreaOutlineStyle; +} /** * 에디터 Props */ @@ -165,6 +244,8 @@ interface DistortionEditorProps { width?: number; /** 캔버스 높이 */ height?: number; + /** 에디터 캔버스 스타일 커스터마이징 */ + canvasStyle?: EditorCanvasStyle; } declare const DistortionEditor: React.FC; diff --git a/dist/index.d.ts b/dist/index.d.ts index c2f32ee..83c3a71 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -149,6 +149,85 @@ interface EditorState { /** 드래그 중인 포인트 인덱스 (0-3) */ draggingPointIndex: number | null; } +/** + * 왜곡 영역 원 레벨 스타일 + */ +interface CircleLevelStyle { + /** 반지름 (0.0 - 1.0, UV 좌표) */ + radius: number; + /** 투명도 (0.0 - 1.0) */ + opacity: number; + /** 선 두께 (픽셀) */ + lineWidth: number; + /** 선 색상 (CSS color) */ + color?: string; + /** 대시 패턴 [dash, gap] */ + dashPattern?: [number, number]; +} +/** + * 중심점 스타일 + */ +interface CenterPointStyle { + /** 반지름 (픽셀) */ + radius?: number; + /** 채우기 색상 */ + fillColor?: string; + /** 테두리 색상 */ + strokeColor?: string; + /** 테두리 두께 */ + strokeWidth?: number; +} +/** + * 포인트 핸들 스타일 + */ +interface PointHandleStyle { + /** 핸들 크기 (픽셀) */ + size?: number; + /** 채우기 색상 */ + fillColor?: string; + /** 테두리 색상 */ + strokeColor?: string; + /** 테두리 두께 */ + strokeWidth?: number; + /** 레이블 색상 */ + labelColor?: string; + /** 레이블 폰트 크기 */ + labelFontSize?: number; +} +/** + * 영역 외곽선 스타일 + */ +interface AreaOutlineStyle { + /** 선택된 영역 색상 */ + selectedColor?: string; + /** 선택되지 않은 영역 색상 */ + unselectedColor?: string; + /** 선택된 영역 선 두께 */ + selectedWidth?: number; + /** 선택되지 않은 영역 선 두께 */ + unselectedWidth?: number; + /** 선택되지 않은 영역 대시 패턴 */ + unselectedDashPattern?: [number, number]; + /** 선택된 영역 배경 채우기 색상 */ + selectedFillColor?: string; + /** 선택되지 않은 영역 배경 채우기 색상 */ + unselectedFillColor?: string; +} +/** + * 에디터 캔버스 스타일 + */ +interface EditorCanvasStyle { + /** 왜곡 영역 원 레벨 스타일 배열 (외부 -> 내부 순) */ + circleLevels?: CircleLevelStyle[]; + /** 왜곡 영역 내부 채우기 색상 */ + circleFillColor?: string; + /** 중심점 스타일 */ + centerPoint?: CenterPointStyle; + /** 포인트 핸들 스타일 */ + pointHandle?: PointHandleStyle; + /** 영역 외곽선 스타일 */ + areaOutline?: AreaOutlineStyle; +} /** * 에디터 Props */ @@ -165,6 +244,8 @@ interface DistortionEditorProps { width?: number; /** 캔버스 높이 */ height?: number; + /** 에디터 캔버스 스타일 커스터마이징 */ + canvasStyle?: EditorCanvasStyle; } declare const DistortionEditor: React.FC; diff --git a/dist/index.js b/dist/index.js index f67d8fe..75cba9d 100644 --- a/dist/index.js +++ b/dist/index.js @@ -583,6 +583,66 @@ var useDistortionEditor = (initialAreas = []) => { // src/editor/components/EditorCanvas.tsx var import_react4 = require("react"); + +// src/editor/constants.ts +var DEFAULT_EDITOR_CANVAS_STYLE = { + // 3단계 원 스타일 (외부 -> 내부) + circleLevels: [ + { + radius: 0.5, + opacity: 0.3, + lineWidth: 2, + color: "rgba(255, 200, 0, 1)", + dashPattern: [8, 4] + }, + { + radius: 0.33, + opacity: 0.6, + lineWidth: 2.5, + color: "rgba(255, 200, 0, 1)", + dashPattern: [8, 4] + }, + { + radius: 0.167, + opacity: 0.9, + lineWidth: 3, + color: "rgba(255, 200, 0, 1)", + dashPattern: [8, 4] + } + ], + // 원 내부 채우기 + circleFillColor: "rgba(255, 200, 0, 0.08)", + // 중심점 + centerPoint: { + radius: 5, + fillColor: "rgba(255, 200, 0, 1)", + strokeColor: "rgba(255, 255, 255, 0.8)", + strokeWidth: 2 + }, + // 포인트 핸들 + pointHandle: { + size: 16, + fillColor: "#00aaff", + strokeColor: "white", + strokeWidth: 2, + labelColor: "#00aaff", + labelFontSize: 11 + }, + // 영역 외곽선 + areaOutline: { + selectedColor: "#00aaff", + unselectedColor: "#888", + selectedWidth: 2, + unselectedWidth: 1, + unselectedDashPattern: [5, 5], + selectedFillColor: "rgba(0, 170, 255, 0.08)", + // 선택된 영역 배경 (연한 파란색) + unselectedFillColor: "rgba(136, 136, 136, 0.03)" + // 선택 안된 영역 배경 (연한 회색) + } +}; + +// src/editor/components/EditorCanvas.tsx var import_jsx_runtime2 = require("react/jsx-runtime"); var EditorCanvas = ({ areas, @@ -594,12 +654,30 @@ var EditorCanvas = ({ onUpdateArea, draggingPointIndex, onStartDragging, - onStopDragging + onStopDragging, + style: customStyle }) => { const containerRef = (0, import_react4.useRef)(null); const [canvasSize, setCanvasSize] = (0, import_react4.useState)({ width: 0, height: 0 }); const [isDraggingArea, setIsDraggingArea] = (0, import_react4.useState)(false); const [dragStartPos, setDragStartPos] = (0, import_react4.useState)(null); + const editorStyle = (0, import_react4.useMemo)(() => ({ + ...DEFAULT_EDITOR_CANVAS_STYLE, + ...customStyle, + circleLevels: customStyle?.circleLevels || DEFAULT_EDITOR_CANVAS_STYLE.circleLevels, + centerPoint: { + ...DEFAULT_EDITOR_CANVAS_STYLE.centerPoint, + ...customStyle?.centerPoint + }, + pointHandle: { + ...DEFAULT_EDITOR_CANVAS_STYLE.pointHandle, + ...customStyle?.pointHandle + }, + areaOutline: { + ...DEFAULT_EDITOR_CANVAS_STYLE.areaOutline, + ...customStyle?.areaOutline + } + }), [customStyle]); (0, import_react4.useEffect)(() => { if (!containerRef.current) return; const rect = containerRef.current.getBoundingClientRect(); @@ -690,63 +768,57 @@ var EditorCanvas = ({ y: posY * canvasHeight }; }; - const drawDistortionCircle = (ctx, points, canvasWidth, canvasHeight) => { + const drawDistortionCircle = (0, import_react4.useCallback)((ctx, points, canvasWidth, canvasHeight) => { const segments = 128; const centerU = 0.5; const centerV = 0.5; - const maxRadius = 0.5; - const circlePoints = []; - for (let i = 0; i <= segments; i++) { - const theta = i / segments * 2 * Math.PI; - const u = centerU - maxRadius * Math.sin(theta); - const v = centerV + maxRadius * Math.cos(theta); - const pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight); - circlePoints.push(pixelPos); - } - ctx.beginPath(); - ctx.moveTo(circlePoints[0].x, circlePoints[0].y); - for (let i = 1; i < circlePoints.length; i++) { - ctx.lineTo(circlePoints[i].x, circlePoints[i].y); - } - ctx.closePath(); - ctx.strokeStyle = "rgba(255, 200, 0, 0.9)"; - ctx.lineWidth = 3; - ctx.setLineDash([8, 4]); - ctx.stroke(); - ctx.setLineDash([]); - ctx.fillStyle = "rgba(255, 200, 0, 0.12)"; - ctx.fill(); - for (const r of [0.25, 0.375]) { - const gradientPoints = []; + const circleLevels = editorStyle.circleLevels || []; + circleLevels.forEach((level, index) => { + const levelPoints = []; for (let i = 0; i <= segments; i++) { const theta = i / segments * 2 * Math.PI; - const u = centerU - r * Math.sin(theta); - const v = centerV + r * Math.cos(theta); + const u = centerU - level.radius * Math.sin(theta); + const v = centerV + level.radius * Math.cos(theta); const pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight); - gradientPoints.push(pixelPos); + levelPoints.push(pixelPos); } ctx.beginPath(); - ctx.moveTo(gradientPoints[0].x, gradientPoints[0].y); - for (let i = 1; i < gradientPoints.length; i++) { - ctx.lineTo(gradientPoints[i].x, gradientPoints[i].y); + ctx.moveTo(levelPoints[0].x, levelPoints[0].y); + for (let i = 1; i < levelPoints.length; i++) { + ctx.lineTo(levelPoints[i].x, levelPoints[i].y); } ctx.closePath(); - const alpha = r / maxRadius; - ctx.strokeStyle = `rgba(255, 220, 100, ${0.3 * alpha})`; - ctx.lineWidth = 1; - ctx.setLineDash([3, 3]); + const baseColor = level.color || "rgba(255, 200, 0, 1)"; + const colorWithOpacity = baseColor.replace(/rgba?\(([^)]+)\)/, (_, rgb) => { + const parts = rgb.split(",").map((p) => p.trim()); + return `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${level.opacity})`; + }); + ctx.strokeStyle = colorWithOpacity; + ctx.lineWidth = level.lineWidth; + if (level.dashPattern) { + ctx.setLineDash(level.dashPattern); + } ctx.stroke(); ctx.setLineDash([]); - } + if (index === 0 && editorStyle.circleFillColor) { + ctx.fillStyle = editorStyle.circleFillColor; + ctx.fill(); + } + }); + const centerPointStyle = editorStyle.centerPoint || {}; const centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight); ctx.beginPath(); - ctx.arc(centerPixel.x, centerPixel.y, 5, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(255, 200, 0, 1)"; - ctx.fill(); - ctx.strokeStyle = "rgba(255, 255, 255, 0.8)"; - ctx.lineWidth = 2; - ctx.stroke(); - }; + ctx.arc(centerPixel.x, centerPixel.y, centerPointStyle.radius || 5, 0, 2 * Math.PI); + if (centerPointStyle.fillColor) { + ctx.fillStyle = centerPointStyle.fillColor; + ctx.fill(); + } + if (centerPointStyle.strokeColor) { + ctx.strokeStyle = centerPointStyle.strokeColor; + ctx.lineWidth = centerPointStyle.strokeWidth || 2; + ctx.stroke(); + } + }, [editorStyle]); const getCursorStyle = () => { if (draggingPointIndex !== null) return "grabbing"; if (isDraggingArea) return "grabbing"; @@ -761,7 +833,7 @@ var EditorCanvas = ({ onMouseDown: handleCanvasMouseDown, onMouseMove: handleMouseMove, children: [ - /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ImageDistortion, { imageSrc, areas, width, height }), + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ImageDistortion, { imageSrc, areas }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "svg", { @@ -776,14 +848,15 @@ var EditorCanvas = ({ children: areas.map((area) => { const isSelected = area.id === selectedAreaId; const points = area.basePoints; + const outlineStyle = editorStyle.areaOutline || {}; return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("g", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "polygon", { points: points.map((p) => `${p.x * canvasSize.width},${p.y * canvasSize.height}`).join(" "), - fill: "none", - stroke: isSelected ? "#00aaff" : "#888", - strokeWidth: isSelected ? 2 : 1, - strokeDasharray: isSelected ? "0" : "5,5", + fill: isSelected ? outlineStyle.selectedFillColor || "rgba(0, 170, 255, 0.08)" : outlineStyle.unselectedFillColor || "rgba(136, 136, 136, 0.03)", + stroke: isSelected ? outlineStyle.selectedColor || "#00aaff" : outlineStyle.unselectedColor || "#888", + strokeWidth: isSelected ? outlineStyle.selectedWidth || 2 : outlineStyle.unselectedWidth || 1, + strokeDasharray: isSelected ? "0" : outlineStyle.unselectedDashPattern?.join(",") || "5,5", opacity: isSelected ? 1 : 0.5 } ) }, area.id); @@ -814,48 +887,51 @@ var EditorCanvas = ({ } } ), - selectedArea && selectedArea.basePoints.map((point, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( - "div", - { - className: `point-handle ${draggingPointIndex === index ? "dragging" : ""}`, - style: { - position: "absolute", - left: `${point.x * 100}%`, - top: `${point.y * 100}%`, - transform: "translate(-50%, -50%)", - width: 16, - height: 16, - borderRadius: "50%", - backgroundColor: "#00aaff", - border: "2px solid white", - cursor: "grab", - pointerEvents: "auto", - boxShadow: "0 2px 4px rgba(0,0,0,0.3)" + selectedArea && selectedArea.basePoints.map((point, index) => { + const handleStyle = editorStyle.pointHandle || {}; + return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( + "div", + { + className: `point-handle ${draggingPointIndex === index ? "dragging" : ""}`, + style: { + position: "absolute", + left: `${point.x * 100}%`, + top: `${point.y * 100}%`, + transform: "translate(-50%, -50%)", + width: handleStyle.size || 16, + height: handleStyle.size || 16, + borderRadius: "50%", + backgroundColor: handleStyle.fillColor || "#00aaff", + border: `${handleStyle.strokeWidth || 2}px solid ${handleStyle.strokeColor || "white"}`, + cursor: "grab", + pointerEvents: "auto", + boxShadow: "0 2px 4px rgba(0,0,0,0.3)" + }, + onMouseDown: handleMouseDown(index), + children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)( + "div", + { + style: { + position: "absolute", + top: -24, + left: "50%", + transform: "translateX(-50%)", + fontSize: handleStyle.labelFontSize || 11, + color: handleStyle.labelColor || "#00aaff", + fontWeight: "bold", + textShadow: "1px 1px 2px rgba(0,0,0,0.8)", + whiteSpace: "nowrap" + }, + children: [ + "P", + index + 1 + ] + } + ) }, - onMouseDown: handleMouseDown(index), - children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)( - "div", - { - style: { - position: "absolute", - top: -24, - left: "50%", - transform: "translateX(-50%)", - fontSize: 11, - color: "#00aaff", - fontWeight: "bold", - textShadow: "1px 1px 2px rgba(0,0,0,0.8)", - whiteSpace: "nowrap" - }, - children: [ - "P", - index + 1 - ] - } - ) - }, - index - )) + index + ); + }) ] } ); @@ -1059,7 +1135,8 @@ var DistortionEditor = ({ onAreasChange, onSelectedAreaChange, width = 800, - height = 600 + height = 600, + canvasStyle }) => { const { state, @@ -1118,7 +1195,8 @@ var DistortionEditor = ({ onUpdateArea: updateArea, draggingPointIndex: state.draggingPointIndex, onStartDragging: startDragging, - onStopDragging: stopDragging + onStopDragging: stopDragging, + style: canvasStyle } ) }), /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "editor-sidebar", children: [ diff --git a/dist/index.js.map b/dist/index.js.map index 33c1c26..4bb5e49 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/index.ts","../src/components/ImageDistortion.tsx","../src/engine/ThreeScene.ts","../src/engine/ShaderManager.ts","../src/utils/easing.ts","../src/engine/AnimationLoop.ts","../src/hooks/useAnimationFrame.ts","../src/utils/constants.ts","../src/editor/DistortionEditor.tsx","../src/editor/hooks/useDistortionEditor.ts","../src/editor/components/EditorCanvas.tsx","../src/editor/components/AreaList.tsx","../src/editor/components/ParameterPanel.tsx"],"sourcesContent":["// 메인 컴포넌트\nexport { ImageDistortion } from './components/ImageDistortion';\nexport type { ImageDistortionProps } from './components/ImageDistortion';\n\n// 에디터 (4점 사각형 + 정확한 UV 좌표계 원형 왜곡 가이드)\nexport { DistortionEditor } from './editor';\nexport type { DistortionEditorProps, EditorState, EditMode } from './editor';\nexport { useDistortionEditor } from './editor';\n\n// 타입 정의\nexport type {\n Point,\n EasingFunction,\n DistortionMovement,\n DistortionArea,\n AreaBounds,\n ShaderUniforms,\n ShaderConfig,\n AnimationState,\n AnimationTicker,\n} from './types';\n\n// 유틸리티 함수\nexport { applyEasing } from './utils/easing';\nexport { SHADER_CONFIG, ANIMATION_CONFIG, DEFAULT_AREA } from './utils/constants';\n\n// 엔진 클래스 (고급 사용자용)\nexport { ThreeScene } from './engine/ThreeScene';\nexport { ShaderManager } from './engine/ShaderManager';\nexport { AnimationLoop } from './engine/AnimationLoop';\n\n// 훅\nexport { useAnimationFrame } from './hooks/useAnimationFrame';","import React, { useEffect, useRef, useState, useCallback } from 'react';\nimport * as THREE from 'three';\nimport { type DistortionArea } from '../types';\nimport { ThreeScene } from '../engine/ThreeScene';\nimport { ShaderManager } from '../engine/ShaderManager';\nimport { AnimationLoop } from '../engine/AnimationLoop';\nimport { useAnimationFrame } from '../hooks/useAnimationFrame';\nimport { SHADER_CONFIG } from '../utils/constants';\n\n/**\n * ImageDistortion 컴포넌트 Props\n */\nexport interface ImageDistortionProps {\n /** 이미지 소스 URL */\n imageSrc: string;\n /** 왜곡 영역 배열 */\n areas: DistortionArea[];\n /** 버텍스 셰이더 경로 (선택사항) */\n vertexShaderPath?: string;\n /** 프래그먼트 셰이더 경로 (선택사항) */\n fragmentShaderPath?: string;\n /** 애니메이션 재생 여부 */\n isPlaying?: boolean;\n /** 컨테이너 스타일 */\n style?: React.CSSProperties;\n /** 컨테이너 클래스명 */\n className?: string;\n}\n\n/**\n * GPU 가속 이미지 왜곡 컴포넌트\n * Three.js와 GLSL 셰이더를 사용하여 실시간 이미지 왜곡 효과를 제공합니다.\n */\nexport const ImageDistortion: React.FC = ({\n imageSrc,\n areas,\n vertexShaderPath,\n fragmentShaderPath,\n isPlaying = true,\n style,\n className,\n}) => {\n const containerRef = useRef(null);\n const sceneRef = useRef(null);\n const shaderManagerRef = useRef(new ShaderManager());\n const textureRef = useRef(null);\n\n const [isReady, setIsReady] = useState(false);\n const [imageLoaded, setImageLoaded] = useState(false);\n const [currentAreas, setCurrentAreas] = useState(areas);\n\n // 영역 변경 시 상태 업데이트\n useEffect(() => {\n setCurrentAreas(areas);\n }, [areas]);\n\n // Three.js 씬 초기화\n useEffect(() => {\n console.log('[ImageDistortion] useEffect 실행, containerRef.current:', containerRef.current);\n\n if (!containerRef.current) {\n console.warn('[ImageDistortion] containerRef.current가 null입니다. 컴포넌트가 제대로 마운트되지 않았습니다.');\n return;\n }\n\n console.log('[ImageDistortion] 초기화 시작');\n const scene = new ThreeScene(containerRef.current);\n sceneRef.current = scene;\n\n // 셰이더 로드\n const vertPath = vertexShaderPath || '/shaders/distortion.vert.glsl';\n const fragPath = fragmentShaderPath || '/shaders/distortion.frag.glsl';\n\n console.log('[ImageDistortion] 셰이더 로드 시도:', { vertPath, fragPath });\n\n shaderManagerRef.current\n .loadShaders(vertPath, fragPath)\n .then(({ vertex, fragment }) => {\n console.log('[ImageDistortion] 셰이더 로드 성공');\n scene.setShaderMaterial(vertex, fragment);\n setIsReady(true);\n })\n .catch((error) => {\n console.error('[ImageDistortion] 셰이더 로드 실패:', error);\n });\n\n return () => {\n scene.dispose();\n if (textureRef.current) {\n textureRef.current.dispose();\n }\n };\n }, [vertexShaderPath, fragmentShaderPath]);\n\n // 이미지 텍스처 로드\n useEffect(() => {\n if (!imageSrc || !isReady) {\n console.log('[ImageDistortion] 이미지 로드 스킵:', { imageSrc, isReady });\n return;\n }\n\n console.log('[ImageDistortion] 이미지 로드 시작:', imageSrc);\n setImageLoaded(false);\n\n const loader = new THREE.TextureLoader();\n loader.load(\n imageSrc,\n (texture) => {\n console.log('[ImageDistortion] 이미지 로드 성공!', {\n width: texture.image.width,\n height: texture.image.height\n });\n textureRef.current = texture;\n setImageLoaded(true);\n if (sceneRef.current) {\n sceneRef.current.updateUniforms({\n u_texture: { value: texture },\n });\n sceneRef.current.render();\n console.log('[ImageDistortion] 텍스처 업데이트 및 렌더링 완료');\n }\n },\n (progress) => {\n console.log('[ImageDistortion] 이미지 로딩 중...',\n Math.round((progress.loaded / progress.total) * 100) + '%'\n );\n },\n (error) => {\n console.error('[ImageDistortion] 이미지 로드 실패:', error);\n setImageLoaded(false);\n }\n );\n\n return () => {\n if (textureRef.current) {\n textureRef.current.dispose();\n textureRef.current = null;\n }\n };\n }, [imageSrc, isReady]);\n\n // 셰이더 유니폼 업데이트\n useEffect(() => {\n if (!sceneRef.current || !isReady) return;\n\n // 현재 해상도 가져오기\n const resolution = sceneRef.current.getResolution();\n\n // 포인트 배열 생성\n // UI는 좌상단 (0,0), WebGL은 좌하단 (0,0)이므로 y 좌표를 반전\n const points = new Float32Array(SHADER_CONFIG.MAX_POINTS * 2);\n currentAreas.forEach((area, areaIndex) => {\n area.basePoints.forEach((point, pointIndex) => {\n const index = (areaIndex * 4 + pointIndex) * 2;\n points[index] = point.x;\n points[index + 1] = 1.0 - point.y; // y 좌표 반전\n });\n });\n\n // 드래그 벡터 배열 생성\n // dragVector도 y 좌표계를 맞춰야 하므로 y를 반전\n const dragVectors = new Float32Array(SHADER_CONFIG.MAX_DRAG_VECTORS * 2);\n currentAreas.forEach((area, index) => {\n const baseIndex = index * 2;\n dragVectors[baseIndex] = area.dragVector.x;\n dragVectors[baseIndex + 1] = -area.dragVector.y; // y 방향 반전\n });\n\n // 강도 배열 생성\n const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS);\n currentAreas.forEach((area, index) => {\n strengths[index] = area.distortionStrength;\n });\n\n sceneRef.current.updateUniforms({\n u_numAreas: { value: currentAreas.length },\n u_points: { value: points },\n u_dragVectors: { value: dragVectors },\n u_distortionStrengths: { value: strengths },\n });\n\n sceneRef.current.render();\n }, [currentAreas, isReady]);\n\n // 애니메이션 루프\n const animationCallback = useCallback((deltaTime: number) => {\n if (!isReady) return;\n\n setCurrentAreas((prevAreas) => {\n // 진행도 업데이트\n const updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);\n // 드래그 벡터 업데이트\n return AnimationLoop.updateAreaDragVectors(updatedAreas);\n });\n }, [isReady]);\n\n useAnimationFrame(animationCallback, isPlaying);\n\n return (\n \n {!imageLoaded && (\n \n 이미지 로딩 중...\n \n )}\n \n );\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 console.log('[ThreeScene] render() 호출됨, mesh:', this.mesh);\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';\n\ntype EasingFunc = (t: number) => number;\n\n/**\n * 이징 함수 구현 맵\n */\nconst easingFunctions: Record = {\n linear: (t) => t,\n\n easeIn: (t) => t * t,\n easeOut: (t) => t * (2 - t),\n easeInOut: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),\n\n easeInQuad: (t) => t * t,\n easeOutQuad: (t) => t * (2 - t),\n};\n\n/**\n * 진행도에 이징 함수를 적용\n * @param progress 진행도 (0.0 - 1.0)\n * @param easingType 적용할 이징 함수 타입\n * @returns 이징이 적용된 진행도 (0.0 - 1.0)\n */\nexport const applyEasing = (\n progress: number,\n easingType: EasingFunction\n): number => {\n const clampedProgress = Math.max(0, Math.min(1, progress));\n return easingFunctions[easingType](clampedProgress);\n};","import { applyEasing } from '../utils/easing';\nimport type {DistortionArea, Point} from \"../types\";\n\n/**\n * 애니메이션 루프 관리 클래스\n */\nexport class AnimationLoop {\n /**\n * 영역들의 드래그 벡터를 현재 진행도에 따라 업데이트\n * @param areas 왜곡 영역 배열\n * @returns 업데이트된 영역 배열\n */\n public static updateAreaDragVectors(\n areas: DistortionArea[]\n ): DistortionArea[] {\n return areas.map((area) => {\n const { progress, movement } = area;\n\n // 이징 적용\n const easedProgress = applyEasing(progress, movement.easing);\n\n // 벡터 간 보간\n let dragVector: Point;\n\n if (easedProgress < 0.5) {\n // 0.0 -> 0.5: 0에서 vectorA로 보간\n const t = easedProgress * 2;\n dragVector = {\n x: movement.vectorA.x * t,\n y: movement.vectorA.y * t,\n };\n } else {\n // 0.5 -> 1.0: vectorA에서 0으로 보간\n const t = (easedProgress - 0.5) * 2;\n dragVector = {\n x: movement.vectorA.x * (1 - t),\n y: movement.vectorA.y * (1 - t),\n };\n }\n\n return {\n ...area,\n dragVector,\n };\n });\n }\n\n /**\n * 모든 영역의 진행도를 델타 타임만큼 업데이트\n * @param areas 왜곡 영역 배열\n * @param deltaTime 델타 타임 (초)\n * @returns 업데이트된 영역 배열\n */\n public static updateProgress(\n areas: DistortionArea[],\n deltaTime: number\n ): DistortionArea[] {\n return areas.map((area) => {\n let newProgress = area.progress + deltaTime / area.movement.duration;\n newProgress %= 1.0; // 루프\n\n return {\n ...area,\n progress: newProgress,\n };\n });\n }\n}","import { useEffect, useRef } from 'react';\n\n/**\n * requestAnimationFrame을 사용한 애니메이션 루프 훅\n * @param callback 매 프레임마다 호출될 콜백 (deltaTime을 인자로 받음)\n * @param isPlaying 애니메이션 재생 여부\n */\nexport const useAnimationFrame = (\n callback: (deltaTime: number) => void,\n isPlaying: boolean = true\n) => {\n const requestRef = useRef(undefined);\n const previousTimeRef = useRef(undefined);\n\n useEffect(() => {\n if (!isPlaying) return;\n\n const animate = (time: number) => {\n if (previousTimeRef.current !== undefined) {\n const deltaTime = (time - previousTimeRef.current) / 1000; // 밀리초를 초로 변환\n callback(deltaTime);\n }\n previousTimeRef.current = time;\n requestRef.current = requestAnimationFrame(animate);\n };\n\n requestRef.current = requestAnimationFrame(animate);\n\n return () => {\n if (requestRef.current) {\n cancelAnimationFrame(requestRef.current);\n }\n };\n }, [callback, isPlaying]);\n};","/**\n * 셰이더 관련 설정\n */\nexport const SHADER_CONFIG = {\n /** 최대 영역 개수 */\n MAX_AREAS: 8,\n /** 최대 포인트 개수 (8영역 × 4포인트) */\n MAX_POINTS: 32,\n /** 최대 드래그 벡터 개수 */\n MAX_DRAG_VECTORS: 8,\n /** 최대 강도 배열 크기 */\n MAX_STRENGTHS: 8,\n} as const;\n\n/**\n * 애니메이션 관련 설정\n */\nexport const ANIMATION_CONFIG = {\n /** 목표 FPS */\n TARGET_FPS: 60,\n /** 델타 타임 (약 16.67ms) */\n DELTA_TIME: 1 / 60,\n} as const;\n\n/**\n * 기본 영역 설정값\n */\nexport const DEFAULT_AREA = {\n /** 기본 왜곡 강도 */\n DISTORTION_STRENGTH: 0.5,\n /** 기본 애니메이션 지속 시간 (초) */\n DURATION: 2.0,\n /** 기본 이징 함수 */\n EASING: 'easeInOut' as const,\n /** 기본 벡터 A */\n VECTOR_A: { x: 0.1, y: 0.1 },\n /** 기본 벡터 B */\n VECTOR_B: { x: -0.1, y: -0.1 },\n} as const;","import React, { useEffect } from 'react';\nimport { DistortionArea } from '../types/area';\nimport { DistortionEditorProps } from './types';\nimport { useDistortionEditor } from './hooks/useDistortionEditor';\nimport { EditorCanvas } from './components/EditorCanvas';\nimport { AreaList } from './components/AreaList';\nimport { ParameterPanel } from './components/ParameterPanel';\nimport { DEFAULT_AREA } from '../utils/constants';\n\nexport const DistortionEditor: React.FC = ({\n\tinitialAreas = [],\n\timageSrc,\n\tonAreasChange,\n\tonSelectedAreaChange,\n\twidth = 800,\n\theight = 600,\n}) => {\n\tconst {\n\t\tstate,\n\t\tselectArea,\n\t\taddArea,\n\t\tremoveArea,\n\t\tupdateArea,\n\t\tupdatePoint,\n\t\tstartDragging,\n\t\tstopDragging,\n\t\tgetSelectedArea,\n\t} = useDistortionEditor(initialAreas);\n\n\t// 영역 변경 시 콜백 호출\n\tuseEffect(() => {\n\t\tonAreasChange?.(state.areas);\n\t}, [state.areas, onAreasChange]);\n\n\t// 선택된 영역 변경 시 콜백 호출\n\tuseEffect(() => {\n\t\tonSelectedAreaChange?.(state.selectedAreaId);\n\t}, [state.selectedAreaId, onSelectedAreaChange]);\n\n\t// 새 영역 추가 핸들러\n\tconst handleAddArea = () => {\n\t\tconst newArea: DistortionArea = {\n\t\t\tid: `area-${Date.now()}`,\n\t\t\tbasePoints: [\n\t\t\t\t{ x: 0.3, y: 0.3 },\n\t\t\t\t{ x: 0.7, y: 0.3 },\n\t\t\t\t{ x: 0.7, y: 0.7 },\n\t\t\t\t{ x: 0.3, y: 0.7 },\n\t\t\t],\n\t\t\tmovement: {\n\t\t\t\tvectorA: { x: DEFAULT_AREA.VECTOR_A.x, y: DEFAULT_AREA.VECTOR_A.y },\n\t\t\t\tvectorB: { x: DEFAULT_AREA.VECTOR_B.x, y: DEFAULT_AREA.VECTOR_B.y },\n\t\t\t\tduration: DEFAULT_AREA.DURATION,\n\t\t\t\teasing: DEFAULT_AREA.EASING as any,\n\t\t\t},\n\t\t\tdistortionStrength: DEFAULT_AREA.DISTORTION_STRENGTH,\n\t\t\tprogress: 0,\n\t\t\tdragVector: { x: 0, y: 0 },\n\t\t};\n\t\taddArea(newArea);\n\t};\n\n\t// 파라미터 업데이트 핸들러\n\tconst handleUpdateArea = (updates: Partial) => {\n\t\tif (state.selectedAreaId) {\n\t\t\tupdateArea(state.selectedAreaId, updates);\n\t\t}\n\t};\n\n\tconst selectedArea = getSelectedArea();\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t{/* 왼쪽: 캔버스 */}\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t
\n\n\t\t\t\t{/* 오른쪽: 사이드바 */}\n\t\t\t\t
\n\t\t\t\t\t{/* 영역 목록 */}\n\t\t\t\t\t\n\n\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';\nimport { DistortionArea, Point } from '../../types/area';\nimport { EditorState } from '../types';\n\nexport const useDistortionEditor = (initialAreas: DistortionArea[] = []) => {\n\tconst [state, setState] = useState({\n\t\tselectedAreaId: initialAreas[0]?.id || null,\n\t\tareas: initialAreas,\n\t\teditMode: 'normal',\n\t\tdraggingPointIndex: null,\n\t});\n\n\t/** 영역 선택 */\n\tconst selectArea = useCallback((areaId: string | null) => {\n\t\tsetState((prev) => ({ ...prev, selectedAreaId: areaId }));\n\t}, []);\n\n\t/** 영역 추가 */\n\tconst addArea = useCallback((area: DistortionArea) => {\n\t\tsetState((prev) => ({\n\t\t\t...prev,\n\t\t\tareas: [...prev.areas, area],\n\t\t\tselectedAreaId: area.id,\n\t\t}));\n\t}, []);\n\n\t/** 영역 삭제 */\n\tconst removeArea = useCallback((areaId: string) => {\n\t\tsetState((prev) => {\n\t\t\tconst newAreas = prev.areas.filter((a) => a.id !== areaId);\n\t\t\treturn {\n\t\t\t\t...prev,\n\t\t\t\tareas: newAreas,\n\t\t\t\tselectedAreaId:\n\t\t\t\t\tprev.selectedAreaId === areaId ? newAreas[0]?.id || null : prev.selectedAreaId,\n\t\t\t};\n\t\t});\n\t}, []);\n\n\t/** 영역 업데이트 */\n\tconst updateArea = useCallback((areaId: string, updates: Partial) => {\n\t\tsetState((prev) => ({\n\t\t\t...prev,\n\t\t\tareas: prev.areas.map((area) => (area.id === areaId ? { ...area, ...updates } : area)),\n\t\t}));\n\t}, []);\n\n\t/** 포인트 업데이트 */\n\tconst updatePoint = useCallback((areaId: string, pointIndex: number, point: Point) => {\n\t\tsetState((prev) => ({\n\t\t\t...prev,\n\t\t\tareas: prev.areas.map((area) => {\n\t\t\t\tif (area.id === areaId) {\n\t\t\t\t\tconst newPoints = [...area.basePoints] as [Point, Point, Point, Point];\n\t\t\t\t\tnewPoints[pointIndex] = point;\n\t\t\t\t\treturn { ...area, basePoints: newPoints };\n\t\t\t\t}\n\t\t\t\treturn area;\n\t\t\t}),\n\t\t}));\n\t}, []);\n\n\t/** 드래그 시작 */\n\tconst startDragging = useCallback((pointIndex: number) => {\n\t\tsetState((prev) => ({ ...prev, draggingPointIndex: pointIndex }));\n\t}, []);\n\n\t/** 드래그 종료 */\n\tconst stopDragging = useCallback(() => {\n\t\tsetState((prev) => ({ ...prev, draggingPointIndex: null }));\n\t}, []);\n\n\t/** 편집 모드 변경 */\n\tconst setEditMode = useCallback((mode: EditorState['editMode']) => {\n\t\tsetState((prev) => ({ ...prev, editMode: mode }));\n\t}, []);\n\n\t/** 선택된 영역 가져오기 */\n\tconst getSelectedArea = useCallback(() => {\n\t\treturn state.areas.find((a) => a.id === state.selectedAreaId) || null;\n\t}, [state.areas, state.selectedAreaId]);\n\n\treturn {\n\t\tstate,\n\t\tselectArea,\n\t\taddArea,\n\t\tremoveArea,\n\t\tupdateArea,\n\t\tupdatePoint,\n\t\tstartDragging,\n\t\tstopDragging,\n\t\tsetEditMode,\n\t\tgetSelectedArea,\n\t};\n};\n","import React, {useRef, useEffect, useState, useCallback} from 'react';\nimport {DistortionArea, Point} from '../../types/area';\nimport {ImageDistortion} from '../../components/ImageDistortion';\n\ninterface EditorCanvasProps {\n\tareas: DistortionArea[];\n\tselectedAreaId: string | null;\n\timageSrc: string;\n\twidth: number;\n\theight: number;\n\tonUpdatePoint: (areaId: string, pointIndex: number, point: Point) => void;\n\tonUpdateArea: (areaId: string, updates: Partial) => void;\n\tdraggingPointIndex: number | null;\n\tonStartDragging: (pointIndex: number) => void;\n\tonStopDragging: () => void;\n}\n\nexport const EditorCanvas: React.FC = ({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t areas,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t selectedAreaId,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t imageSrc,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t width,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t height,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onUpdatePoint,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onUpdateArea,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t draggingPointIndex,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onStartDragging,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onStopDragging,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t }) => {\n\tconst containerRef = useRef(null);\n\tconst [canvasSize, setCanvasSize] = useState({width: 0, height: 0});\n\tconst [isDraggingArea, setIsDraggingArea] = useState(false);\n\tconst [dragStartPos, setDragStartPos] = useState(null);\n\n\t// 컨테이너 크기 측정\n\tuseEffect(() => {\n\t\tif (!containerRef.current) return;\n\t\tconst rect = containerRef.current.getBoundingClientRect();\n\t\tsetCanvasSize({width: rect.width, height: rect.height});\n\t}, [width, height]);\n\n\t// 선택된 영역 찾기\n\tconst selectedArea = areas.find((a) => a.id === selectedAreaId);\n\n\t// 점이 사각형 내부에 있는지 확인 (Point-in-Polygon test)\n\tconst isPointInPolygon = useCallback((point: Point, polygon: Point[]): boolean => {\n\t\tlet inside = false;\n\t\tfor (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\n\t\t\tconst xi = polygon[i].x, yi = polygon[i].y;\n\t\t\tconst xj = polygon[j].x, yj = polygon[j].y;\n\n\t\t\tconst intersect = ((yi > point.y) !== (yj > point.y))\n\t\t\t\t&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);\n\t\t\tif (intersect) inside = !inside;\n\t\t}\n\t\treturn inside;\n\t}, []);\n\n\t// 마우스 이벤트 핸들러\n\tconst handleMouseDown = useCallback(\n\t\t(pointIndex: number) => (e: React.MouseEvent) => {\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t\tonStartDragging(pointIndex);\n\t\t},\n\t\t[onStartDragging]\n\t);\n\n\t// 캔버스 클릭 (사각형 내부 클릭 감지)\n\tconst handleCanvasMouseDown = useCallback(\n\t\t(e: React.MouseEvent) => {\n\t\t\tif (!selectedArea || !containerRef.current) return;\n\n\t\t\tconst rect = containerRef.current.getBoundingClientRect();\n\t\t\tconst x = (e.clientX - rect.left) / rect.width;\n\t\t\tconst y = (e.clientY - rect.top) / rect.height;\n\t\t\tconst clickPoint = { x, y };\n\n\t\t\t// 사각형 내부를 클릭했는지 확인\n\t\t\tif (isPointInPolygon(clickPoint, selectedArea.basePoints)) {\n\t\t\t\tsetIsDraggingArea(true);\n\t\t\t\tsetDragStartPos(clickPoint);\n\t\t\t\te.preventDefault();\n\t\t\t}\n\t\t},\n\t\t[selectedArea, isPointInPolygon]\n\t);\n\n\tconst handleMouseMove = useCallback(\n\t\t(e: React.MouseEvent) => {\n\t\t\tif (!selectedArea || !containerRef.current) return;\n\n\t\t\tconst rect = containerRef.current.getBoundingClientRect();\n\t\t\tconst x = (e.clientX - rect.left) / rect.width;\n\t\t\tconst y = (e.clientY - rect.top) / rect.height;\n\n\t\t\t// 포인트 드래그 중\n\t\t\tif (draggingPointIndex !== null) {\n\t\t\t\tconst clampedX = Math.max(0, Math.min(1, x));\n\t\t\t\tconst clampedY = Math.max(0, Math.min(1, y));\n\t\t\t\tonUpdatePoint(selectedArea.id, draggingPointIndex, {x: clampedX, y: clampedY});\n\t\t\t}\n\t\t\t// 사각형 전체 드래그 중\n\t\t\telse if (isDraggingArea && dragStartPos) {\n\t\t\t\tconst deltaX = x - dragStartPos.x;\n\t\t\t\tconst deltaY = y - dragStartPos.y;\n\n\t\t\t\t// 모든 포인트를 delta만큼 이동\n\t\t\t\tconst newPoints = selectedArea.basePoints.map((point) => ({\n\t\t\t\t\tx: Math.max(0, Math.min(1, point.x + deltaX)),\n\t\t\t\t\ty: Math.max(0, Math.min(1, point.y + deltaY)),\n\t\t\t\t})) as [Point, Point, Point, Point];\n\n\t\t\t\tonUpdateArea(selectedArea.id, { basePoints: newPoints });\n\t\t\t\tsetDragStartPos({ x, y }); // 다음 프레임을 위해 시작 위치 업데이트\n\t\t\t}\n\t\t},\n\t\t[draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]\n\t);\n\n\tconst handleMouseUp = useCallback(() => {\n\t\tif (draggingPointIndex !== null) {\n\t\t\tonStopDragging();\n\t\t}\n\t\tif (isDraggingArea) {\n\t\t\tsetIsDraggingArea(false);\n\t\t\tsetDragStartPos(null);\n\t\t}\n\t}, [draggingPointIndex, isDraggingArea, onStopDragging]);\n\n\t// 전역 마우스 업 이벤트\n\tuseEffect(() => {\n\t\tif (draggingPointIndex !== null || isDraggingArea) {\n\t\t\twindow.addEventListener('mouseup', handleMouseUp);\n\t\t\treturn () => window.removeEventListener('mouseup', handleMouseUp);\n\t\t}\n\t}, [draggingPointIndex, isDraggingArea, handleMouseUp]);\n\n\t// UV 좌표를 픽셀 좌표로 변환 (셰이더와 동일한 bilinear interpolation)\n\tconst uvToPixel = (\n\t\tu: number,\n\t\tv: number,\n\t\tpoints: [Point, Point, Point, Point],\n\t\tcanvasWidth: number,\n\t\tcanvasHeight: number\n\t): { x: number; y: number } => {\n\t\t// p0=좌상, p1=우상, p2=우하, p3=좌하\n\t\tconst [p0, p1, p2, p3] = points;\n\n\t\t// 셰이더 computeUV와 동일한 순서로 bilinear interpolation\n\t\t// left = mix(p0, p1, u) -> 상단 가장자리\n\t\t// right = mix(p3, p2, u) -> 하단 가장자리\n\t\t// position = mix(left, right, v)\n\t\tconst leftX = p0.x * (1 - u) + p1.x * u;\n\t\tconst leftY = p0.y * (1 - u) + p1.y * u;\n\n\t\tconst rightX = p3.x * (1 - u) + p2.x * u;\n\t\tconst rightY = p3.y * (1 - u) + p2.y * u;\n\n\t\tconst posX = leftX * (1 - v) + rightX * v;\n\t\tconst posY = leftY * (1 - v) + rightY * v;\n\n\t\treturn {\n\t\t\tx: posX * canvasWidth,\n\t\t\ty: posY * canvasHeight,\n\t\t};\n\t};\n\n\t// UV 좌표계의 원을 정확히 그리기 (찌그러진 원 형태)\n\tconst drawDistortionCircle = (\n\t\tctx: CanvasRenderingContext2D,\n\t\tpoints: [Point, Point, Point, Point],\n\t\tcanvasWidth: number,\n\t\tcanvasHeight: number\n\t) => {\n\t\tconst segments = 128; // 원을 128개 세그먼트로 촘촘히 분할\n\t\tconst centerU = 0.5;\n\t\tconst centerV = 0.5;\n\t\tconst maxRadius = 0.5; // UV 좌표계에서 최대 반지름 0.5 (셰이더의 maxUvRadius)\n\n\t\t// 원 위의 점들을 UV 좌표로 샘플링 후 픽셀 좌표로 변환\n\t\t// 4가지 조합을 모두 테스트 (사용자가 이미지에서 P1-P3 대각선으로 늘렸을 때 왜곡도 같은 방향이어야 함)\n\t\tconst circlePoints: { x: number; y: number }[] = [];\n\t\tfor (let i = 0; i <= segments; i++) {\n\t\t\tconst theta = (i / segments) * 2 * Math.PI;\n\n\t\t\t// 테스트: u=-sin, v=cos (-90도 회전, P1-P3 방향에 맞춤)\n\t\t\tconst u = centerU - maxRadius * Math.sin(theta);\n\t\t\tconst v = centerV + maxRadius * Math.cos(theta);\n\n\t\t\tconst pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);\n\t\t\tcirclePoints.push(pixelPos);\n\t\t}\n\n\t\t// 찌그러진 원 그리기 (실제 왜곡 적용 경계)\n\t\tctx.beginPath();\n\t\tctx.moveTo(circlePoints[0].x, circlePoints[0].y);\n\t\tfor (let i = 1; i < circlePoints.length; i++) {\n\t\t\tctx.lineTo(circlePoints[i].x, circlePoints[i].y);\n\t\t}\n\t\tctx.closePath();\n\t\tctx.strokeStyle = 'rgba(255, 200, 0, 0.9)';\n\t\tctx.lineWidth = 3;\n\t\tctx.setLineDash([8, 4]);\n\t\tctx.stroke();\n\t\tctx.setLineDash([]);\n\n\t\t// 내부를 반투명하게 채우기\n\t\tctx.fillStyle = 'rgba(255, 200, 0, 0.12)';\n\t\tctx.fill();\n\n\t\t// 영향력 그라디언트를 나타내는 추가 원들 (0.25, 0.375 반지름)\n\t\tfor (const r of [0.25, 0.375]) {\n\t\t\tconst gradientPoints: { x: number; y: number }[] = [];\n\t\t\tfor (let i = 0; i <= segments; i++) {\n\t\t\t\tconst theta = (i / segments) * 2 * Math.PI;\n\t\t\t\tconst u = centerU - r * Math.sin(theta);\n\t\t\t\tconst v = centerV + r * Math.cos(theta);\n\t\t\t\tconst pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);\n\t\t\t\tgradientPoints.push(pixelPos);\n\t\t\t}\n\n\t\t\tctx.beginPath();\n\t\t\tctx.moveTo(gradientPoints[0].x, gradientPoints[0].y);\n\t\t\tfor (let i = 1; i < gradientPoints.length; i++) {\n\t\t\t\tctx.lineTo(gradientPoints[i].x, gradientPoints[i].y);\n\t\t\t}\n\t\t\tctx.closePath();\n\t\t\tconst alpha = r / maxRadius;\n\t\t\tctx.strokeStyle = `rgba(255, 220, 100, ${0.3 * alpha})`;\n\t\t\tctx.lineWidth = 1;\n\t\t\tctx.setLineDash([3, 3]);\n\t\t\tctx.stroke();\n\t\t\tctx.setLineDash([]);\n\t\t}\n\n\t\t// 중심점 표시\n\t\tconst centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight);\n\t\tctx.beginPath();\n\t\tctx.arc(centerPixel.x, centerPixel.y, 5, 0, 2 * Math.PI);\n\t\tctx.fillStyle = 'rgba(255, 200, 0, 1)';\n\t\tctx.fill();\n\t\tctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';\n\t\tctx.lineWidth = 2;\n\t\tctx.stroke();\n\t};\n\n\t// 커서 스타일 결정\n\tconst getCursorStyle = () => {\n\t\tif (draggingPointIndex !== null) return 'grabbing';\n\t\tif (isDraggingArea) return 'grabbing';\n\t\treturn 'default';\n\t};\n\n\treturn (\n\t\t\n\t\t\t{/* ImageDistortion 컴포넌트 */}\n\t\t\t\n\n\t\t\t{/* 오버레이 SVG */}\n\t\t\t\n\t\t\t\t{/* 모든 영역의 사각형 표시 */}\n\t\t\t\t{areas.map((area) => {\n\t\t\t\t\tconst isSelected = area.id === selectedAreaId;\n\t\t\t\t\tconst points = area.basePoints;\n\t\t\t\t\treturn (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{/* 사각형 경계선 */}\n\t\t\t\t\t\t\t `${p.x * canvasSize.width},${p.y * canvasSize.height}`)\n\t\t\t\t\t\t\t\t\t.join(' ')}\n\t\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t\tstroke={isSelected ? '#00aaff' : '#888'}\n\t\t\t\t\t\t\t\tstrokeWidth={isSelected ? 2 : 1}\n\t\t\t\t\t\t\t\tstrokeDasharray={isSelected ? '0' : '5,5'}\n\t\t\t\t\t\t\t\topacity={isSelected ? 1 : 0.5}\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\n\t\t\t{/* 선택된 영역의 타원 가이드 (Canvas로 그리기) */}\n\t\t\t{selectedArea && canvasSize.width > 0 && (\n\t\t\t\t {\n\t\t\t\t\t\tif (canvas) {\n\t\t\t\t\t\t\tconst ctx = canvas.getContext('2d');\n\t\t\t\t\t\t\tif (ctx) {\n\t\t\t\t\t\t\t\tctx.clearRect(0, 0, canvasSize.width, canvasSize.height);\n\t\t\t\t\t\t\t\tdrawDistortionCircle(ctx, selectedArea.basePoints, canvasSize.width, canvasSize.height);\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\n\t\t\t{/* 선택된 영역의 포인트 핸들 */}\n\t\t\t{selectedArea &&\n\t\t\t\tselectedArea.basePoints.map((point, index) => (\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\tP{index + 1}\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t))}\n\t\t\n\t);\n};\n","import React from 'react';\nimport { DistortionArea } from '../../types/area';\n\ninterface 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\ninterface 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{/* 벡터 A (X) */}\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: {\n\t\t\t\t\t\t\t\t...area.movement,\n\t\t\t\t\t\t\t\tvectorA: { ...area.movement.vectorA, x: parseFloat(e.target.value) },\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\tclassName=\"slider\"\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{/* 벡터 A (Y) */}\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: {\n\t\t\t\t\t\t\t\t...area.movement,\n\t\t\t\t\t\t\t\tvectorA: { ...area.movement.vectorA, y: parseFloat(e.target.value) },\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\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{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"],"mappings":"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","names":["import_react","THREE","import_react","import_react","import_react","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime"]} \ No newline at end of file +{"version":3,"sources":["../src/index.ts","../src/components/ImageDistortion.tsx","../src/engine/ThreeScene.ts","../src/engine/ShaderManager.ts","../src/utils/easing.ts","../src/engine/AnimationLoop.ts","../src/hooks/useAnimationFrame.ts","../src/utils/constants.ts","../src/editor/DistortionEditor.tsx","../src/editor/hooks/useDistortionEditor.ts","../src/editor/components/EditorCanvas.tsx","../src/editor/constants.ts","../src/editor/components/AreaList.tsx","../src/editor/components/ParameterPanel.tsx"],"sourcesContent":["// 메인 컴포넌트\nexport { ImageDistortion } from './components/ImageDistortion';\nexport type { ImageDistortionProps } from './components/ImageDistortion';\n\n// 에디터 (4점 사각형 + 정확한 UV 좌표계 원형 왜곡 가이드)\nexport { DistortionEditor } from './editor';\nexport type { DistortionEditorProps, EditorState, EditMode } from './editor';\nexport { useDistortionEditor } from './editor';\n\n// 타입 정의\nexport type {\n Point,\n EasingFunction,\n DistortionMovement,\n DistortionArea,\n AreaBounds,\n ShaderUniforms,\n ShaderConfig,\n AnimationState,\n AnimationTicker,\n} from './types';\n\n// 유틸리티 함수\nexport { applyEasing } from './utils/easing';\nexport { SHADER_CONFIG, ANIMATION_CONFIG, DEFAULT_AREA } from './utils/constants';\n\n// 엔진 클래스 (고급 사용자용)\nexport { ThreeScene } from './engine/ThreeScene';\nexport { ShaderManager } from './engine/ShaderManager';\nexport { AnimationLoop } from './engine/AnimationLoop';\n\n// 훅\nexport { useAnimationFrame } from './hooks/useAnimationFrame';","import React, { useEffect, useRef, useState, useCallback } from 'react';\nimport * as THREE from 'three';\nimport { type DistortionArea } from '../types';\nimport { ThreeScene } from '../engine/ThreeScene';\nimport { ShaderManager } from '../engine/ShaderManager';\nimport { AnimationLoop } from '../engine/AnimationLoop';\nimport { useAnimationFrame } from '../hooks/useAnimationFrame';\nimport { SHADER_CONFIG } from '../utils/constants';\n\n/**\n * ImageDistortion 컴포넌트 Props\n */\nexport interface ImageDistortionProps {\n /** 이미지 소스 URL */\n imageSrc: string;\n /** 왜곡 영역 배열 */\n areas: DistortionArea[];\n /** 버텍스 셰이더 경로 (선택사항) */\n vertexShaderPath?: string;\n /** 프래그먼트 셰이더 경로 (선택사항) */\n fragmentShaderPath?: string;\n /** 애니메이션 재생 여부 */\n isPlaying?: boolean;\n /** 컨테이너 스타일 */\n style?: React.CSSProperties;\n /** 컨테이너 클래스명 */\n className?: string;\n}\n\n/**\n * GPU 가속 이미지 왜곡 컴포넌트\n * Three.js와 GLSL 셰이더를 사용하여 실시간 이미지 왜곡 효과를 제공합니다.\n */\nexport const ImageDistortion: React.FC = ({\n imageSrc,\n areas,\n vertexShaderPath,\n fragmentShaderPath,\n isPlaying = true,\n style,\n className,\n}) => {\n const containerRef = useRef(null);\n const sceneRef = useRef(null);\n const shaderManagerRef = useRef(new ShaderManager());\n const textureRef = useRef(null);\n\n const [isReady, setIsReady] = useState(false);\n const [imageLoaded, setImageLoaded] = useState(false);\n const [currentAreas, setCurrentAreas] = useState(areas);\n\n // 영역 변경 시 상태 업데이트\n useEffect(() => {\n setCurrentAreas(areas);\n }, [areas]);\n\n // Three.js 씬 초기화\n useEffect(() => {\n console.log('[ImageDistortion] useEffect 실행, containerRef.current:', containerRef.current);\n\n if (!containerRef.current) {\n console.warn('[ImageDistortion] containerRef.current가 null입니다. 컴포넌트가 제대로 마운트되지 않았습니다.');\n return;\n }\n\n console.log('[ImageDistortion] 초기화 시작');\n const scene = new ThreeScene(containerRef.current);\n sceneRef.current = scene;\n\n // 셰이더 로드\n const vertPath = vertexShaderPath || '/shaders/distortion.vert.glsl';\n const fragPath = fragmentShaderPath || '/shaders/distortion.frag.glsl';\n\n console.log('[ImageDistortion] 셰이더 로드 시도:', { vertPath, fragPath });\n\n shaderManagerRef.current\n .loadShaders(vertPath, fragPath)\n .then(({ vertex, fragment }) => {\n console.log('[ImageDistortion] 셰이더 로드 성공');\n scene.setShaderMaterial(vertex, fragment);\n setIsReady(true);\n })\n .catch((error) => {\n console.error('[ImageDistortion] 셰이더 로드 실패:', error);\n });\n\n return () => {\n scene.dispose();\n if (textureRef.current) {\n textureRef.current.dispose();\n }\n };\n }, [vertexShaderPath, fragmentShaderPath]);\n\n // 이미지 텍스처 로드\n useEffect(() => {\n if (!imageSrc || !isReady) {\n console.log('[ImageDistortion] 이미지 로드 스킵:', { imageSrc, isReady });\n return;\n }\n\n console.log('[ImageDistortion] 이미지 로드 시작:', imageSrc);\n setImageLoaded(false);\n\n const loader = new THREE.TextureLoader();\n loader.load(\n imageSrc,\n (texture) => {\n console.log('[ImageDistortion] 이미지 로드 성공!', {\n width: texture.image.width,\n height: texture.image.height\n });\n textureRef.current = texture;\n setImageLoaded(true);\n if (sceneRef.current) {\n sceneRef.current.updateUniforms({\n u_texture: { value: texture },\n });\n sceneRef.current.render();\n console.log('[ImageDistortion] 텍스처 업데이트 및 렌더링 완료');\n }\n },\n (progress) => {\n console.log('[ImageDistortion] 이미지 로딩 중...',\n Math.round((progress.loaded / progress.total) * 100) + '%'\n );\n },\n (error) => {\n console.error('[ImageDistortion] 이미지 로드 실패:', error);\n setImageLoaded(false);\n }\n );\n\n return () => {\n if (textureRef.current) {\n textureRef.current.dispose();\n textureRef.current = null;\n }\n };\n }, [imageSrc, isReady]);\n\n // 셰이더 유니폼 업데이트\n useEffect(() => {\n if (!sceneRef.current || !isReady) return;\n\n // 현재 해상도 가져오기\n const resolution = sceneRef.current.getResolution();\n\n // 포인트 배열 생성\n // UI는 좌상단 (0,0), WebGL은 좌하단 (0,0)이므로 y 좌표를 반전\n const points = new Float32Array(SHADER_CONFIG.MAX_POINTS * 2);\n currentAreas.forEach((area, areaIndex) => {\n area.basePoints.forEach((point, pointIndex) => {\n const index = (areaIndex * 4 + pointIndex) * 2;\n points[index] = point.x;\n points[index + 1] = 1.0 - point.y; // y 좌표 반전\n });\n });\n\n // 드래그 벡터 배열 생성\n // dragVector도 y 좌표계를 맞춰야 하므로 y를 반전\n const dragVectors = new Float32Array(SHADER_CONFIG.MAX_DRAG_VECTORS * 2);\n currentAreas.forEach((area, index) => {\n const baseIndex = index * 2;\n dragVectors[baseIndex] = area.dragVector.x;\n dragVectors[baseIndex + 1] = -area.dragVector.y; // y 방향 반전\n });\n\n // 강도 배열 생성\n const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS);\n currentAreas.forEach((area, index) => {\n strengths[index] = area.distortionStrength;\n });\n\n sceneRef.current.updateUniforms({\n u_numAreas: { value: currentAreas.length },\n u_points: { value: points },\n u_dragVectors: { value: dragVectors },\n u_distortionStrengths: { value: strengths },\n });\n\n sceneRef.current.render();\n }, [currentAreas, isReady]);\n\n // 애니메이션 루프\n const animationCallback = useCallback((deltaTime: number) => {\n if (!isReady) return;\n\n setCurrentAreas((prevAreas) => {\n // 진행도 업데이트\n const updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);\n // 드래그 벡터 업데이트\n return AnimationLoop.updateAreaDragVectors(updatedAreas);\n });\n }, [isReady]);\n\n useAnimationFrame(animationCallback, isPlaying);\n\n return (\n \n {!imageLoaded && (\n \n 이미지 로딩 중...\n \n )}\n \n );\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 console.log('[ThreeScene] render() 호출됨, mesh:', this.mesh);\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';\n\ntype EasingFunc = (t: number) => number;\n\n/**\n * 이징 함수 구현 맵\n */\nconst easingFunctions: Record = {\n linear: (t) => t,\n\n easeIn: (t) => t * t,\n easeOut: (t) => t * (2 - t),\n easeInOut: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),\n\n easeInQuad: (t) => t * t,\n easeOutQuad: (t) => t * (2 - t),\n};\n\n/**\n * 진행도에 이징 함수를 적용\n * @param progress 진행도 (0.0 - 1.0)\n * @param easingType 적용할 이징 함수 타입\n * @returns 이징이 적용된 진행도 (0.0 - 1.0)\n */\nexport const applyEasing = (\n progress: number,\n easingType: EasingFunction\n): number => {\n const clampedProgress = Math.max(0, Math.min(1, progress));\n return easingFunctions[easingType](clampedProgress);\n};","import { applyEasing } from '../utils/easing';\nimport type {DistortionArea, Point} from \"../types\";\n\n/**\n * 애니메이션 루프 관리 클래스\n */\nexport class AnimationLoop {\n /**\n * 영역들의 드래그 벡터를 현재 진행도에 따라 업데이트\n * @param areas 왜곡 영역 배열\n * @returns 업데이트된 영역 배열\n */\n public static updateAreaDragVectors(\n areas: DistortionArea[]\n ): DistortionArea[] {\n return areas.map((area) => {\n const { progress, movement } = area;\n\n // 이징 적용\n const easedProgress = applyEasing(progress, movement.easing);\n\n // 벡터 간 보간\n let dragVector: Point;\n\n if (easedProgress < 0.5) {\n // 0.0 -> 0.5: 0에서 vectorA로 보간\n const t = easedProgress * 2;\n dragVector = {\n x: movement.vectorA.x * t,\n y: movement.vectorA.y * t,\n };\n } else {\n // 0.5 -> 1.0: vectorA에서 0으로 보간\n const t = (easedProgress - 0.5) * 2;\n dragVector = {\n x: movement.vectorA.x * (1 - t),\n y: movement.vectorA.y * (1 - t),\n };\n }\n\n return {\n ...area,\n dragVector,\n };\n });\n }\n\n /**\n * 모든 영역의 진행도를 델타 타임만큼 업데이트\n * @param areas 왜곡 영역 배열\n * @param deltaTime 델타 타임 (초)\n * @returns 업데이트된 영역 배열\n */\n public static updateProgress(\n areas: DistortionArea[],\n deltaTime: number\n ): DistortionArea[] {\n return areas.map((area) => {\n let newProgress = area.progress + deltaTime / area.movement.duration;\n newProgress %= 1.0; // 루프\n\n return {\n ...area,\n progress: newProgress,\n };\n });\n }\n}","import { useEffect, useRef } from 'react';\n\n/**\n * requestAnimationFrame을 사용한 애니메이션 루프 훅\n * @param callback 매 프레임마다 호출될 콜백 (deltaTime을 인자로 받음)\n * @param isPlaying 애니메이션 재생 여부\n */\nexport const useAnimationFrame = (\n callback: (deltaTime: number) => void,\n isPlaying: boolean = true\n) => {\n const requestRef = useRef(undefined);\n const previousTimeRef = useRef(undefined);\n\n useEffect(() => {\n if (!isPlaying) return;\n\n const animate = (time: number) => {\n if (previousTimeRef.current !== undefined) {\n const deltaTime = (time - previousTimeRef.current) / 1000; // 밀리초를 초로 변환\n callback(deltaTime);\n }\n previousTimeRef.current = time;\n requestRef.current = requestAnimationFrame(animate);\n };\n\n requestRef.current = requestAnimationFrame(animate);\n\n return () => {\n if (requestRef.current) {\n cancelAnimationFrame(requestRef.current);\n }\n };\n }, [callback, isPlaying]);\n};","/**\n * 셰이더 관련 설정\n */\nexport const SHADER_CONFIG = {\n /** 최대 영역 개수 */\n MAX_AREAS: 8,\n /** 최대 포인트 개수 (8영역 × 4포인트) */\n MAX_POINTS: 32,\n /** 최대 드래그 벡터 개수 */\n MAX_DRAG_VECTORS: 8,\n /** 최대 강도 배열 크기 */\n MAX_STRENGTHS: 8,\n} as const;\n\n/**\n * 애니메이션 관련 설정\n */\nexport const ANIMATION_CONFIG = {\n /** 목표 FPS */\n TARGET_FPS: 60,\n /** 델타 타임 (약 16.67ms) */\n DELTA_TIME: 1 / 60,\n} as const;\n\n/**\n * 기본 영역 설정값\n */\nexport const DEFAULT_AREA = {\n /** 기본 왜곡 강도 */\n DISTORTION_STRENGTH: 0.5,\n /** 기본 애니메이션 지속 시간 (초) */\n DURATION: 2.0,\n /** 기본 이징 함수 */\n EASING: 'easeInOut' as const,\n /** 기본 벡터 A */\n VECTOR_A: { x: 0.1, y: 0.1 },\n /** 기본 벡터 B */\n VECTOR_B: { x: -0.1, y: -0.1 },\n} as const;","import React, { useEffect } from 'react';\nimport { DistortionArea } from '../types/area';\nimport { DistortionEditorProps } from './types';\nimport { useDistortionEditor } from './hooks/useDistortionEditor';\nimport { EditorCanvas } from './components/EditorCanvas';\nimport { AreaList } from './components/AreaList';\nimport { ParameterPanel } from './components/ParameterPanel';\nimport { DEFAULT_AREA } from '../utils/constants';\n\nexport const DistortionEditor: React.FC = ({\n\tinitialAreas = [],\n\timageSrc,\n\tonAreasChange,\n\tonSelectedAreaChange,\n\twidth = 800,\n\theight = 600,\n\tcanvasStyle,\n}) => {\n\tconst {\n\t\tstate,\n\t\tselectArea,\n\t\taddArea,\n\t\tremoveArea,\n\t\tupdateArea,\n\t\tupdatePoint,\n\t\tstartDragging,\n\t\tstopDragging,\n\t\tgetSelectedArea,\n\t} = useDistortionEditor(initialAreas);\n\n\t// 영역 변경 시 콜백 호출\n\tuseEffect(() => {\n\t\tonAreasChange?.(state.areas);\n\t}, [state.areas, onAreasChange]);\n\n\t// 선택된 영역 변경 시 콜백 호출\n\tuseEffect(() => {\n\t\tonSelectedAreaChange?.(state.selectedAreaId);\n\t}, [state.selectedAreaId, onSelectedAreaChange]);\n\n\t// 새 영역 추가 핸들러\n\tconst handleAddArea = () => {\n\t\tconst newArea: DistortionArea = {\n\t\t\tid: `area-${Date.now()}`,\n\t\t\tbasePoints: [\n\t\t\t\t{ x: 0.3, y: 0.3 },\n\t\t\t\t{ x: 0.7, y: 0.3 },\n\t\t\t\t{ x: 0.7, y: 0.7 },\n\t\t\t\t{ x: 0.3, y: 0.7 },\n\t\t\t],\n\t\t\tmovement: {\n\t\t\t\tvectorA: { x: DEFAULT_AREA.VECTOR_A.x, y: DEFAULT_AREA.VECTOR_A.y },\n\t\t\t\tvectorB: { x: DEFAULT_AREA.VECTOR_B.x, y: DEFAULT_AREA.VECTOR_B.y },\n\t\t\t\tduration: DEFAULT_AREA.DURATION,\n\t\t\t\teasing: DEFAULT_AREA.EASING as any,\n\t\t\t},\n\t\t\tdistortionStrength: DEFAULT_AREA.DISTORTION_STRENGTH,\n\t\t\tprogress: 0,\n\t\t\tdragVector: { x: 0, y: 0 },\n\t\t};\n\t\taddArea(newArea);\n\t};\n\n\t// 파라미터 업데이트 핸들러\n\tconst handleUpdateArea = (updates: Partial) => {\n\t\tif (state.selectedAreaId) {\n\t\t\tupdateArea(state.selectedAreaId, updates);\n\t\t}\n\t};\n\n\tconst selectedArea = getSelectedArea();\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t{/* 왼쪽: 캔버스 */}\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t
\n\n\t\t\t\t{/* 오른쪽: 사이드바 */}\n\t\t\t\t
\n\t\t\t\t\t{/* 영역 목록 */}\n\t\t\t\t\t\n\n\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';\nimport { DistortionArea, Point } from '../../types/area';\nimport { EditorState } from '../types';\n\nexport const useDistortionEditor = (initialAreas: DistortionArea[] = []) => {\n\tconst [state, setState] = useState({\n\t\tselectedAreaId: initialAreas[0]?.id || null,\n\t\tareas: initialAreas,\n\t\teditMode: 'normal',\n\t\tdraggingPointIndex: null,\n\t});\n\n\t/** 영역 선택 */\n\tconst selectArea = useCallback((areaId: string | null) => {\n\t\tsetState((prev) => ({ ...prev, selectedAreaId: areaId }));\n\t}, []);\n\n\t/** 영역 추가 */\n\tconst addArea = useCallback((area: DistortionArea) => {\n\t\tsetState((prev) => ({\n\t\t\t...prev,\n\t\t\tareas: [...prev.areas, area],\n\t\t\tselectedAreaId: area.id,\n\t\t}));\n\t}, []);\n\n\t/** 영역 삭제 */\n\tconst removeArea = useCallback((areaId: string) => {\n\t\tsetState((prev) => {\n\t\t\tconst newAreas = prev.areas.filter((a) => a.id !== areaId);\n\t\t\treturn {\n\t\t\t\t...prev,\n\t\t\t\tareas: newAreas,\n\t\t\t\tselectedAreaId:\n\t\t\t\t\tprev.selectedAreaId === areaId ? newAreas[0]?.id || null : prev.selectedAreaId,\n\t\t\t};\n\t\t});\n\t}, []);\n\n\t/** 영역 업데이트 */\n\tconst updateArea = useCallback((areaId: string, updates: Partial) => {\n\t\tsetState((prev) => ({\n\t\t\t...prev,\n\t\t\tareas: prev.areas.map((area) => (area.id === areaId ? { ...area, ...updates } : area)),\n\t\t}));\n\t}, []);\n\n\t/** 포인트 업데이트 */\n\tconst updatePoint = useCallback((areaId: string, pointIndex: number, point: Point) => {\n\t\tsetState((prev) => ({\n\t\t\t...prev,\n\t\t\tareas: prev.areas.map((area) => {\n\t\t\t\tif (area.id === areaId) {\n\t\t\t\t\tconst newPoints = [...area.basePoints] as [Point, Point, Point, Point];\n\t\t\t\t\tnewPoints[pointIndex] = point;\n\t\t\t\t\treturn { ...area, basePoints: newPoints };\n\t\t\t\t}\n\t\t\t\treturn area;\n\t\t\t}),\n\t\t}));\n\t}, []);\n\n\t/** 드래그 시작 */\n\tconst startDragging = useCallback((pointIndex: number) => {\n\t\tsetState((prev) => ({ ...prev, draggingPointIndex: pointIndex }));\n\t}, []);\n\n\t/** 드래그 종료 */\n\tconst stopDragging = useCallback(() => {\n\t\tsetState((prev) => ({ ...prev, draggingPointIndex: null }));\n\t}, []);\n\n\t/** 편집 모드 변경 */\n\tconst setEditMode = useCallback((mode: EditorState['editMode']) => {\n\t\tsetState((prev) => ({ ...prev, editMode: mode }));\n\t}, []);\n\n\t/** 선택된 영역 가져오기 */\n\tconst getSelectedArea = useCallback(() => {\n\t\treturn state.areas.find((a) => a.id === state.selectedAreaId) || null;\n\t}, [state.areas, state.selectedAreaId]);\n\n\treturn {\n\t\tstate,\n\t\tselectArea,\n\t\taddArea,\n\t\tremoveArea,\n\t\tupdateArea,\n\t\tupdatePoint,\n\t\tstartDragging,\n\t\tstopDragging,\n\t\tsetEditMode,\n\t\tgetSelectedArea,\n\t};\n};\n","import React, {useRef, useEffect, useState, useCallback, useMemo} from 'react';\nimport {DistortionArea, Point} from '../../types/area';\nimport {ImageDistortion} from '../../components/ImageDistortion';\nimport {EditorCanvasStyle} from '../types';\nimport {DEFAULT_EDITOR_CANVAS_STYLE} from '../constants';\n\ninterface EditorCanvasProps {\n\tareas: DistortionArea[];\n\tselectedAreaId: string | null;\n\timageSrc: string;\n\twidth: number;\n\theight: number;\n\tonUpdatePoint: (areaId: string, pointIndex: number, point: Point) => void;\n\tonUpdateArea: (areaId: string, updates: Partial) => void;\n\tdraggingPointIndex: number | null;\n\tonStartDragging: (pointIndex: number) => void;\n\tonStopDragging: () => void;\n\t/** 에디터 캔버스 스타일 커스터마이징 */\n\tstyle?: EditorCanvasStyle;\n}\n\nexport const EditorCanvas: React.FC = ({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t areas,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t selectedAreaId,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t imageSrc,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t width,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t height,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onUpdatePoint,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onUpdateArea,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t draggingPointIndex,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onStartDragging,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onStopDragging,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t style: customStyle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t }) => {\n\tconst containerRef = useRef(null);\n\tconst [canvasSize, setCanvasSize] = useState({width: 0, height: 0});\n\tconst [isDraggingArea, setIsDraggingArea] = useState(false);\n\tconst [dragStartPos, setDragStartPos] = useState(null);\n\n\t// 스타일 병합 (커스텀 스타일 우선)\n\tconst editorStyle = useMemo(() => ({\n\t\t...DEFAULT_EDITOR_CANVAS_STYLE,\n\t\t...customStyle,\n\t\tcircleLevels: customStyle?.circleLevels || DEFAULT_EDITOR_CANVAS_STYLE.circleLevels,\n\t\tcenterPoint: {\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.centerPoint,\n\t\t\t...customStyle?.centerPoint,\n\t\t},\n\t\tpointHandle: {\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.pointHandle,\n\t\t\t...customStyle?.pointHandle,\n\t\t},\n\t\tareaOutline: {\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.areaOutline,\n\t\t\t...customStyle?.areaOutline,\n\t\t},\n\t}), [customStyle]);\n\n\t// 컨테이너 크기 측정\n\tuseEffect(() => {\n\t\tif (!containerRef.current) return;\n\t\tconst rect = containerRef.current.getBoundingClientRect();\n\t\tsetCanvasSize({width: rect.width, height: rect.height});\n\t}, [width, height]);\n\n\t// 선택된 영역 찾기\n\tconst selectedArea = areas.find((a) => a.id === selectedAreaId);\n\n\t// 점이 사각형 내부에 있는지 확인 (Point-in-Polygon test)\n\tconst isPointInPolygon = useCallback((point: Point, polygon: Point[]): boolean => {\n\t\tlet inside = false;\n\t\tfor (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\n\t\t\tconst xi = polygon[i].x, yi = polygon[i].y;\n\t\t\tconst xj = polygon[j].x, yj = polygon[j].y;\n\n\t\t\tconst intersect = ((yi > point.y) !== (yj > point.y))\n\t\t\t\t&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);\n\t\t\tif (intersect) inside = !inside;\n\t\t}\n\t\treturn inside;\n\t}, []);\n\n\t// 마우스 이벤트 핸들러\n\tconst handleMouseDown = useCallback(\n\t\t(pointIndex: number) => (e: React.MouseEvent) => {\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t\tonStartDragging(pointIndex);\n\t\t},\n\t\t[onStartDragging]\n\t);\n\n\t// 캔버스 클릭 (사각형 내부 클릭 감지)\n\tconst handleCanvasMouseDown = useCallback(\n\t\t(e: React.MouseEvent) => {\n\t\t\tif (!selectedArea || !containerRef.current) return;\n\n\t\t\tconst rect = containerRef.current.getBoundingClientRect();\n\t\t\tconst x = (e.clientX - rect.left) / rect.width;\n\t\t\tconst y = (e.clientY - rect.top) / rect.height;\n\t\t\tconst clickPoint = { x, y };\n\n\t\t\t// 사각형 내부를 클릭했는지 확인\n\t\t\tif (isPointInPolygon(clickPoint, selectedArea.basePoints)) {\n\t\t\t\tsetIsDraggingArea(true);\n\t\t\t\tsetDragStartPos(clickPoint);\n\t\t\t\te.preventDefault();\n\t\t\t}\n\t\t},\n\t\t[selectedArea, isPointInPolygon]\n\t);\n\n\tconst handleMouseMove = useCallback(\n\t\t(e: React.MouseEvent) => {\n\t\t\tif (!selectedArea || !containerRef.current) return;\n\n\t\t\tconst rect = containerRef.current.getBoundingClientRect();\n\t\t\tconst x = (e.clientX - rect.left) / rect.width;\n\t\t\tconst y = (e.clientY - rect.top) / rect.height;\n\n\t\t\t// 포인트 드래그 중\n\t\t\tif (draggingPointIndex !== null) {\n\t\t\t\tconst clampedX = Math.max(0, Math.min(1, x));\n\t\t\t\tconst clampedY = Math.max(0, Math.min(1, y));\n\t\t\t\tonUpdatePoint(selectedArea.id, draggingPointIndex, {x: clampedX, y: clampedY});\n\t\t\t}\n\t\t\t// 사각형 전체 드래그 중\n\t\t\telse if (isDraggingArea && dragStartPos) {\n\t\t\t\tconst deltaX = x - dragStartPos.x;\n\t\t\t\tconst deltaY = y - dragStartPos.y;\n\n\t\t\t\t// 모든 포인트를 delta만큼 이동\n\t\t\t\tconst newPoints = selectedArea.basePoints.map((point) => ({\n\t\t\t\t\tx: Math.max(0, Math.min(1, point.x + deltaX)),\n\t\t\t\t\ty: Math.max(0, Math.min(1, point.y + deltaY)),\n\t\t\t\t})) as [Point, Point, Point, Point];\n\n\t\t\t\tonUpdateArea(selectedArea.id, { basePoints: newPoints });\n\t\t\t\tsetDragStartPos({ x, y }); // 다음 프레임을 위해 시작 위치 업데이트\n\t\t\t}\n\t\t},\n\t\t[draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]\n\t);\n\n\tconst handleMouseUp = useCallback(() => {\n\t\tif (draggingPointIndex !== null) {\n\t\t\tonStopDragging();\n\t\t}\n\t\tif (isDraggingArea) {\n\t\t\tsetIsDraggingArea(false);\n\t\t\tsetDragStartPos(null);\n\t\t}\n\t}, [draggingPointIndex, isDraggingArea, onStopDragging]);\n\n\t// 전역 마우스 업 이벤트\n\tuseEffect(() => {\n\t\tif (draggingPointIndex !== null || isDraggingArea) {\n\t\t\twindow.addEventListener('mouseup', handleMouseUp);\n\t\t\treturn () => window.removeEventListener('mouseup', handleMouseUp);\n\t\t}\n\t}, [draggingPointIndex, isDraggingArea, handleMouseUp]);\n\n\t// UV 좌표를 픽셀 좌표로 변환 (셰이더와 동일한 bilinear interpolation)\n\tconst uvToPixel = (\n\t\tu: number,\n\t\tv: number,\n\t\tpoints: [Point, Point, Point, Point],\n\t\tcanvasWidth: number,\n\t\tcanvasHeight: number\n\t): { x: number; y: number } => {\n\t\t// p0=좌상, p1=우상, p2=우하, p3=좌하\n\t\tconst [p0, p1, p2, p3] = points;\n\n\t\t// 셰이더 computeUV와 동일한 순서로 bilinear interpolation\n\t\t// left = mix(p0, p1, u) -> 상단 가장자리\n\t\t// right = mix(p3, p2, u) -> 하단 가장자리\n\t\t// position = mix(left, right, v)\n\t\tconst leftX = p0.x * (1 - u) + p1.x * u;\n\t\tconst leftY = p0.y * (1 - u) + p1.y * u;\n\n\t\tconst rightX = p3.x * (1 - u) + p2.x * u;\n\t\tconst rightY = p3.y * (1 - u) + p2.y * u;\n\n\t\tconst posX = leftX * (1 - v) + rightX * v;\n\t\tconst posY = leftY * (1 - v) + rightY * v;\n\n\t\treturn {\n\t\t\tx: posX * canvasWidth,\n\t\t\ty: posY * canvasHeight,\n\t\t};\n\t};\n\n\t// UV 좌표계의 원을 정확히 그리기 (찌그러진 원 형태)\n\tconst drawDistortionCircle = useCallback((\n\t\tctx: CanvasRenderingContext2D,\n\t\tpoints: [Point, Point, Point, Point],\n\t\tcanvasWidth: number,\n\t\tcanvasHeight: number\n\t) => {\n\t\tconst segments = 128; // 원을 128개 세그먼트로 촘촘히 분할\n\t\tconst centerU = 0.5;\n\t\tconst centerV = 0.5;\n\n\t\tconst circleLevels = editorStyle.circleLevels || [];\n\n\t\t// 원 레벨별로 그리기 (외부 -> 내부 순)\n\t\tcircleLevels.forEach((level, index) => {\n\t\t\tconst levelPoints: { x: number; y: number }[] = [];\n\t\t\tfor (let i = 0; i <= segments; i++) {\n\t\t\t\tconst theta = (i / segments) * 2 * Math.PI;\n\t\t\t\tconst u = centerU - level.radius * Math.sin(theta);\n\t\t\t\tconst v = centerV + level.radius * Math.cos(theta);\n\t\t\t\tconst pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);\n\t\t\t\tlevelPoints.push(pixelPos);\n\t\t\t}\n\n\t\t\tctx.beginPath();\n\t\t\tctx.moveTo(levelPoints[0].x, levelPoints[0].y);\n\t\t\tfor (let i = 1; i < levelPoints.length; i++) {\n\t\t\t\tctx.lineTo(levelPoints[i].x, levelPoints[i].y);\n\t\t\t}\n\t\t\tctx.closePath();\n\n\t\t\t// 원 테두리\n\t\t\tconst baseColor = level.color || 'rgba(255, 200, 0, 1)';\n\t\t\t// baseColor에서 RGB 추출하고 opacity 적용\n\t\t\tconst colorWithOpacity = baseColor.replace(/rgba?\\(([^)]+)\\)/, (_, rgb) => {\n\t\t\t\tconst parts = rgb.split(',').map((p: string) => p.trim());\n\t\t\t\treturn `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${level.opacity})`;\n\t\t\t});\n\t\t\tctx.strokeStyle = colorWithOpacity;\n\t\t\tctx.lineWidth = level.lineWidth;\n\t\t\tif (level.dashPattern) {\n\t\t\t\tctx.setLineDash(level.dashPattern);\n\t\t\t}\n\t\t\tctx.stroke();\n\t\t\tctx.setLineDash([]);\n\n\t\t\t// 가장 외부 원만 내부 채우기\n\t\t\tif (index === 0 && editorStyle.circleFillColor) {\n\t\t\t\tctx.fillStyle = editorStyle.circleFillColor;\n\t\t\t\tctx.fill();\n\t\t\t}\n\t\t});\n\n\t\t// 중심점 표시\n\t\tconst centerPointStyle = editorStyle.centerPoint || {};\n\t\tconst centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight);\n\t\tctx.beginPath();\n\t\tctx.arc(centerPixel.x, centerPixel.y, centerPointStyle.radius || 5, 0, 2 * Math.PI);\n\t\tif (centerPointStyle.fillColor) {\n\t\t\tctx.fillStyle = centerPointStyle.fillColor;\n\t\t\tctx.fill();\n\t\t}\n\t\tif (centerPointStyle.strokeColor) {\n\t\t\tctx.strokeStyle = centerPointStyle.strokeColor;\n\t\t\tctx.lineWidth = centerPointStyle.strokeWidth || 2;\n\t\t\tctx.stroke();\n\t\t}\n\t}, [editorStyle]);\n\n\t// 커서 스타일 결정\n\tconst getCursorStyle = () => {\n\t\tif (draggingPointIndex !== null) return 'grabbing';\n\t\tif (isDraggingArea) return 'grabbing';\n\t\treturn 'default';\n\t};\n\n\treturn (\n\t\t\n\t\t\t{/* ImageDistortion 컴포넌트 */}\n\t\t\t\n\n\t\t\t{/* 오버레이 SVG */}\n\t\t\t\n\t\t\t\t{/* 모든 영역의 사각형 표시 */}\n\t\t\t\t{areas.map((area) => {\n\t\t\t\t\tconst isSelected = area.id === selectedAreaId;\n\t\t\t\t\tconst points = area.basePoints;\n\t\t\t\t\tconst outlineStyle = editorStyle.areaOutline || {};\n\t\t\t\t\treturn (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{/* 사각형 배경 및 경계선 */}\n\t\t\t\t\t\t\t `${p.x * canvasSize.width},${p.y * canvasSize.height}`)\n\t\t\t\t\t\t\t\t\t.join(' ')}\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)')}\n\t\t\t\t\t\t\t\tstroke={isSelected ? (outlineStyle.selectedColor || '#00aaff') : (outlineStyle.unselectedColor || '#888')}\n\t\t\t\t\t\t\t\tstrokeWidth={isSelected ? (outlineStyle.selectedWidth || 2) : (outlineStyle.unselectedWidth || 1)}\n\t\t\t\t\t\t\t\tstrokeDasharray={isSelected ? '0' : (outlineStyle.unselectedDashPattern?.join(',') || '5,5')}\n\t\t\t\t\t\t\t\topacity={isSelected ? 1 : 0.5}\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\n\t\t\t{/* 선택된 영역의 타원 가이드 (Canvas로 그리기) */}\n\t\t\t{selectedArea && canvasSize.width > 0 && (\n\t\t\t\t {\n\t\t\t\t\t\tif (canvas) {\n\t\t\t\t\t\t\tconst ctx = canvas.getContext('2d');\n\t\t\t\t\t\t\tif (ctx) {\n\t\t\t\t\t\t\t\tctx.clearRect(0, 0, canvasSize.width, canvasSize.height);\n\t\t\t\t\t\t\t\tdrawDistortionCircle(ctx, selectedArea.basePoints, canvasSize.width, canvasSize.height);\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\n\t\t\t{/* 선택된 영역의 포인트 핸들 */}\n\t\t\t{selectedArea &&\n\t\t\t\tselectedArea.basePoints.map((point, index) => {\n\t\t\t\t\tconst handleStyle = editorStyle.pointHandle || {};\n\t\t\t\t\treturn (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tP{index + 1}\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\n\t);\n};\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","import React from 'react';\nimport { DistortionArea } from '../../types/area';\n\ninterface 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\ninterface 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{/* 벡터 A (X) */}\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: {\n\t\t\t\t\t\t\t\t...area.movement,\n\t\t\t\t\t\t\t\tvectorA: { ...area.movement.vectorA, x: parseFloat(e.target.value) },\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\tclassName=\"slider\"\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{/* 벡터 A (Y) */}\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: {\n\t\t\t\t\t\t\t\t...area.movement,\n\t\t\t\t\t\t\t\tvectorA: { ...area.movement.vectorA, y: parseFloat(e.target.value) },\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\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{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"],"mappings":"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","names":["import_react","THREE","import_react","import_react","import_react","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime"]} \ No newline at end of file diff --git a/dist/index.mjs b/dist/index.mjs index 1919858..8cef508 100644 --- a/dist/index.mjs +++ b/dist/index.mjs @@ -536,7 +536,67 @@ var useDistortionEditor = (initialAreas = []) => { }; // src/editor/components/EditorCanvas.tsx -import { useRef as useRef3, useEffect as useEffect3, useState as useState3, useCallback as useCallback3 } from "react"; +import { useRef as useRef3, useEffect as useEffect3, useState as useState3, useCallback as useCallback3, useMemo } from "react"; + +// src/editor/constants.ts +var DEFAULT_EDITOR_CANVAS_STYLE = { + // 3단계 원 스타일 (외부 -> 내부) + circleLevels: [ + { + radius: 0.5, + opacity: 0.3, + lineWidth: 2, + color: "rgba(255, 200, 0, 1)", + dashPattern: [8, 4] + }, + { + radius: 0.33, + opacity: 0.6, + lineWidth: 2.5, + color: "rgba(255, 200, 0, 1)", + dashPattern: [8, 4] + }, + { + radius: 0.167, + opacity: 0.9, + lineWidth: 3, + color: "rgba(255, 200, 0, 1)", + dashPattern: [8, 4] + } + ], + // 원 내부 채우기 + circleFillColor: "rgba(255, 200, 0, 0.08)", + // 중심점 + centerPoint: { + radius: 5, + fillColor: "rgba(255, 200, 0, 1)", + strokeColor: "rgba(255, 255, 255, 0.8)", + strokeWidth: 2 + }, + // 포인트 핸들 + pointHandle: { + size: 16, + fillColor: "#00aaff", + strokeColor: "white", + strokeWidth: 2, + labelColor: "#00aaff", + labelFontSize: 11 + }, + // 영역 외곽선 + areaOutline: { + selectedColor: "#00aaff", + unselectedColor: "#888", + selectedWidth: 2, + unselectedWidth: 1, + unselectedDashPattern: [5, 5], + selectedFillColor: "rgba(0, 170, 255, 0.08)", + // 선택된 영역 배경 (연한 파란색) + unselectedFillColor: "rgba(136, 136, 136, 0.03)" + // 선택 안된 영역 배경 (연한 회색) + } +}; + +// src/editor/components/EditorCanvas.tsx import { jsx as jsx2, jsxs } from "react/jsx-runtime"; var EditorCanvas = ({ areas, @@ -548,12 +608,30 @@ var EditorCanvas = ({ onUpdateArea, draggingPointIndex, onStartDragging, - onStopDragging + onStopDragging, + style: customStyle }) => { const containerRef = useRef3(null); const [canvasSize, setCanvasSize] = useState3({ width: 0, height: 0 }); const [isDraggingArea, setIsDraggingArea] = useState3(false); const [dragStartPos, setDragStartPos] = useState3(null); + const editorStyle = useMemo(() => ({ + ...DEFAULT_EDITOR_CANVAS_STYLE, + ...customStyle, + circleLevels: customStyle?.circleLevels || DEFAULT_EDITOR_CANVAS_STYLE.circleLevels, + centerPoint: { + ...DEFAULT_EDITOR_CANVAS_STYLE.centerPoint, + ...customStyle?.centerPoint + }, + pointHandle: { + ...DEFAULT_EDITOR_CANVAS_STYLE.pointHandle, + ...customStyle?.pointHandle + }, + areaOutline: { + ...DEFAULT_EDITOR_CANVAS_STYLE.areaOutline, + ...customStyle?.areaOutline + } + }), [customStyle]); useEffect3(() => { if (!containerRef.current) return; const rect = containerRef.current.getBoundingClientRect(); @@ -644,63 +722,57 @@ var EditorCanvas = ({ y: posY * canvasHeight }; }; - const drawDistortionCircle = (ctx, points, canvasWidth, canvasHeight) => { + const drawDistortionCircle = useCallback3((ctx, points, canvasWidth, canvasHeight) => { const segments = 128; const centerU = 0.5; const centerV = 0.5; - const maxRadius = 0.5; - const circlePoints = []; - for (let i = 0; i <= segments; i++) { - const theta = i / segments * 2 * Math.PI; - const u = centerU - maxRadius * Math.sin(theta); - const v = centerV + maxRadius * Math.cos(theta); - const pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight); - circlePoints.push(pixelPos); - } - ctx.beginPath(); - ctx.moveTo(circlePoints[0].x, circlePoints[0].y); - for (let i = 1; i < circlePoints.length; i++) { - ctx.lineTo(circlePoints[i].x, circlePoints[i].y); - } - ctx.closePath(); - ctx.strokeStyle = "rgba(255, 200, 0, 0.9)"; - ctx.lineWidth = 3; - ctx.setLineDash([8, 4]); - ctx.stroke(); - ctx.setLineDash([]); - ctx.fillStyle = "rgba(255, 200, 0, 0.12)"; - ctx.fill(); - for (const r of [0.25, 0.375]) { - const gradientPoints = []; + const circleLevels = editorStyle.circleLevels || []; + circleLevels.forEach((level, index) => { + const levelPoints = []; for (let i = 0; i <= segments; i++) { const theta = i / segments * 2 * Math.PI; - const u = centerU - r * Math.sin(theta); - const v = centerV + r * Math.cos(theta); + const u = centerU - level.radius * Math.sin(theta); + const v = centerV + level.radius * Math.cos(theta); const pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight); - gradientPoints.push(pixelPos); + levelPoints.push(pixelPos); } ctx.beginPath(); - ctx.moveTo(gradientPoints[0].x, gradientPoints[0].y); - for (let i = 1; i < gradientPoints.length; i++) { - ctx.lineTo(gradientPoints[i].x, gradientPoints[i].y); + ctx.moveTo(levelPoints[0].x, levelPoints[0].y); + for (let i = 1; i < levelPoints.length; i++) { + ctx.lineTo(levelPoints[i].x, levelPoints[i].y); } ctx.closePath(); - const alpha = r / maxRadius; - ctx.strokeStyle = `rgba(255, 220, 100, ${0.3 * alpha})`; - ctx.lineWidth = 1; - ctx.setLineDash([3, 3]); + const baseColor = level.color || "rgba(255, 200, 0, 1)"; + const colorWithOpacity = baseColor.replace(/rgba?\(([^)]+)\)/, (_, rgb) => { + const parts = rgb.split(",").map((p) => p.trim()); + return `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${level.opacity})`; + }); + ctx.strokeStyle = colorWithOpacity; + ctx.lineWidth = level.lineWidth; + if (level.dashPattern) { + ctx.setLineDash(level.dashPattern); + } ctx.stroke(); ctx.setLineDash([]); - } + if (index === 0 && editorStyle.circleFillColor) { + ctx.fillStyle = editorStyle.circleFillColor; + ctx.fill(); + } + }); + const centerPointStyle = editorStyle.centerPoint || {}; const centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight); ctx.beginPath(); - ctx.arc(centerPixel.x, centerPixel.y, 5, 0, 2 * Math.PI); - ctx.fillStyle = "rgba(255, 200, 0, 1)"; - ctx.fill(); - ctx.strokeStyle = "rgba(255, 255, 255, 0.8)"; - ctx.lineWidth = 2; - ctx.stroke(); - }; + ctx.arc(centerPixel.x, centerPixel.y, centerPointStyle.radius || 5, 0, 2 * Math.PI); + if (centerPointStyle.fillColor) { + ctx.fillStyle = centerPointStyle.fillColor; + ctx.fill(); + } + if (centerPointStyle.strokeColor) { + ctx.strokeStyle = centerPointStyle.strokeColor; + ctx.lineWidth = centerPointStyle.strokeWidth || 2; + ctx.stroke(); + } + }, [editorStyle]); const getCursorStyle = () => { if (draggingPointIndex !== null) return "grabbing"; if (isDraggingArea) return "grabbing"; @@ -715,7 +787,7 @@ var EditorCanvas = ({ onMouseDown: handleCanvasMouseDown, onMouseMove: handleMouseMove, children: [ - /* @__PURE__ */ jsx2(ImageDistortion, { imageSrc, areas, width, height }), + /* @__PURE__ */ jsx2(ImageDistortion, { imageSrc, areas }), /* @__PURE__ */ jsx2( "svg", { @@ -730,14 +802,15 @@ var EditorCanvas = ({ children: areas.map((area) => { const isSelected = area.id === selectedAreaId; const points = area.basePoints; + const outlineStyle = editorStyle.areaOutline || {}; return /* @__PURE__ */ jsx2("g", { children: /* @__PURE__ */ jsx2( "polygon", { points: points.map((p) => `${p.x * canvasSize.width},${p.y * canvasSize.height}`).join(" "), - fill: "none", - stroke: isSelected ? "#00aaff" : "#888", - strokeWidth: isSelected ? 2 : 1, - strokeDasharray: isSelected ? "0" : "5,5", + fill: isSelected ? outlineStyle.selectedFillColor || "rgba(0, 170, 255, 0.08)" : outlineStyle.unselectedFillColor || "rgba(136, 136, 136, 0.03)", + stroke: isSelected ? outlineStyle.selectedColor || "#00aaff" : outlineStyle.unselectedColor || "#888", + strokeWidth: isSelected ? outlineStyle.selectedWidth || 2 : outlineStyle.unselectedWidth || 1, + strokeDasharray: isSelected ? "0" : outlineStyle.unselectedDashPattern?.join(",") || "5,5", opacity: isSelected ? 1 : 0.5 } ) }, area.id); @@ -768,48 +841,51 @@ var EditorCanvas = ({ } } ), - selectedArea && selectedArea.basePoints.map((point, index) => /* @__PURE__ */ jsx2( - "div", - { - className: `point-handle ${draggingPointIndex === index ? "dragging" : ""}`, - style: { - position: "absolute", - left: `${point.x * 100}%`, - top: `${point.y * 100}%`, - transform: "translate(-50%, -50%)", - width: 16, - height: 16, - borderRadius: "50%", - backgroundColor: "#00aaff", - border: "2px solid white", - cursor: "grab", - pointerEvents: "auto", - boxShadow: "0 2px 4px rgba(0,0,0,0.3)" + selectedArea && selectedArea.basePoints.map((point, index) => { + const handleStyle = editorStyle.pointHandle || {}; + return /* @__PURE__ */ jsx2( + "div", + { + className: `point-handle ${draggingPointIndex === index ? "dragging" : ""}`, + style: { + position: "absolute", + left: `${point.x * 100}%`, + top: `${point.y * 100}%`, + transform: "translate(-50%, -50%)", + width: handleStyle.size || 16, + height: handleStyle.size || 16, + borderRadius: "50%", + backgroundColor: handleStyle.fillColor || "#00aaff", + border: `${handleStyle.strokeWidth || 2}px solid ${handleStyle.strokeColor || "white"}`, + cursor: "grab", + pointerEvents: "auto", + boxShadow: "0 2px 4px rgba(0,0,0,0.3)" + }, + onMouseDown: handleMouseDown(index), + children: /* @__PURE__ */ jsxs( + "div", + { + style: { + position: "absolute", + top: -24, + left: "50%", + transform: "translateX(-50%)", + fontSize: handleStyle.labelFontSize || 11, + color: handleStyle.labelColor || "#00aaff", + fontWeight: "bold", + textShadow: "1px 1px 2px rgba(0,0,0,0.8)", + whiteSpace: "nowrap" + }, + children: [ + "P", + index + 1 + ] + } + ) }, - onMouseDown: handleMouseDown(index), - children: /* @__PURE__ */ jsxs( - "div", - { - style: { - position: "absolute", - top: -24, - left: "50%", - transform: "translateX(-50%)", - fontSize: 11, - color: "#00aaff", - fontWeight: "bold", - textShadow: "1px 1px 2px rgba(0,0,0,0.8)", - whiteSpace: "nowrap" - }, - children: [ - "P", - index + 1 - ] - } - ) - }, - index - )) + index + ); + }) ] } ); @@ -1013,7 +1089,8 @@ var DistortionEditor = ({ onAreasChange, onSelectedAreaChange, width = 800, - height = 600 + height = 600, + canvasStyle }) => { const { state, @@ -1072,7 +1149,8 @@ var DistortionEditor = ({ onUpdateArea: updateArea, draggingPointIndex: state.draggingPointIndex, onStartDragging: startDragging, - onStopDragging: stopDragging + onStopDragging: stopDragging, + style: canvasStyle } ) }), /* @__PURE__ */ jsxs4("div", { className: "editor-sidebar", children: [ diff --git a/dist/index.mjs.map b/dist/index.mjs.map index 68d285f..ce39baf 100644 --- a/dist/index.mjs.map +++ b/dist/index.mjs.map @@ -1 +1 @@ -{"version":3,"sources":["../src/components/ImageDistortion.tsx","../src/engine/ThreeScene.ts","../src/engine/ShaderManager.ts","../src/utils/easing.ts","../src/engine/AnimationLoop.ts","../src/hooks/useAnimationFrame.ts","../src/utils/constants.ts","../src/editor/DistortionEditor.tsx","../src/editor/hooks/useDistortionEditor.ts","../src/editor/components/EditorCanvas.tsx","../src/editor/components/AreaList.tsx","../src/editor/components/ParameterPanel.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState, useCallback } from 'react';\nimport * as THREE from 'three';\nimport { type DistortionArea } from '../types';\nimport { ThreeScene } from '../engine/ThreeScene';\nimport { ShaderManager } from '../engine/ShaderManager';\nimport { AnimationLoop } from '../engine/AnimationLoop';\nimport { useAnimationFrame } from '../hooks/useAnimationFrame';\nimport { SHADER_CONFIG } from '../utils/constants';\n\n/**\n * ImageDistortion 컴포넌트 Props\n */\nexport interface ImageDistortionProps {\n /** 이미지 소스 URL */\n imageSrc: string;\n /** 왜곡 영역 배열 */\n areas: DistortionArea[];\n /** 버텍스 셰이더 경로 (선택사항) */\n vertexShaderPath?: string;\n /** 프래그먼트 셰이더 경로 (선택사항) */\n fragmentShaderPath?: string;\n /** 애니메이션 재생 여부 */\n isPlaying?: boolean;\n /** 컨테이너 스타일 */\n style?: React.CSSProperties;\n /** 컨테이너 클래스명 */\n className?: string;\n}\n\n/**\n * GPU 가속 이미지 왜곡 컴포넌트\n * Three.js와 GLSL 셰이더를 사용하여 실시간 이미지 왜곡 효과를 제공합니다.\n */\nexport const ImageDistortion: React.FC = ({\n imageSrc,\n areas,\n vertexShaderPath,\n fragmentShaderPath,\n isPlaying = true,\n style,\n className,\n}) => {\n const containerRef = useRef(null);\n const sceneRef = useRef(null);\n const shaderManagerRef = useRef(new ShaderManager());\n const textureRef = useRef(null);\n\n const [isReady, setIsReady] = useState(false);\n const [imageLoaded, setImageLoaded] = useState(false);\n const [currentAreas, setCurrentAreas] = useState(areas);\n\n // 영역 변경 시 상태 업데이트\n useEffect(() => {\n setCurrentAreas(areas);\n }, [areas]);\n\n // Three.js 씬 초기화\n useEffect(() => {\n console.log('[ImageDistortion] useEffect 실행, containerRef.current:', containerRef.current);\n\n if (!containerRef.current) {\n console.warn('[ImageDistortion] containerRef.current가 null입니다. 컴포넌트가 제대로 마운트되지 않았습니다.');\n return;\n }\n\n console.log('[ImageDistortion] 초기화 시작');\n const scene = new ThreeScene(containerRef.current);\n sceneRef.current = scene;\n\n // 셰이더 로드\n const vertPath = vertexShaderPath || '/shaders/distortion.vert.glsl';\n const fragPath = fragmentShaderPath || '/shaders/distortion.frag.glsl';\n\n console.log('[ImageDistortion] 셰이더 로드 시도:', { vertPath, fragPath });\n\n shaderManagerRef.current\n .loadShaders(vertPath, fragPath)\n .then(({ vertex, fragment }) => {\n console.log('[ImageDistortion] 셰이더 로드 성공');\n scene.setShaderMaterial(vertex, fragment);\n setIsReady(true);\n })\n .catch((error) => {\n console.error('[ImageDistortion] 셰이더 로드 실패:', error);\n });\n\n return () => {\n scene.dispose();\n if (textureRef.current) {\n textureRef.current.dispose();\n }\n };\n }, [vertexShaderPath, fragmentShaderPath]);\n\n // 이미지 텍스처 로드\n useEffect(() => {\n if (!imageSrc || !isReady) {\n console.log('[ImageDistortion] 이미지 로드 스킵:', { imageSrc, isReady });\n return;\n }\n\n console.log('[ImageDistortion] 이미지 로드 시작:', imageSrc);\n setImageLoaded(false);\n\n const loader = new THREE.TextureLoader();\n loader.load(\n imageSrc,\n (texture) => {\n console.log('[ImageDistortion] 이미지 로드 성공!', {\n width: texture.image.width,\n height: texture.image.height\n });\n textureRef.current = texture;\n setImageLoaded(true);\n if (sceneRef.current) {\n sceneRef.current.updateUniforms({\n u_texture: { value: texture },\n });\n sceneRef.current.render();\n console.log('[ImageDistortion] 텍스처 업데이트 및 렌더링 완료');\n }\n },\n (progress) => {\n console.log('[ImageDistortion] 이미지 로딩 중...',\n Math.round((progress.loaded / progress.total) * 100) + '%'\n );\n },\n (error) => {\n console.error('[ImageDistortion] 이미지 로드 실패:', error);\n setImageLoaded(false);\n }\n );\n\n return () => {\n if (textureRef.current) {\n textureRef.current.dispose();\n textureRef.current = null;\n }\n };\n }, [imageSrc, isReady]);\n\n // 셰이더 유니폼 업데이트\n useEffect(() => {\n if (!sceneRef.current || !isReady) return;\n\n // 현재 해상도 가져오기\n const resolution = sceneRef.current.getResolution();\n\n // 포인트 배열 생성\n // UI는 좌상단 (0,0), WebGL은 좌하단 (0,0)이므로 y 좌표를 반전\n const points = new Float32Array(SHADER_CONFIG.MAX_POINTS * 2);\n currentAreas.forEach((area, areaIndex) => {\n area.basePoints.forEach((point, pointIndex) => {\n const index = (areaIndex * 4 + pointIndex) * 2;\n points[index] = point.x;\n points[index + 1] = 1.0 - point.y; // y 좌표 반전\n });\n });\n\n // 드래그 벡터 배열 생성\n // dragVector도 y 좌표계를 맞춰야 하므로 y를 반전\n const dragVectors = new Float32Array(SHADER_CONFIG.MAX_DRAG_VECTORS * 2);\n currentAreas.forEach((area, index) => {\n const baseIndex = index * 2;\n dragVectors[baseIndex] = area.dragVector.x;\n dragVectors[baseIndex + 1] = -area.dragVector.y; // y 방향 반전\n });\n\n // 강도 배열 생성\n const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS);\n currentAreas.forEach((area, index) => {\n strengths[index] = area.distortionStrength;\n });\n\n sceneRef.current.updateUniforms({\n u_numAreas: { value: currentAreas.length },\n u_points: { value: points },\n u_dragVectors: { value: dragVectors },\n u_distortionStrengths: { value: strengths },\n });\n\n sceneRef.current.render();\n }, [currentAreas, isReady]);\n\n // 애니메이션 루프\n const animationCallback = useCallback((deltaTime: number) => {\n if (!isReady) return;\n\n setCurrentAreas((prevAreas) => {\n // 진행도 업데이트\n const updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);\n // 드래그 벡터 업데이트\n return AnimationLoop.updateAreaDragVectors(updatedAreas);\n });\n }, [isReady]);\n\n useAnimationFrame(animationCallback, isPlaying);\n\n return (\n \n {!imageLoaded && (\n \n 이미지 로딩 중...\n \n )}\n \n );\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 console.log('[ThreeScene] render() 호출됨, mesh:', this.mesh);\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';\n\ntype EasingFunc = (t: number) => number;\n\n/**\n * 이징 함수 구현 맵\n */\nconst easingFunctions: Record = {\n linear: (t) => t,\n\n easeIn: (t) => t * t,\n easeOut: (t) => t * (2 - t),\n easeInOut: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),\n\n easeInQuad: (t) => t * t,\n easeOutQuad: (t) => t * (2 - t),\n};\n\n/**\n * 진행도에 이징 함수를 적용\n * @param progress 진행도 (0.0 - 1.0)\n * @param easingType 적용할 이징 함수 타입\n * @returns 이징이 적용된 진행도 (0.0 - 1.0)\n */\nexport const applyEasing = (\n progress: number,\n easingType: EasingFunction\n): number => {\n const clampedProgress = Math.max(0, Math.min(1, progress));\n return easingFunctions[easingType](clampedProgress);\n};","import { applyEasing } from '../utils/easing';\nimport type {DistortionArea, Point} from \"../types\";\n\n/**\n * 애니메이션 루프 관리 클래스\n */\nexport class AnimationLoop {\n /**\n * 영역들의 드래그 벡터를 현재 진행도에 따라 업데이트\n * @param areas 왜곡 영역 배열\n * @returns 업데이트된 영역 배열\n */\n public static updateAreaDragVectors(\n areas: DistortionArea[]\n ): DistortionArea[] {\n return areas.map((area) => {\n const { progress, movement } = area;\n\n // 이징 적용\n const easedProgress = applyEasing(progress, movement.easing);\n\n // 벡터 간 보간\n let dragVector: Point;\n\n if (easedProgress < 0.5) {\n // 0.0 -> 0.5: 0에서 vectorA로 보간\n const t = easedProgress * 2;\n dragVector = {\n x: movement.vectorA.x * t,\n y: movement.vectorA.y * t,\n };\n } else {\n // 0.5 -> 1.0: vectorA에서 0으로 보간\n const t = (easedProgress - 0.5) * 2;\n dragVector = {\n x: movement.vectorA.x * (1 - t),\n y: movement.vectorA.y * (1 - t),\n };\n }\n\n return {\n ...area,\n dragVector,\n };\n });\n }\n\n /**\n * 모든 영역의 진행도를 델타 타임만큼 업데이트\n * @param areas 왜곡 영역 배열\n * @param deltaTime 델타 타임 (초)\n * @returns 업데이트된 영역 배열\n */\n public static updateProgress(\n areas: DistortionArea[],\n deltaTime: number\n ): DistortionArea[] {\n return areas.map((area) => {\n let newProgress = area.progress + deltaTime / area.movement.duration;\n newProgress %= 1.0; // 루프\n\n return {\n ...area,\n progress: newProgress,\n };\n });\n }\n}","import { useEffect, useRef } from 'react';\n\n/**\n * requestAnimationFrame을 사용한 애니메이션 루프 훅\n * @param callback 매 프레임마다 호출될 콜백 (deltaTime을 인자로 받음)\n * @param isPlaying 애니메이션 재생 여부\n */\nexport const useAnimationFrame = (\n callback: (deltaTime: number) => void,\n isPlaying: boolean = true\n) => {\n const requestRef = useRef(undefined);\n const previousTimeRef = useRef(undefined);\n\n useEffect(() => {\n if (!isPlaying) return;\n\n const animate = (time: number) => {\n if (previousTimeRef.current !== undefined) {\n const deltaTime = (time - previousTimeRef.current) / 1000; // 밀리초를 초로 변환\n callback(deltaTime);\n }\n previousTimeRef.current = time;\n requestRef.current = requestAnimationFrame(animate);\n };\n\n requestRef.current = requestAnimationFrame(animate);\n\n return () => {\n if (requestRef.current) {\n cancelAnimationFrame(requestRef.current);\n }\n };\n }, [callback, isPlaying]);\n};","/**\n * 셰이더 관련 설정\n */\nexport const SHADER_CONFIG = {\n /** 최대 영역 개수 */\n MAX_AREAS: 8,\n /** 최대 포인트 개수 (8영역 × 4포인트) */\n MAX_POINTS: 32,\n /** 최대 드래그 벡터 개수 */\n MAX_DRAG_VECTORS: 8,\n /** 최대 강도 배열 크기 */\n MAX_STRENGTHS: 8,\n} as const;\n\n/**\n * 애니메이션 관련 설정\n */\nexport const ANIMATION_CONFIG = {\n /** 목표 FPS */\n TARGET_FPS: 60,\n /** 델타 타임 (약 16.67ms) */\n DELTA_TIME: 1 / 60,\n} as const;\n\n/**\n * 기본 영역 설정값\n */\nexport const DEFAULT_AREA = {\n /** 기본 왜곡 강도 */\n DISTORTION_STRENGTH: 0.5,\n /** 기본 애니메이션 지속 시간 (초) */\n DURATION: 2.0,\n /** 기본 이징 함수 */\n EASING: 'easeInOut' as const,\n /** 기본 벡터 A */\n VECTOR_A: { x: 0.1, y: 0.1 },\n /** 기본 벡터 B */\n VECTOR_B: { x: -0.1, y: -0.1 },\n} as const;","import React, { useEffect } from 'react';\nimport { DistortionArea } from '../types/area';\nimport { DistortionEditorProps } from './types';\nimport { useDistortionEditor } from './hooks/useDistortionEditor';\nimport { EditorCanvas } from './components/EditorCanvas';\nimport { AreaList } from './components/AreaList';\nimport { ParameterPanel } from './components/ParameterPanel';\nimport { DEFAULT_AREA } from '../utils/constants';\n\nexport const DistortionEditor: React.FC = ({\n\tinitialAreas = [],\n\timageSrc,\n\tonAreasChange,\n\tonSelectedAreaChange,\n\twidth = 800,\n\theight = 600,\n}) => {\n\tconst {\n\t\tstate,\n\t\tselectArea,\n\t\taddArea,\n\t\tremoveArea,\n\t\tupdateArea,\n\t\tupdatePoint,\n\t\tstartDragging,\n\t\tstopDragging,\n\t\tgetSelectedArea,\n\t} = useDistortionEditor(initialAreas);\n\n\t// 영역 변경 시 콜백 호출\n\tuseEffect(() => {\n\t\tonAreasChange?.(state.areas);\n\t}, [state.areas, onAreasChange]);\n\n\t// 선택된 영역 변경 시 콜백 호출\n\tuseEffect(() => {\n\t\tonSelectedAreaChange?.(state.selectedAreaId);\n\t}, [state.selectedAreaId, onSelectedAreaChange]);\n\n\t// 새 영역 추가 핸들러\n\tconst handleAddArea = () => {\n\t\tconst newArea: DistortionArea = {\n\t\t\tid: `area-${Date.now()}`,\n\t\t\tbasePoints: [\n\t\t\t\t{ x: 0.3, y: 0.3 },\n\t\t\t\t{ x: 0.7, y: 0.3 },\n\t\t\t\t{ x: 0.7, y: 0.7 },\n\t\t\t\t{ x: 0.3, y: 0.7 },\n\t\t\t],\n\t\t\tmovement: {\n\t\t\t\tvectorA: { x: DEFAULT_AREA.VECTOR_A.x, y: DEFAULT_AREA.VECTOR_A.y },\n\t\t\t\tvectorB: { x: DEFAULT_AREA.VECTOR_B.x, y: DEFAULT_AREA.VECTOR_B.y },\n\t\t\t\tduration: DEFAULT_AREA.DURATION,\n\t\t\t\teasing: DEFAULT_AREA.EASING as any,\n\t\t\t},\n\t\t\tdistortionStrength: DEFAULT_AREA.DISTORTION_STRENGTH,\n\t\t\tprogress: 0,\n\t\t\tdragVector: { x: 0, y: 0 },\n\t\t};\n\t\taddArea(newArea);\n\t};\n\n\t// 파라미터 업데이트 핸들러\n\tconst handleUpdateArea = (updates: Partial) => {\n\t\tif (state.selectedAreaId) {\n\t\t\tupdateArea(state.selectedAreaId, updates);\n\t\t}\n\t};\n\n\tconst selectedArea = getSelectedArea();\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t{/* 왼쪽: 캔버스 */}\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t
\n\n\t\t\t\t{/* 오른쪽: 사이드바 */}\n\t\t\t\t
\n\t\t\t\t\t{/* 영역 목록 */}\n\t\t\t\t\t\n\n\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';\nimport { DistortionArea, Point } from '../../types/area';\nimport { EditorState } from '../types';\n\nexport const useDistortionEditor = (initialAreas: DistortionArea[] = []) => {\n\tconst [state, setState] = useState({\n\t\tselectedAreaId: initialAreas[0]?.id || null,\n\t\tareas: initialAreas,\n\t\teditMode: 'normal',\n\t\tdraggingPointIndex: null,\n\t});\n\n\t/** 영역 선택 */\n\tconst selectArea = useCallback((areaId: string | null) => {\n\t\tsetState((prev) => ({ ...prev, selectedAreaId: areaId }));\n\t}, []);\n\n\t/** 영역 추가 */\n\tconst addArea = useCallback((area: DistortionArea) => {\n\t\tsetState((prev) => ({\n\t\t\t...prev,\n\t\t\tareas: [...prev.areas, area],\n\t\t\tselectedAreaId: area.id,\n\t\t}));\n\t}, []);\n\n\t/** 영역 삭제 */\n\tconst removeArea = useCallback((areaId: string) => {\n\t\tsetState((prev) => {\n\t\t\tconst newAreas = prev.areas.filter((a) => a.id !== areaId);\n\t\t\treturn {\n\t\t\t\t...prev,\n\t\t\t\tareas: newAreas,\n\t\t\t\tselectedAreaId:\n\t\t\t\t\tprev.selectedAreaId === areaId ? newAreas[0]?.id || null : prev.selectedAreaId,\n\t\t\t};\n\t\t});\n\t}, []);\n\n\t/** 영역 업데이트 */\n\tconst updateArea = useCallback((areaId: string, updates: Partial) => {\n\t\tsetState((prev) => ({\n\t\t\t...prev,\n\t\t\tareas: prev.areas.map((area) => (area.id === areaId ? { ...area, ...updates } : area)),\n\t\t}));\n\t}, []);\n\n\t/** 포인트 업데이트 */\n\tconst updatePoint = useCallback((areaId: string, pointIndex: number, point: Point) => {\n\t\tsetState((prev) => ({\n\t\t\t...prev,\n\t\t\tareas: prev.areas.map((area) => {\n\t\t\t\tif (area.id === areaId) {\n\t\t\t\t\tconst newPoints = [...area.basePoints] as [Point, Point, Point, Point];\n\t\t\t\t\tnewPoints[pointIndex] = point;\n\t\t\t\t\treturn { ...area, basePoints: newPoints };\n\t\t\t\t}\n\t\t\t\treturn area;\n\t\t\t}),\n\t\t}));\n\t}, []);\n\n\t/** 드래그 시작 */\n\tconst startDragging = useCallback((pointIndex: number) => {\n\t\tsetState((prev) => ({ ...prev, draggingPointIndex: pointIndex }));\n\t}, []);\n\n\t/** 드래그 종료 */\n\tconst stopDragging = useCallback(() => {\n\t\tsetState((prev) => ({ ...prev, draggingPointIndex: null }));\n\t}, []);\n\n\t/** 편집 모드 변경 */\n\tconst setEditMode = useCallback((mode: EditorState['editMode']) => {\n\t\tsetState((prev) => ({ ...prev, editMode: mode }));\n\t}, []);\n\n\t/** 선택된 영역 가져오기 */\n\tconst getSelectedArea = useCallback(() => {\n\t\treturn state.areas.find((a) => a.id === state.selectedAreaId) || null;\n\t}, [state.areas, state.selectedAreaId]);\n\n\treturn {\n\t\tstate,\n\t\tselectArea,\n\t\taddArea,\n\t\tremoveArea,\n\t\tupdateArea,\n\t\tupdatePoint,\n\t\tstartDragging,\n\t\tstopDragging,\n\t\tsetEditMode,\n\t\tgetSelectedArea,\n\t};\n};\n","import React, {useRef, useEffect, useState, useCallback} from 'react';\nimport {DistortionArea, Point} from '../../types/area';\nimport {ImageDistortion} from '../../components/ImageDistortion';\n\ninterface EditorCanvasProps {\n\tareas: DistortionArea[];\n\tselectedAreaId: string | null;\n\timageSrc: string;\n\twidth: number;\n\theight: number;\n\tonUpdatePoint: (areaId: string, pointIndex: number, point: Point) => void;\n\tonUpdateArea: (areaId: string, updates: Partial) => void;\n\tdraggingPointIndex: number | null;\n\tonStartDragging: (pointIndex: number) => void;\n\tonStopDragging: () => void;\n}\n\nexport const EditorCanvas: React.FC = ({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t areas,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t selectedAreaId,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t imageSrc,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t width,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t height,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onUpdatePoint,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onUpdateArea,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t draggingPointIndex,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onStartDragging,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onStopDragging,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t }) => {\n\tconst containerRef = useRef(null);\n\tconst [canvasSize, setCanvasSize] = useState({width: 0, height: 0});\n\tconst [isDraggingArea, setIsDraggingArea] = useState(false);\n\tconst [dragStartPos, setDragStartPos] = useState(null);\n\n\t// 컨테이너 크기 측정\n\tuseEffect(() => {\n\t\tif (!containerRef.current) return;\n\t\tconst rect = containerRef.current.getBoundingClientRect();\n\t\tsetCanvasSize({width: rect.width, height: rect.height});\n\t}, [width, height]);\n\n\t// 선택된 영역 찾기\n\tconst selectedArea = areas.find((a) => a.id === selectedAreaId);\n\n\t// 점이 사각형 내부에 있는지 확인 (Point-in-Polygon test)\n\tconst isPointInPolygon = useCallback((point: Point, polygon: Point[]): boolean => {\n\t\tlet inside = false;\n\t\tfor (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\n\t\t\tconst xi = polygon[i].x, yi = polygon[i].y;\n\t\t\tconst xj = polygon[j].x, yj = polygon[j].y;\n\n\t\t\tconst intersect = ((yi > point.y) !== (yj > point.y))\n\t\t\t\t&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);\n\t\t\tif (intersect) inside = !inside;\n\t\t}\n\t\treturn inside;\n\t}, []);\n\n\t// 마우스 이벤트 핸들러\n\tconst handleMouseDown = useCallback(\n\t\t(pointIndex: number) => (e: React.MouseEvent) => {\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t\tonStartDragging(pointIndex);\n\t\t},\n\t\t[onStartDragging]\n\t);\n\n\t// 캔버스 클릭 (사각형 내부 클릭 감지)\n\tconst handleCanvasMouseDown = useCallback(\n\t\t(e: React.MouseEvent) => {\n\t\t\tif (!selectedArea || !containerRef.current) return;\n\n\t\t\tconst rect = containerRef.current.getBoundingClientRect();\n\t\t\tconst x = (e.clientX - rect.left) / rect.width;\n\t\t\tconst y = (e.clientY - rect.top) / rect.height;\n\t\t\tconst clickPoint = { x, y };\n\n\t\t\t// 사각형 내부를 클릭했는지 확인\n\t\t\tif (isPointInPolygon(clickPoint, selectedArea.basePoints)) {\n\t\t\t\tsetIsDraggingArea(true);\n\t\t\t\tsetDragStartPos(clickPoint);\n\t\t\t\te.preventDefault();\n\t\t\t}\n\t\t},\n\t\t[selectedArea, isPointInPolygon]\n\t);\n\n\tconst handleMouseMove = useCallback(\n\t\t(e: React.MouseEvent) => {\n\t\t\tif (!selectedArea || !containerRef.current) return;\n\n\t\t\tconst rect = containerRef.current.getBoundingClientRect();\n\t\t\tconst x = (e.clientX - rect.left) / rect.width;\n\t\t\tconst y = (e.clientY - rect.top) / rect.height;\n\n\t\t\t// 포인트 드래그 중\n\t\t\tif (draggingPointIndex !== null) {\n\t\t\t\tconst clampedX = Math.max(0, Math.min(1, x));\n\t\t\t\tconst clampedY = Math.max(0, Math.min(1, y));\n\t\t\t\tonUpdatePoint(selectedArea.id, draggingPointIndex, {x: clampedX, y: clampedY});\n\t\t\t}\n\t\t\t// 사각형 전체 드래그 중\n\t\t\telse if (isDraggingArea && dragStartPos) {\n\t\t\t\tconst deltaX = x - dragStartPos.x;\n\t\t\t\tconst deltaY = y - dragStartPos.y;\n\n\t\t\t\t// 모든 포인트를 delta만큼 이동\n\t\t\t\tconst newPoints = selectedArea.basePoints.map((point) => ({\n\t\t\t\t\tx: Math.max(0, Math.min(1, point.x + deltaX)),\n\t\t\t\t\ty: Math.max(0, Math.min(1, point.y + deltaY)),\n\t\t\t\t})) as [Point, Point, Point, Point];\n\n\t\t\t\tonUpdateArea(selectedArea.id, { basePoints: newPoints });\n\t\t\t\tsetDragStartPos({ x, y }); // 다음 프레임을 위해 시작 위치 업데이트\n\t\t\t}\n\t\t},\n\t\t[draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]\n\t);\n\n\tconst handleMouseUp = useCallback(() => {\n\t\tif (draggingPointIndex !== null) {\n\t\t\tonStopDragging();\n\t\t}\n\t\tif (isDraggingArea) {\n\t\t\tsetIsDraggingArea(false);\n\t\t\tsetDragStartPos(null);\n\t\t}\n\t}, [draggingPointIndex, isDraggingArea, onStopDragging]);\n\n\t// 전역 마우스 업 이벤트\n\tuseEffect(() => {\n\t\tif (draggingPointIndex !== null || isDraggingArea) {\n\t\t\twindow.addEventListener('mouseup', handleMouseUp);\n\t\t\treturn () => window.removeEventListener('mouseup', handleMouseUp);\n\t\t}\n\t}, [draggingPointIndex, isDraggingArea, handleMouseUp]);\n\n\t// UV 좌표를 픽셀 좌표로 변환 (셰이더와 동일한 bilinear interpolation)\n\tconst uvToPixel = (\n\t\tu: number,\n\t\tv: number,\n\t\tpoints: [Point, Point, Point, Point],\n\t\tcanvasWidth: number,\n\t\tcanvasHeight: number\n\t): { x: number; y: number } => {\n\t\t// p0=좌상, p1=우상, p2=우하, p3=좌하\n\t\tconst [p0, p1, p2, p3] = points;\n\n\t\t// 셰이더 computeUV와 동일한 순서로 bilinear interpolation\n\t\t// left = mix(p0, p1, u) -> 상단 가장자리\n\t\t// right = mix(p3, p2, u) -> 하단 가장자리\n\t\t// position = mix(left, right, v)\n\t\tconst leftX = p0.x * (1 - u) + p1.x * u;\n\t\tconst leftY = p0.y * (1 - u) + p1.y * u;\n\n\t\tconst rightX = p3.x * (1 - u) + p2.x * u;\n\t\tconst rightY = p3.y * (1 - u) + p2.y * u;\n\n\t\tconst posX = leftX * (1 - v) + rightX * v;\n\t\tconst posY = leftY * (1 - v) + rightY * v;\n\n\t\treturn {\n\t\t\tx: posX * canvasWidth,\n\t\t\ty: posY * canvasHeight,\n\t\t};\n\t};\n\n\t// UV 좌표계의 원을 정확히 그리기 (찌그러진 원 형태)\n\tconst drawDistortionCircle = (\n\t\tctx: CanvasRenderingContext2D,\n\t\tpoints: [Point, Point, Point, Point],\n\t\tcanvasWidth: number,\n\t\tcanvasHeight: number\n\t) => {\n\t\tconst segments = 128; // 원을 128개 세그먼트로 촘촘히 분할\n\t\tconst centerU = 0.5;\n\t\tconst centerV = 0.5;\n\t\tconst maxRadius = 0.5; // UV 좌표계에서 최대 반지름 0.5 (셰이더의 maxUvRadius)\n\n\t\t// 원 위의 점들을 UV 좌표로 샘플링 후 픽셀 좌표로 변환\n\t\t// 4가지 조합을 모두 테스트 (사용자가 이미지에서 P1-P3 대각선으로 늘렸을 때 왜곡도 같은 방향이어야 함)\n\t\tconst circlePoints: { x: number; y: number }[] = [];\n\t\tfor (let i = 0; i <= segments; i++) {\n\t\t\tconst theta = (i / segments) * 2 * Math.PI;\n\n\t\t\t// 테스트: u=-sin, v=cos (-90도 회전, P1-P3 방향에 맞춤)\n\t\t\tconst u = centerU - maxRadius * Math.sin(theta);\n\t\t\tconst v = centerV + maxRadius * Math.cos(theta);\n\n\t\t\tconst pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);\n\t\t\tcirclePoints.push(pixelPos);\n\t\t}\n\n\t\t// 찌그러진 원 그리기 (실제 왜곡 적용 경계)\n\t\tctx.beginPath();\n\t\tctx.moveTo(circlePoints[0].x, circlePoints[0].y);\n\t\tfor (let i = 1; i < circlePoints.length; i++) {\n\t\t\tctx.lineTo(circlePoints[i].x, circlePoints[i].y);\n\t\t}\n\t\tctx.closePath();\n\t\tctx.strokeStyle = 'rgba(255, 200, 0, 0.9)';\n\t\tctx.lineWidth = 3;\n\t\tctx.setLineDash([8, 4]);\n\t\tctx.stroke();\n\t\tctx.setLineDash([]);\n\n\t\t// 내부를 반투명하게 채우기\n\t\tctx.fillStyle = 'rgba(255, 200, 0, 0.12)';\n\t\tctx.fill();\n\n\t\t// 영향력 그라디언트를 나타내는 추가 원들 (0.25, 0.375 반지름)\n\t\tfor (const r of [0.25, 0.375]) {\n\t\t\tconst gradientPoints: { x: number; y: number }[] = [];\n\t\t\tfor (let i = 0; i <= segments; i++) {\n\t\t\t\tconst theta = (i / segments) * 2 * Math.PI;\n\t\t\t\tconst u = centerU - r * Math.sin(theta);\n\t\t\t\tconst v = centerV + r * Math.cos(theta);\n\t\t\t\tconst pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);\n\t\t\t\tgradientPoints.push(pixelPos);\n\t\t\t}\n\n\t\t\tctx.beginPath();\n\t\t\tctx.moveTo(gradientPoints[0].x, gradientPoints[0].y);\n\t\t\tfor (let i = 1; i < gradientPoints.length; i++) {\n\t\t\t\tctx.lineTo(gradientPoints[i].x, gradientPoints[i].y);\n\t\t\t}\n\t\t\tctx.closePath();\n\t\t\tconst alpha = r / maxRadius;\n\t\t\tctx.strokeStyle = `rgba(255, 220, 100, ${0.3 * alpha})`;\n\t\t\tctx.lineWidth = 1;\n\t\t\tctx.setLineDash([3, 3]);\n\t\t\tctx.stroke();\n\t\t\tctx.setLineDash([]);\n\t\t}\n\n\t\t// 중심점 표시\n\t\tconst centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight);\n\t\tctx.beginPath();\n\t\tctx.arc(centerPixel.x, centerPixel.y, 5, 0, 2 * Math.PI);\n\t\tctx.fillStyle = 'rgba(255, 200, 0, 1)';\n\t\tctx.fill();\n\t\tctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';\n\t\tctx.lineWidth = 2;\n\t\tctx.stroke();\n\t};\n\n\t// 커서 스타일 결정\n\tconst getCursorStyle = () => {\n\t\tif (draggingPointIndex !== null) return 'grabbing';\n\t\tif (isDraggingArea) return 'grabbing';\n\t\treturn 'default';\n\t};\n\n\treturn (\n\t\t\n\t\t\t{/* ImageDistortion 컴포넌트 */}\n\t\t\t\n\n\t\t\t{/* 오버레이 SVG */}\n\t\t\t\n\t\t\t\t{/* 모든 영역의 사각형 표시 */}\n\t\t\t\t{areas.map((area) => {\n\t\t\t\t\tconst isSelected = area.id === selectedAreaId;\n\t\t\t\t\tconst points = area.basePoints;\n\t\t\t\t\treturn (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{/* 사각형 경계선 */}\n\t\t\t\t\t\t\t `${p.x * canvasSize.width},${p.y * canvasSize.height}`)\n\t\t\t\t\t\t\t\t\t.join(' ')}\n\t\t\t\t\t\t\t\tfill=\"none\"\n\t\t\t\t\t\t\t\tstroke={isSelected ? '#00aaff' : '#888'}\n\t\t\t\t\t\t\t\tstrokeWidth={isSelected ? 2 : 1}\n\t\t\t\t\t\t\t\tstrokeDasharray={isSelected ? '0' : '5,5'}\n\t\t\t\t\t\t\t\topacity={isSelected ? 1 : 0.5}\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\n\t\t\t{/* 선택된 영역의 타원 가이드 (Canvas로 그리기) */}\n\t\t\t{selectedArea && canvasSize.width > 0 && (\n\t\t\t\t {\n\t\t\t\t\t\tif (canvas) {\n\t\t\t\t\t\t\tconst ctx = canvas.getContext('2d');\n\t\t\t\t\t\t\tif (ctx) {\n\t\t\t\t\t\t\t\tctx.clearRect(0, 0, canvasSize.width, canvasSize.height);\n\t\t\t\t\t\t\t\tdrawDistortionCircle(ctx, selectedArea.basePoints, canvasSize.width, canvasSize.height);\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\n\t\t\t{/* 선택된 영역의 포인트 핸들 */}\n\t\t\t{selectedArea &&\n\t\t\t\tselectedArea.basePoints.map((point, index) => (\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\tP{index + 1}\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t))}\n\t\t\n\t);\n};\n","import React from 'react';\nimport { DistortionArea } from '../../types/area';\n\ninterface 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\ninterface 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{/* 벡터 A (X) */}\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: {\n\t\t\t\t\t\t\t\t...area.movement,\n\t\t\t\t\t\t\t\tvectorA: { ...area.movement.vectorA, x: parseFloat(e.target.value) },\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\tclassName=\"slider\"\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{/* 벡터 A (Y) */}\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: {\n\t\t\t\t\t\t\t\t...area.movement,\n\t\t\t\t\t\t\t\tvectorA: { ...area.movement.vectorA, y: parseFloat(e.target.value) },\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\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{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"],"mappings":"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","names":["useEffect","useRef","THREE","useRef","useEffect","useEffect","useState","useCallback","useRef","useEffect","useState","useCallback","jsx","useRef","useState","useEffect","useCallback","jsx","jsxs","jsx","jsxs","jsx","jsxs","useEffect"]} \ No newline at end of file +{"version":3,"sources":["../src/components/ImageDistortion.tsx","../src/engine/ThreeScene.ts","../src/engine/ShaderManager.ts","../src/utils/easing.ts","../src/engine/AnimationLoop.ts","../src/hooks/useAnimationFrame.ts","../src/utils/constants.ts","../src/editor/DistortionEditor.tsx","../src/editor/hooks/useDistortionEditor.ts","../src/editor/components/EditorCanvas.tsx","../src/editor/constants.ts","../src/editor/components/AreaList.tsx","../src/editor/components/ParameterPanel.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState, useCallback } from 'react';\nimport * as THREE from 'three';\nimport { type DistortionArea } from '../types';\nimport { ThreeScene } from '../engine/ThreeScene';\nimport { ShaderManager } from '../engine/ShaderManager';\nimport { AnimationLoop } from '../engine/AnimationLoop';\nimport { useAnimationFrame } from '../hooks/useAnimationFrame';\nimport { SHADER_CONFIG } from '../utils/constants';\n\n/**\n * ImageDistortion 컴포넌트 Props\n */\nexport interface ImageDistortionProps {\n /** 이미지 소스 URL */\n imageSrc: string;\n /** 왜곡 영역 배열 */\n areas: DistortionArea[];\n /** 버텍스 셰이더 경로 (선택사항) */\n vertexShaderPath?: string;\n /** 프래그먼트 셰이더 경로 (선택사항) */\n fragmentShaderPath?: string;\n /** 애니메이션 재생 여부 */\n isPlaying?: boolean;\n /** 컨테이너 스타일 */\n style?: React.CSSProperties;\n /** 컨테이너 클래스명 */\n className?: string;\n}\n\n/**\n * GPU 가속 이미지 왜곡 컴포넌트\n * Three.js와 GLSL 셰이더를 사용하여 실시간 이미지 왜곡 효과를 제공합니다.\n */\nexport const ImageDistortion: React.FC = ({\n imageSrc,\n areas,\n vertexShaderPath,\n fragmentShaderPath,\n isPlaying = true,\n style,\n className,\n}) => {\n const containerRef = useRef(null);\n const sceneRef = useRef(null);\n const shaderManagerRef = useRef(new ShaderManager());\n const textureRef = useRef(null);\n\n const [isReady, setIsReady] = useState(false);\n const [imageLoaded, setImageLoaded] = useState(false);\n const [currentAreas, setCurrentAreas] = useState(areas);\n\n // 영역 변경 시 상태 업데이트\n useEffect(() => {\n setCurrentAreas(areas);\n }, [areas]);\n\n // Three.js 씬 초기화\n useEffect(() => {\n console.log('[ImageDistortion] useEffect 실행, containerRef.current:', containerRef.current);\n\n if (!containerRef.current) {\n console.warn('[ImageDistortion] containerRef.current가 null입니다. 컴포넌트가 제대로 마운트되지 않았습니다.');\n return;\n }\n\n console.log('[ImageDistortion] 초기화 시작');\n const scene = new ThreeScene(containerRef.current);\n sceneRef.current = scene;\n\n // 셰이더 로드\n const vertPath = vertexShaderPath || '/shaders/distortion.vert.glsl';\n const fragPath = fragmentShaderPath || '/shaders/distortion.frag.glsl';\n\n console.log('[ImageDistortion] 셰이더 로드 시도:', { vertPath, fragPath });\n\n shaderManagerRef.current\n .loadShaders(vertPath, fragPath)\n .then(({ vertex, fragment }) => {\n console.log('[ImageDistortion] 셰이더 로드 성공');\n scene.setShaderMaterial(vertex, fragment);\n setIsReady(true);\n })\n .catch((error) => {\n console.error('[ImageDistortion] 셰이더 로드 실패:', error);\n });\n\n return () => {\n scene.dispose();\n if (textureRef.current) {\n textureRef.current.dispose();\n }\n };\n }, [vertexShaderPath, fragmentShaderPath]);\n\n // 이미지 텍스처 로드\n useEffect(() => {\n if (!imageSrc || !isReady) {\n console.log('[ImageDistortion] 이미지 로드 스킵:', { imageSrc, isReady });\n return;\n }\n\n console.log('[ImageDistortion] 이미지 로드 시작:', imageSrc);\n setImageLoaded(false);\n\n const loader = new THREE.TextureLoader();\n loader.load(\n imageSrc,\n (texture) => {\n console.log('[ImageDistortion] 이미지 로드 성공!', {\n width: texture.image.width,\n height: texture.image.height\n });\n textureRef.current = texture;\n setImageLoaded(true);\n if (sceneRef.current) {\n sceneRef.current.updateUniforms({\n u_texture: { value: texture },\n });\n sceneRef.current.render();\n console.log('[ImageDistortion] 텍스처 업데이트 및 렌더링 완료');\n }\n },\n (progress) => {\n console.log('[ImageDistortion] 이미지 로딩 중...',\n Math.round((progress.loaded / progress.total) * 100) + '%'\n );\n },\n (error) => {\n console.error('[ImageDistortion] 이미지 로드 실패:', error);\n setImageLoaded(false);\n }\n );\n\n return () => {\n if (textureRef.current) {\n textureRef.current.dispose();\n textureRef.current = null;\n }\n };\n }, [imageSrc, isReady]);\n\n // 셰이더 유니폼 업데이트\n useEffect(() => {\n if (!sceneRef.current || !isReady) return;\n\n // 현재 해상도 가져오기\n const resolution = sceneRef.current.getResolution();\n\n // 포인트 배열 생성\n // UI는 좌상단 (0,0), WebGL은 좌하단 (0,0)이므로 y 좌표를 반전\n const points = new Float32Array(SHADER_CONFIG.MAX_POINTS * 2);\n currentAreas.forEach((area, areaIndex) => {\n area.basePoints.forEach((point, pointIndex) => {\n const index = (areaIndex * 4 + pointIndex) * 2;\n points[index] = point.x;\n points[index + 1] = 1.0 - point.y; // y 좌표 반전\n });\n });\n\n // 드래그 벡터 배열 생성\n // dragVector도 y 좌표계를 맞춰야 하므로 y를 반전\n const dragVectors = new Float32Array(SHADER_CONFIG.MAX_DRAG_VECTORS * 2);\n currentAreas.forEach((area, index) => {\n const baseIndex = index * 2;\n dragVectors[baseIndex] = area.dragVector.x;\n dragVectors[baseIndex + 1] = -area.dragVector.y; // y 방향 반전\n });\n\n // 강도 배열 생성\n const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS);\n currentAreas.forEach((area, index) => {\n strengths[index] = area.distortionStrength;\n });\n\n sceneRef.current.updateUniforms({\n u_numAreas: { value: currentAreas.length },\n u_points: { value: points },\n u_dragVectors: { value: dragVectors },\n u_distortionStrengths: { value: strengths },\n });\n\n sceneRef.current.render();\n }, [currentAreas, isReady]);\n\n // 애니메이션 루프\n const animationCallback = useCallback((deltaTime: number) => {\n if (!isReady) return;\n\n setCurrentAreas((prevAreas) => {\n // 진행도 업데이트\n const updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);\n // 드래그 벡터 업데이트\n return AnimationLoop.updateAreaDragVectors(updatedAreas);\n });\n }, [isReady]);\n\n useAnimationFrame(animationCallback, isPlaying);\n\n return (\n \n {!imageLoaded && (\n \n 이미지 로딩 중...\n \n )}\n \n );\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 console.log('[ThreeScene] render() 호출됨, mesh:', this.mesh);\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';\n\ntype EasingFunc = (t: number) => number;\n\n/**\n * 이징 함수 구현 맵\n */\nconst easingFunctions: Record = {\n linear: (t) => t,\n\n easeIn: (t) => t * t,\n easeOut: (t) => t * (2 - t),\n easeInOut: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),\n\n easeInQuad: (t) => t * t,\n easeOutQuad: (t) => t * (2 - t),\n};\n\n/**\n * 진행도에 이징 함수를 적용\n * @param progress 진행도 (0.0 - 1.0)\n * @param easingType 적용할 이징 함수 타입\n * @returns 이징이 적용된 진행도 (0.0 - 1.0)\n */\nexport const applyEasing = (\n progress: number,\n easingType: EasingFunction\n): number => {\n const clampedProgress = Math.max(0, Math.min(1, progress));\n return easingFunctions[easingType](clampedProgress);\n};","import { applyEasing } from '../utils/easing';\nimport type {DistortionArea, Point} from \"../types\";\n\n/**\n * 애니메이션 루프 관리 클래스\n */\nexport class AnimationLoop {\n /**\n * 영역들의 드래그 벡터를 현재 진행도에 따라 업데이트\n * @param areas 왜곡 영역 배열\n * @returns 업데이트된 영역 배열\n */\n public static updateAreaDragVectors(\n areas: DistortionArea[]\n ): DistortionArea[] {\n return areas.map((area) => {\n const { progress, movement } = area;\n\n // 이징 적용\n const easedProgress = applyEasing(progress, movement.easing);\n\n // 벡터 간 보간\n let dragVector: Point;\n\n if (easedProgress < 0.5) {\n // 0.0 -> 0.5: 0에서 vectorA로 보간\n const t = easedProgress * 2;\n dragVector = {\n x: movement.vectorA.x * t,\n y: movement.vectorA.y * t,\n };\n } else {\n // 0.5 -> 1.0: vectorA에서 0으로 보간\n const t = (easedProgress - 0.5) * 2;\n dragVector = {\n x: movement.vectorA.x * (1 - t),\n y: movement.vectorA.y * (1 - t),\n };\n }\n\n return {\n ...area,\n dragVector,\n };\n });\n }\n\n /**\n * 모든 영역의 진행도를 델타 타임만큼 업데이트\n * @param areas 왜곡 영역 배열\n * @param deltaTime 델타 타임 (초)\n * @returns 업데이트된 영역 배열\n */\n public static updateProgress(\n areas: DistortionArea[],\n deltaTime: number\n ): DistortionArea[] {\n return areas.map((area) => {\n let newProgress = area.progress + deltaTime / area.movement.duration;\n newProgress %= 1.0; // 루프\n\n return {\n ...area,\n progress: newProgress,\n };\n });\n }\n}","import { useEffect, useRef } from 'react';\n\n/**\n * requestAnimationFrame을 사용한 애니메이션 루프 훅\n * @param callback 매 프레임마다 호출될 콜백 (deltaTime을 인자로 받음)\n * @param isPlaying 애니메이션 재생 여부\n */\nexport const useAnimationFrame = (\n callback: (deltaTime: number) => void,\n isPlaying: boolean = true\n) => {\n const requestRef = useRef(undefined);\n const previousTimeRef = useRef(undefined);\n\n useEffect(() => {\n if (!isPlaying) return;\n\n const animate = (time: number) => {\n if (previousTimeRef.current !== undefined) {\n const deltaTime = (time - previousTimeRef.current) / 1000; // 밀리초를 초로 변환\n callback(deltaTime);\n }\n previousTimeRef.current = time;\n requestRef.current = requestAnimationFrame(animate);\n };\n\n requestRef.current = requestAnimationFrame(animate);\n\n return () => {\n if (requestRef.current) {\n cancelAnimationFrame(requestRef.current);\n }\n };\n }, [callback, isPlaying]);\n};","/**\n * 셰이더 관련 설정\n */\nexport const SHADER_CONFIG = {\n /** 최대 영역 개수 */\n MAX_AREAS: 8,\n /** 최대 포인트 개수 (8영역 × 4포인트) */\n MAX_POINTS: 32,\n /** 최대 드래그 벡터 개수 */\n MAX_DRAG_VECTORS: 8,\n /** 최대 강도 배열 크기 */\n MAX_STRENGTHS: 8,\n} as const;\n\n/**\n * 애니메이션 관련 설정\n */\nexport const ANIMATION_CONFIG = {\n /** 목표 FPS */\n TARGET_FPS: 60,\n /** 델타 타임 (약 16.67ms) */\n DELTA_TIME: 1 / 60,\n} as const;\n\n/**\n * 기본 영역 설정값\n */\nexport const DEFAULT_AREA = {\n /** 기본 왜곡 강도 */\n DISTORTION_STRENGTH: 0.5,\n /** 기본 애니메이션 지속 시간 (초) */\n DURATION: 2.0,\n /** 기본 이징 함수 */\n EASING: 'easeInOut' as const,\n /** 기본 벡터 A */\n VECTOR_A: { x: 0.1, y: 0.1 },\n /** 기본 벡터 B */\n VECTOR_B: { x: -0.1, y: -0.1 },\n} as const;","import React, { useEffect } from 'react';\nimport { DistortionArea } from '../types/area';\nimport { DistortionEditorProps } from './types';\nimport { useDistortionEditor } from './hooks/useDistortionEditor';\nimport { EditorCanvas } from './components/EditorCanvas';\nimport { AreaList } from './components/AreaList';\nimport { ParameterPanel } from './components/ParameterPanel';\nimport { DEFAULT_AREA } from '../utils/constants';\n\nexport const DistortionEditor: React.FC = ({\n\tinitialAreas = [],\n\timageSrc,\n\tonAreasChange,\n\tonSelectedAreaChange,\n\twidth = 800,\n\theight = 600,\n\tcanvasStyle,\n}) => {\n\tconst {\n\t\tstate,\n\t\tselectArea,\n\t\taddArea,\n\t\tremoveArea,\n\t\tupdateArea,\n\t\tupdatePoint,\n\t\tstartDragging,\n\t\tstopDragging,\n\t\tgetSelectedArea,\n\t} = useDistortionEditor(initialAreas);\n\n\t// 영역 변경 시 콜백 호출\n\tuseEffect(() => {\n\t\tonAreasChange?.(state.areas);\n\t}, [state.areas, onAreasChange]);\n\n\t// 선택된 영역 변경 시 콜백 호출\n\tuseEffect(() => {\n\t\tonSelectedAreaChange?.(state.selectedAreaId);\n\t}, [state.selectedAreaId, onSelectedAreaChange]);\n\n\t// 새 영역 추가 핸들러\n\tconst handleAddArea = () => {\n\t\tconst newArea: DistortionArea = {\n\t\t\tid: `area-${Date.now()}`,\n\t\t\tbasePoints: [\n\t\t\t\t{ x: 0.3, y: 0.3 },\n\t\t\t\t{ x: 0.7, y: 0.3 },\n\t\t\t\t{ x: 0.7, y: 0.7 },\n\t\t\t\t{ x: 0.3, y: 0.7 },\n\t\t\t],\n\t\t\tmovement: {\n\t\t\t\tvectorA: { x: DEFAULT_AREA.VECTOR_A.x, y: DEFAULT_AREA.VECTOR_A.y },\n\t\t\t\tvectorB: { x: DEFAULT_AREA.VECTOR_B.x, y: DEFAULT_AREA.VECTOR_B.y },\n\t\t\t\tduration: DEFAULT_AREA.DURATION,\n\t\t\t\teasing: DEFAULT_AREA.EASING as any,\n\t\t\t},\n\t\t\tdistortionStrength: DEFAULT_AREA.DISTORTION_STRENGTH,\n\t\t\tprogress: 0,\n\t\t\tdragVector: { x: 0, y: 0 },\n\t\t};\n\t\taddArea(newArea);\n\t};\n\n\t// 파라미터 업데이트 핸들러\n\tconst handleUpdateArea = (updates: Partial) => {\n\t\tif (state.selectedAreaId) {\n\t\t\tupdateArea(state.selectedAreaId, updates);\n\t\t}\n\t};\n\n\tconst selectedArea = getSelectedArea();\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t{/* 왼쪽: 캔버스 */}\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t
\n\n\t\t\t\t{/* 오른쪽: 사이드바 */}\n\t\t\t\t
\n\t\t\t\t\t{/* 영역 목록 */}\n\t\t\t\t\t\n\n\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';\nimport { DistortionArea, Point } from '../../types/area';\nimport { EditorState } from '../types';\n\nexport const useDistortionEditor = (initialAreas: DistortionArea[] = []) => {\n\tconst [state, setState] = useState({\n\t\tselectedAreaId: initialAreas[0]?.id || null,\n\t\tareas: initialAreas,\n\t\teditMode: 'normal',\n\t\tdraggingPointIndex: null,\n\t});\n\n\t/** 영역 선택 */\n\tconst selectArea = useCallback((areaId: string | null) => {\n\t\tsetState((prev) => ({ ...prev, selectedAreaId: areaId }));\n\t}, []);\n\n\t/** 영역 추가 */\n\tconst addArea = useCallback((area: DistortionArea) => {\n\t\tsetState((prev) => ({\n\t\t\t...prev,\n\t\t\tareas: [...prev.areas, area],\n\t\t\tselectedAreaId: area.id,\n\t\t}));\n\t}, []);\n\n\t/** 영역 삭제 */\n\tconst removeArea = useCallback((areaId: string) => {\n\t\tsetState((prev) => {\n\t\t\tconst newAreas = prev.areas.filter((a) => a.id !== areaId);\n\t\t\treturn {\n\t\t\t\t...prev,\n\t\t\t\tareas: newAreas,\n\t\t\t\tselectedAreaId:\n\t\t\t\t\tprev.selectedAreaId === areaId ? newAreas[0]?.id || null : prev.selectedAreaId,\n\t\t\t};\n\t\t});\n\t}, []);\n\n\t/** 영역 업데이트 */\n\tconst updateArea = useCallback((areaId: string, updates: Partial) => {\n\t\tsetState((prev) => ({\n\t\t\t...prev,\n\t\t\tareas: prev.areas.map((area) => (area.id === areaId ? { ...area, ...updates } : area)),\n\t\t}));\n\t}, []);\n\n\t/** 포인트 업데이트 */\n\tconst updatePoint = useCallback((areaId: string, pointIndex: number, point: Point) => {\n\t\tsetState((prev) => ({\n\t\t\t...prev,\n\t\t\tareas: prev.areas.map((area) => {\n\t\t\t\tif (area.id === areaId) {\n\t\t\t\t\tconst newPoints = [...area.basePoints] as [Point, Point, Point, Point];\n\t\t\t\t\tnewPoints[pointIndex] = point;\n\t\t\t\t\treturn { ...area, basePoints: newPoints };\n\t\t\t\t}\n\t\t\t\treturn area;\n\t\t\t}),\n\t\t}));\n\t}, []);\n\n\t/** 드래그 시작 */\n\tconst startDragging = useCallback((pointIndex: number) => {\n\t\tsetState((prev) => ({ ...prev, draggingPointIndex: pointIndex }));\n\t}, []);\n\n\t/** 드래그 종료 */\n\tconst stopDragging = useCallback(() => {\n\t\tsetState((prev) => ({ ...prev, draggingPointIndex: null }));\n\t}, []);\n\n\t/** 편집 모드 변경 */\n\tconst setEditMode = useCallback((mode: EditorState['editMode']) => {\n\t\tsetState((prev) => ({ ...prev, editMode: mode }));\n\t}, []);\n\n\t/** 선택된 영역 가져오기 */\n\tconst getSelectedArea = useCallback(() => {\n\t\treturn state.areas.find((a) => a.id === state.selectedAreaId) || null;\n\t}, [state.areas, state.selectedAreaId]);\n\n\treturn {\n\t\tstate,\n\t\tselectArea,\n\t\taddArea,\n\t\tremoveArea,\n\t\tupdateArea,\n\t\tupdatePoint,\n\t\tstartDragging,\n\t\tstopDragging,\n\t\tsetEditMode,\n\t\tgetSelectedArea,\n\t};\n};\n","import React, {useRef, useEffect, useState, useCallback, useMemo} from 'react';\nimport {DistortionArea, Point} from '../../types/area';\nimport {ImageDistortion} from '../../components/ImageDistortion';\nimport {EditorCanvasStyle} from '../types';\nimport {DEFAULT_EDITOR_CANVAS_STYLE} from '../constants';\n\ninterface EditorCanvasProps {\n\tareas: DistortionArea[];\n\tselectedAreaId: string | null;\n\timageSrc: string;\n\twidth: number;\n\theight: number;\n\tonUpdatePoint: (areaId: string, pointIndex: number, point: Point) => void;\n\tonUpdateArea: (areaId: string, updates: Partial) => void;\n\tdraggingPointIndex: number | null;\n\tonStartDragging: (pointIndex: number) => void;\n\tonStopDragging: () => void;\n\t/** 에디터 캔버스 스타일 커스터마이징 */\n\tstyle?: EditorCanvasStyle;\n}\n\nexport const EditorCanvas: React.FC = ({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t areas,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t selectedAreaId,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t imageSrc,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t width,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t height,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onUpdatePoint,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onUpdateArea,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t draggingPointIndex,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onStartDragging,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t onStopDragging,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t style: customStyle,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t }) => {\n\tconst containerRef = useRef(null);\n\tconst [canvasSize, setCanvasSize] = useState({width: 0, height: 0});\n\tconst [isDraggingArea, setIsDraggingArea] = useState(false);\n\tconst [dragStartPos, setDragStartPos] = useState(null);\n\n\t// 스타일 병합 (커스텀 스타일 우선)\n\tconst editorStyle = useMemo(() => ({\n\t\t...DEFAULT_EDITOR_CANVAS_STYLE,\n\t\t...customStyle,\n\t\tcircleLevels: customStyle?.circleLevels || DEFAULT_EDITOR_CANVAS_STYLE.circleLevels,\n\t\tcenterPoint: {\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.centerPoint,\n\t\t\t...customStyle?.centerPoint,\n\t\t},\n\t\tpointHandle: {\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.pointHandle,\n\t\t\t...customStyle?.pointHandle,\n\t\t},\n\t\tareaOutline: {\n\t\t\t...DEFAULT_EDITOR_CANVAS_STYLE.areaOutline,\n\t\t\t...customStyle?.areaOutline,\n\t\t},\n\t}), [customStyle]);\n\n\t// 컨테이너 크기 측정\n\tuseEffect(() => {\n\t\tif (!containerRef.current) return;\n\t\tconst rect = containerRef.current.getBoundingClientRect();\n\t\tsetCanvasSize({width: rect.width, height: rect.height});\n\t}, [width, height]);\n\n\t// 선택된 영역 찾기\n\tconst selectedArea = areas.find((a) => a.id === selectedAreaId);\n\n\t// 점이 사각형 내부에 있는지 확인 (Point-in-Polygon test)\n\tconst isPointInPolygon = useCallback((point: Point, polygon: Point[]): boolean => {\n\t\tlet inside = false;\n\t\tfor (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {\n\t\t\tconst xi = polygon[i].x, yi = polygon[i].y;\n\t\t\tconst xj = polygon[j].x, yj = polygon[j].y;\n\n\t\t\tconst intersect = ((yi > point.y) !== (yj > point.y))\n\t\t\t\t&& (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi);\n\t\t\tif (intersect) inside = !inside;\n\t\t}\n\t\treturn inside;\n\t}, []);\n\n\t// 마우스 이벤트 핸들러\n\tconst handleMouseDown = useCallback(\n\t\t(pointIndex: number) => (e: React.MouseEvent) => {\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t\tonStartDragging(pointIndex);\n\t\t},\n\t\t[onStartDragging]\n\t);\n\n\t// 캔버스 클릭 (사각형 내부 클릭 감지)\n\tconst handleCanvasMouseDown = useCallback(\n\t\t(e: React.MouseEvent) => {\n\t\t\tif (!selectedArea || !containerRef.current) return;\n\n\t\t\tconst rect = containerRef.current.getBoundingClientRect();\n\t\t\tconst x = (e.clientX - rect.left) / rect.width;\n\t\t\tconst y = (e.clientY - rect.top) / rect.height;\n\t\t\tconst clickPoint = { x, y };\n\n\t\t\t// 사각형 내부를 클릭했는지 확인\n\t\t\tif (isPointInPolygon(clickPoint, selectedArea.basePoints)) {\n\t\t\t\tsetIsDraggingArea(true);\n\t\t\t\tsetDragStartPos(clickPoint);\n\t\t\t\te.preventDefault();\n\t\t\t}\n\t\t},\n\t\t[selectedArea, isPointInPolygon]\n\t);\n\n\tconst handleMouseMove = useCallback(\n\t\t(e: React.MouseEvent) => {\n\t\t\tif (!selectedArea || !containerRef.current) return;\n\n\t\t\tconst rect = containerRef.current.getBoundingClientRect();\n\t\t\tconst x = (e.clientX - rect.left) / rect.width;\n\t\t\tconst y = (e.clientY - rect.top) / rect.height;\n\n\t\t\t// 포인트 드래그 중\n\t\t\tif (draggingPointIndex !== null) {\n\t\t\t\tconst clampedX = Math.max(0, Math.min(1, x));\n\t\t\t\tconst clampedY = Math.max(0, Math.min(1, y));\n\t\t\t\tonUpdatePoint(selectedArea.id, draggingPointIndex, {x: clampedX, y: clampedY});\n\t\t\t}\n\t\t\t// 사각형 전체 드래그 중\n\t\t\telse if (isDraggingArea && dragStartPos) {\n\t\t\t\tconst deltaX = x - dragStartPos.x;\n\t\t\t\tconst deltaY = y - dragStartPos.y;\n\n\t\t\t\t// 모든 포인트를 delta만큼 이동\n\t\t\t\tconst newPoints = selectedArea.basePoints.map((point) => ({\n\t\t\t\t\tx: Math.max(0, Math.min(1, point.x + deltaX)),\n\t\t\t\t\ty: Math.max(0, Math.min(1, point.y + deltaY)),\n\t\t\t\t})) as [Point, Point, Point, Point];\n\n\t\t\t\tonUpdateArea(selectedArea.id, { basePoints: newPoints });\n\t\t\t\tsetDragStartPos({ x, y }); // 다음 프레임을 위해 시작 위치 업데이트\n\t\t\t}\n\t\t},\n\t\t[draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea]\n\t);\n\n\tconst handleMouseUp = useCallback(() => {\n\t\tif (draggingPointIndex !== null) {\n\t\t\tonStopDragging();\n\t\t}\n\t\tif (isDraggingArea) {\n\t\t\tsetIsDraggingArea(false);\n\t\t\tsetDragStartPos(null);\n\t\t}\n\t}, [draggingPointIndex, isDraggingArea, onStopDragging]);\n\n\t// 전역 마우스 업 이벤트\n\tuseEffect(() => {\n\t\tif (draggingPointIndex !== null || isDraggingArea) {\n\t\t\twindow.addEventListener('mouseup', handleMouseUp);\n\t\t\treturn () => window.removeEventListener('mouseup', handleMouseUp);\n\t\t}\n\t}, [draggingPointIndex, isDraggingArea, handleMouseUp]);\n\n\t// UV 좌표를 픽셀 좌표로 변환 (셰이더와 동일한 bilinear interpolation)\n\tconst uvToPixel = (\n\t\tu: number,\n\t\tv: number,\n\t\tpoints: [Point, Point, Point, Point],\n\t\tcanvasWidth: number,\n\t\tcanvasHeight: number\n\t): { x: number; y: number } => {\n\t\t// p0=좌상, p1=우상, p2=우하, p3=좌하\n\t\tconst [p0, p1, p2, p3] = points;\n\n\t\t// 셰이더 computeUV와 동일한 순서로 bilinear interpolation\n\t\t// left = mix(p0, p1, u) -> 상단 가장자리\n\t\t// right = mix(p3, p2, u) -> 하단 가장자리\n\t\t// position = mix(left, right, v)\n\t\tconst leftX = p0.x * (1 - u) + p1.x * u;\n\t\tconst leftY = p0.y * (1 - u) + p1.y * u;\n\n\t\tconst rightX = p3.x * (1 - u) + p2.x * u;\n\t\tconst rightY = p3.y * (1 - u) + p2.y * u;\n\n\t\tconst posX = leftX * (1 - v) + rightX * v;\n\t\tconst posY = leftY * (1 - v) + rightY * v;\n\n\t\treturn {\n\t\t\tx: posX * canvasWidth,\n\t\t\ty: posY * canvasHeight,\n\t\t};\n\t};\n\n\t// UV 좌표계의 원을 정확히 그리기 (찌그러진 원 형태)\n\tconst drawDistortionCircle = useCallback((\n\t\tctx: CanvasRenderingContext2D,\n\t\tpoints: [Point, Point, Point, Point],\n\t\tcanvasWidth: number,\n\t\tcanvasHeight: number\n\t) => {\n\t\tconst segments = 128; // 원을 128개 세그먼트로 촘촘히 분할\n\t\tconst centerU = 0.5;\n\t\tconst centerV = 0.5;\n\n\t\tconst circleLevels = editorStyle.circleLevels || [];\n\n\t\t// 원 레벨별로 그리기 (외부 -> 내부 순)\n\t\tcircleLevels.forEach((level, index) => {\n\t\t\tconst levelPoints: { x: number; y: number }[] = [];\n\t\t\tfor (let i = 0; i <= segments; i++) {\n\t\t\t\tconst theta = (i / segments) * 2 * Math.PI;\n\t\t\t\tconst u = centerU - level.radius * Math.sin(theta);\n\t\t\t\tconst v = centerV + level.radius * Math.cos(theta);\n\t\t\t\tconst pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight);\n\t\t\t\tlevelPoints.push(pixelPos);\n\t\t\t}\n\n\t\t\tctx.beginPath();\n\t\t\tctx.moveTo(levelPoints[0].x, levelPoints[0].y);\n\t\t\tfor (let i = 1; i < levelPoints.length; i++) {\n\t\t\t\tctx.lineTo(levelPoints[i].x, levelPoints[i].y);\n\t\t\t}\n\t\t\tctx.closePath();\n\n\t\t\t// 원 테두리\n\t\t\tconst baseColor = level.color || 'rgba(255, 200, 0, 1)';\n\t\t\t// baseColor에서 RGB 추출하고 opacity 적용\n\t\t\tconst colorWithOpacity = baseColor.replace(/rgba?\\(([^)]+)\\)/, (_, rgb) => {\n\t\t\t\tconst parts = rgb.split(',').map((p: string) => p.trim());\n\t\t\t\treturn `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${level.opacity})`;\n\t\t\t});\n\t\t\tctx.strokeStyle = colorWithOpacity;\n\t\t\tctx.lineWidth = level.lineWidth;\n\t\t\tif (level.dashPattern) {\n\t\t\t\tctx.setLineDash(level.dashPattern);\n\t\t\t}\n\t\t\tctx.stroke();\n\t\t\tctx.setLineDash([]);\n\n\t\t\t// 가장 외부 원만 내부 채우기\n\t\t\tif (index === 0 && editorStyle.circleFillColor) {\n\t\t\t\tctx.fillStyle = editorStyle.circleFillColor;\n\t\t\t\tctx.fill();\n\t\t\t}\n\t\t});\n\n\t\t// 중심점 표시\n\t\tconst centerPointStyle = editorStyle.centerPoint || {};\n\t\tconst centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight);\n\t\tctx.beginPath();\n\t\tctx.arc(centerPixel.x, centerPixel.y, centerPointStyle.radius || 5, 0, 2 * Math.PI);\n\t\tif (centerPointStyle.fillColor) {\n\t\t\tctx.fillStyle = centerPointStyle.fillColor;\n\t\t\tctx.fill();\n\t\t}\n\t\tif (centerPointStyle.strokeColor) {\n\t\t\tctx.strokeStyle = centerPointStyle.strokeColor;\n\t\t\tctx.lineWidth = centerPointStyle.strokeWidth || 2;\n\t\t\tctx.stroke();\n\t\t}\n\t}, [editorStyle]);\n\n\t// 커서 스타일 결정\n\tconst getCursorStyle = () => {\n\t\tif (draggingPointIndex !== null) return 'grabbing';\n\t\tif (isDraggingArea) return 'grabbing';\n\t\treturn 'default';\n\t};\n\n\treturn (\n\t\t\n\t\t\t{/* ImageDistortion 컴포넌트 */}\n\t\t\t\n\n\t\t\t{/* 오버레이 SVG */}\n\t\t\t\n\t\t\t\t{/* 모든 영역의 사각형 표시 */}\n\t\t\t\t{areas.map((area) => {\n\t\t\t\t\tconst isSelected = area.id === selectedAreaId;\n\t\t\t\t\tconst points = area.basePoints;\n\t\t\t\t\tconst outlineStyle = editorStyle.areaOutline || {};\n\t\t\t\t\treturn (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{/* 사각형 배경 및 경계선 */}\n\t\t\t\t\t\t\t `${p.x * canvasSize.width},${p.y * canvasSize.height}`)\n\t\t\t\t\t\t\t\t\t.join(' ')}\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)')}\n\t\t\t\t\t\t\t\tstroke={isSelected ? (outlineStyle.selectedColor || '#00aaff') : (outlineStyle.unselectedColor || '#888')}\n\t\t\t\t\t\t\t\tstrokeWidth={isSelected ? (outlineStyle.selectedWidth || 2) : (outlineStyle.unselectedWidth || 1)}\n\t\t\t\t\t\t\t\tstrokeDasharray={isSelected ? '0' : (outlineStyle.unselectedDashPattern?.join(',') || '5,5')}\n\t\t\t\t\t\t\t\topacity={isSelected ? 1 : 0.5}\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\n\t\t\t{/* 선택된 영역의 타원 가이드 (Canvas로 그리기) */}\n\t\t\t{selectedArea && canvasSize.width > 0 && (\n\t\t\t\t {\n\t\t\t\t\t\tif (canvas) {\n\t\t\t\t\t\t\tconst ctx = canvas.getContext('2d');\n\t\t\t\t\t\t\tif (ctx) {\n\t\t\t\t\t\t\t\tctx.clearRect(0, 0, canvasSize.width, canvasSize.height);\n\t\t\t\t\t\t\t\tdrawDistortionCircle(ctx, selectedArea.basePoints, canvasSize.width, canvasSize.height);\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\n\t\t\t{/* 선택된 영역의 포인트 핸들 */}\n\t\t\t{selectedArea &&\n\t\t\t\tselectedArea.basePoints.map((point, index) => {\n\t\t\t\t\tconst handleStyle = editorStyle.pointHandle || {};\n\t\t\t\t\treturn (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tP{index + 1}\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\n\t);\n};\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","import React from 'react';\nimport { DistortionArea } from '../../types/area';\n\ninterface 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\ninterface 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{/* 벡터 A (X) */}\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: {\n\t\t\t\t\t\t\t\t...area.movement,\n\t\t\t\t\t\t\t\tvectorA: { ...area.movement.vectorA, x: parseFloat(e.target.value) },\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\tclassName=\"slider\"\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{/* 벡터 A (Y) */}\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: {\n\t\t\t\t\t\t\t\t...area.movement,\n\t\t\t\t\t\t\t\tvectorA: { ...area.movement.vectorA, y: parseFloat(e.target.value) },\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\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{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"],"mappings":"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","names":["useEffect","useRef","THREE","useRef","useEffect","useEffect","useState","useCallback","useRef","useEffect","useState","useCallback","jsx","useRef","useState","useEffect","useCallback","jsx","jsxs","jsx","jsxs","jsx","jsxs","useEffect"]} \ No newline at end of file diff --git a/src/editor/DistortionEditor.tsx b/src/editor/DistortionEditor.tsx index 281ea73..b1cb9bb 100644 --- a/src/editor/DistortionEditor.tsx +++ b/src/editor/DistortionEditor.tsx @@ -14,6 +14,7 @@ export const DistortionEditor: React.FC = ({ onSelectedAreaChange, width = 800, height = 600, + canvasStyle, }) => { const { state, @@ -85,6 +86,7 @@ export const DistortionEditor: React.FC = ({ draggingPointIndex={state.draggingPointIndex} onStartDragging={startDragging} onStopDragging={stopDragging} + style={canvasStyle} /> diff --git a/src/editor/components/EditorCanvas.tsx b/src/editor/components/EditorCanvas.tsx index 4428dd8..7c7dba7 100644 --- a/src/editor/components/EditorCanvas.tsx +++ b/src/editor/components/EditorCanvas.tsx @@ -1,6 +1,8 @@ -import React, {useRef, useEffect, useState, useCallback} from 'react'; +import React, {useRef, useEffect, useState, useCallback, useMemo} from 'react'; import {DistortionArea, Point} from '../../types/area'; import {ImageDistortion} from '../../components/ImageDistortion'; +import {EditorCanvasStyle} from '../types'; +import {DEFAULT_EDITOR_CANVAS_STYLE} from '../constants'; interface EditorCanvasProps { areas: DistortionArea[]; @@ -13,6 +15,8 @@ interface EditorCanvasProps { draggingPointIndex: number | null; onStartDragging: (pointIndex: number) => void; onStopDragging: () => void; + /** 에디터 캔버스 스타일 커스터마이징 */ + style?: EditorCanvasStyle; } export const EditorCanvas: React.FC = ({ @@ -26,12 +30,32 @@ export const EditorCanvas: React.FC = ({ draggingPointIndex, onStartDragging, onStopDragging, + style: customStyle, }) => { const containerRef = useRef(null); const [canvasSize, setCanvasSize] = useState({width: 0, height: 0}); const [isDraggingArea, setIsDraggingArea] = useState(false); const [dragStartPos, setDragStartPos] = useState(null); + // 스타일 병합 (커스텀 스타일 우선) + const editorStyle = useMemo(() => ({ + ...DEFAULT_EDITOR_CANVAS_STYLE, + ...customStyle, + circleLevels: customStyle?.circleLevels || DEFAULT_EDITOR_CANVAS_STYLE.circleLevels, + centerPoint: { + ...DEFAULT_EDITOR_CANVAS_STYLE.centerPoint, + ...customStyle?.centerPoint, + }, + pointHandle: { + ...DEFAULT_EDITOR_CANVAS_STYLE.pointHandle, + ...customStyle?.pointHandle, + }, + areaOutline: { + ...DEFAULT_EDITOR_CANVAS_STYLE.areaOutline, + ...customStyle?.areaOutline, + }, + }), [customStyle]); + // 컨테이너 크기 측정 useEffect(() => { if (!containerRef.current) return; @@ -167,7 +191,7 @@ export const EditorCanvas: React.FC = ({ }; // UV 좌표계의 원을 정확히 그리기 (찌그러진 원 형태) - const drawDistortionCircle = ( + const drawDistortionCircle = useCallback(( ctx: CanvasRenderingContext2D, points: [Point, Point, Point, Point], canvasWidth: number, @@ -176,74 +200,64 @@ export const EditorCanvas: React.FC = ({ const segments = 128; // 원을 128개 세그먼트로 촘촘히 분할 const centerU = 0.5; const centerV = 0.5; - const maxRadius = 0.5; // UV 좌표계에서 최대 반지름 0.5 (셰이더의 maxUvRadius) - // 원 위의 점들을 UV 좌표로 샘플링 후 픽셀 좌표로 변환 - // 4가지 조합을 모두 테스트 (사용자가 이미지에서 P1-P3 대각선으로 늘렸을 때 왜곡도 같은 방향이어야 함) - const circlePoints: { x: number; y: number }[] = []; - for (let i = 0; i <= segments; i++) { - const theta = (i / segments) * 2 * Math.PI; + const circleLevels = editorStyle.circleLevels || []; - // 테스트: u=-sin, v=cos (-90도 회전, P1-P3 방향에 맞춤) - const u = centerU - maxRadius * Math.sin(theta); - const v = centerV + maxRadius * Math.cos(theta); - - const pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight); - circlePoints.push(pixelPos); - } - - // 찌그러진 원 그리기 (실제 왜곡 적용 경계) - ctx.beginPath(); - ctx.moveTo(circlePoints[0].x, circlePoints[0].y); - for (let i = 1; i < circlePoints.length; i++) { - ctx.lineTo(circlePoints[i].x, circlePoints[i].y); - } - ctx.closePath(); - ctx.strokeStyle = 'rgba(255, 200, 0, 0.9)'; - ctx.lineWidth = 3; - ctx.setLineDash([8, 4]); - ctx.stroke(); - ctx.setLineDash([]); - - // 내부를 반투명하게 채우기 - ctx.fillStyle = 'rgba(255, 200, 0, 0.12)'; - ctx.fill(); - - // 영향력 그라디언트를 나타내는 추가 원들 (0.25, 0.375 반지름) - for (const r of [0.25, 0.375]) { - const gradientPoints: { x: number; y: number }[] = []; + // 원 레벨별로 그리기 (외부 -> 내부 순) + circleLevels.forEach((level, index) => { + const levelPoints: { x: number; y: number }[] = []; for (let i = 0; i <= segments; i++) { const theta = (i / segments) * 2 * Math.PI; - const u = centerU - r * Math.sin(theta); - const v = centerV + r * Math.cos(theta); + const u = centerU - level.radius * Math.sin(theta); + const v = centerV + level.radius * Math.cos(theta); const pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight); - gradientPoints.push(pixelPos); + levelPoints.push(pixelPos); } ctx.beginPath(); - ctx.moveTo(gradientPoints[0].x, gradientPoints[0].y); - for (let i = 1; i < gradientPoints.length; i++) { - ctx.lineTo(gradientPoints[i].x, gradientPoints[i].y); + ctx.moveTo(levelPoints[0].x, levelPoints[0].y); + for (let i = 1; i < levelPoints.length; i++) { + ctx.lineTo(levelPoints[i].x, levelPoints[i].y); } ctx.closePath(); - const alpha = r / maxRadius; - ctx.strokeStyle = `rgba(255, 220, 100, ${0.3 * alpha})`; - ctx.lineWidth = 1; - ctx.setLineDash([3, 3]); + + // 원 테두리 + const baseColor = level.color || 'rgba(255, 200, 0, 1)'; + // baseColor에서 RGB 추출하고 opacity 적용 + const colorWithOpacity = baseColor.replace(/rgba?\(([^)]+)\)/, (_, rgb) => { + const parts = rgb.split(',').map((p: string) => p.trim()); + return `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${level.opacity})`; + }); + ctx.strokeStyle = colorWithOpacity; + ctx.lineWidth = level.lineWidth; + if (level.dashPattern) { + ctx.setLineDash(level.dashPattern); + } ctx.stroke(); ctx.setLineDash([]); - } + + // 가장 외부 원만 내부 채우기 + if (index === 0 && editorStyle.circleFillColor) { + ctx.fillStyle = editorStyle.circleFillColor; + ctx.fill(); + } + }); // 중심점 표시 + const centerPointStyle = editorStyle.centerPoint || {}; const centerPixel = uvToPixel(centerU, centerV, points, canvasWidth, canvasHeight); ctx.beginPath(); - ctx.arc(centerPixel.x, centerPixel.y, 5, 0, 2 * Math.PI); - ctx.fillStyle = 'rgba(255, 200, 0, 1)'; - ctx.fill(); - ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)'; - ctx.lineWidth = 2; - ctx.stroke(); - }; + ctx.arc(centerPixel.x, centerPixel.y, centerPointStyle.radius || 5, 0, 2 * Math.PI); + if (centerPointStyle.fillColor) { + ctx.fillStyle = centerPointStyle.fillColor; + ctx.fill(); + } + if (centerPointStyle.strokeColor) { + ctx.strokeStyle = centerPointStyle.strokeColor; + ctx.lineWidth = centerPointStyle.strokeWidth || 2; + ctx.stroke(); + } + }, [editorStyle]); // 커서 스타일 결정 const getCursorStyle = () => { @@ -261,7 +275,7 @@ export const EditorCanvas: React.FC = ({ onMouseMove={handleMouseMove} > {/* ImageDistortion 컴포넌트 */} - + {/* 오버레이 SVG */} = ({ {areas.map((area) => { const isSelected = area.id === selectedAreaId; const points = area.basePoints; + const outlineStyle = editorStyle.areaOutline || {}; return ( - {/* 사각형 경계선 */} + {/* 사각형 배경 및 경계선 */} `${p.x * canvasSize.width},${p.y * canvasSize.height}`) .join(' ')} - fill="none" - stroke={isSelected ? '#00aaff' : '#888'} - strokeWidth={isSelected ? 2 : 1} - strokeDasharray={isSelected ? '0' : '5,5'} + fill={isSelected ? (outlineStyle.selectedFillColor || 'rgba(0, 170, 255, 0.08)') : (outlineStyle.unselectedFillColor || 'rgba(136, 136, 136, 0.03)')} + stroke={isSelected ? (outlineStyle.selectedColor || '#00aaff') : (outlineStyle.unselectedColor || '#888')} + strokeWidth={isSelected ? (outlineStyle.selectedWidth || 2) : (outlineStyle.unselectedWidth || 1)} + strokeDasharray={isSelected ? '0' : (outlineStyle.unselectedDashPattern?.join(',') || '5,5')} opacity={isSelected ? 1 : 0.5} /> @@ -323,43 +338,46 @@ export const EditorCanvas: React.FC = ({ {/* 선택된 영역의 포인트 핸들 */} {selectedArea && - selectedArea.basePoints.map((point, index) => ( -
+ selectedArea.basePoints.map((point, index) => { + const handleStyle = editorStyle.pointHandle || {}; + return (
- P{index + 1} +
+ P{index + 1} +
-
- ))} + ); + })} ); }; diff --git a/src/editor/index.ts b/src/editor/index.ts index 47b08c8..d7c11e8 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -1,4 +1,14 @@ export { DistortionEditor } from './DistortionEditor'; -export type { DistortionEditorProps, EditorState, EditMode } from './types'; +export type { + DistortionEditorProps, + EditorState, + EditMode, + EditorCanvasStyle, + CircleLevelStyle, + CenterPointStyle, + PointHandleStyle, + AreaOutlineStyle, +} from './types'; export { useDistortionEditor } from './hooks/useDistortionEditor'; +export { DEFAULT_EDITOR_CANVAS_STYLE } from './constants'; import './editor.css'; diff --git a/src/editor/types.ts b/src/editor/types.ts index d5c02f8..6ebed2a 100644 --- a/src/editor/types.ts +++ b/src/editor/types.ts @@ -32,6 +32,90 @@ export interface PointHandle { label: string; } +/** + * 왜곡 영역 원 레벨 스타일 + */ +export interface CircleLevelStyle { + /** 반지름 (0.0 - 1.0, UV 좌표) */ + radius: number; + /** 투명도 (0.0 - 1.0) */ + opacity: number; + /** 선 두께 (픽셀) */ + lineWidth: number; + /** 선 색상 (CSS color) */ + color?: string; + /** 대시 패턴 [dash, gap] */ + dashPattern?: [number, number]; +} + +/** + * 중심점 스타일 + */ +export interface CenterPointStyle { + /** 반지름 (픽셀) */ + radius?: number; + /** 채우기 색상 */ + fillColor?: string; + /** 테두리 색상 */ + strokeColor?: string; + /** 테두리 두께 */ + strokeWidth?: number; +} + +/** + * 포인트 핸들 스타일 + */ +export interface PointHandleStyle { + /** 핸들 크기 (픽셀) */ + size?: number; + /** 채우기 색상 */ + fillColor?: string; + /** 테두리 색상 */ + strokeColor?: string; + /** 테두리 두께 */ + strokeWidth?: number; + /** 레이블 색상 */ + labelColor?: string; + /** 레이블 폰트 크기 */ + labelFontSize?: number; +} + +/** + * 영역 외곽선 스타일 + */ +export interface AreaOutlineStyle { + /** 선택된 영역 색상 */ + selectedColor?: string; + /** 선택되지 않은 영역 색상 */ + unselectedColor?: string; + /** 선택된 영역 선 두께 */ + selectedWidth?: number; + /** 선택되지 않은 영역 선 두께 */ + unselectedWidth?: number; + /** 선택되지 않은 영역 대시 패턴 */ + unselectedDashPattern?: [number, number]; + /** 선택된 영역 배경 채우기 색상 */ + selectedFillColor?: string; + /** 선택되지 않은 영역 배경 채우기 색상 */ + unselectedFillColor?: string; +} + +/** + * 에디터 캔버스 스타일 + */ +export interface EditorCanvasStyle { + /** 왜곡 영역 원 레벨 스타일 배열 (외부 -> 내부 순) */ + circleLevels?: CircleLevelStyle[]; + /** 왜곡 영역 내부 채우기 색상 */ + circleFillColor?: string; + /** 중심점 스타일 */ + centerPoint?: CenterPointStyle; + /** 포인트 핸들 스타일 */ + pointHandle?: PointHandleStyle; + /** 영역 외곽선 스타일 */ + areaOutline?: AreaOutlineStyle; +} + /** * 에디터 Props */ @@ -48,4 +132,6 @@ export interface DistortionEditorProps { width?: number; /** 캔버스 높이 */ height?: number; + /** 에디터 캔버스 스타일 커스터마이징 */ + canvasStyle?: EditorCanvasStyle; }