From e66b078dd8bc80e60ee5d0f417fcd43035dad355 Mon Sep 17 00:00:00 2001 From: BaekRyang Date: Wed, 5 Nov 2025 11:20:53 +0900 Subject: [PATCH] build: Update compiled distribution files - Update CJS and ESM bundles with coordinate system fixes - Update type definitions (d.ts, d.mts) - Add editor styles (CSS) - Update shader files - Update source maps --- dist/distortion.frag.glsl | 11 +- dist/index.css | 298 +++++++++++++++++ dist/index.css.map | 1 + dist/index.d.mts | 59 +++- dist/index.d.ts | 59 +++- dist/index.js | 655 +++++++++++++++++++++++++++++++++++++- dist/index.js.map | 2 +- dist/index.mjs | 651 ++++++++++++++++++++++++++++++++++++- dist/index.mjs.map | 2 +- 9 files changed, 1721 insertions(+), 17 deletions(-) create mode 100644 dist/index.css create mode 100644 dist/index.css.map diff --git a/dist/distortion.frag.glsl b/dist/distortion.frag.glsl index 9d8ff6f..20e6c31 100644 --- a/dist/distortion.frag.glsl +++ b/dist/distortion.frag.glsl @@ -1,8 +1,8 @@ uniform vec2 u_resolution; uniform sampler2D u_texture; -uniform vec2 u_points[32]; // 최대 8영역 × 4포인트 (정규화된 좌표) +uniform vec2 u_points[32]; // 최대 8영역 × 4포인트 (정규화된 좌표 0-1) uniform int u_numAreas; -uniform vec2 u_dragVectors[8]; // 정규화된 좌표 +uniform vec2 u_dragVectors[8]; // 드래그 벡터 (정규화된 좌표 0-1) uniform float u_distortionStrengths[8]; varying vec2 vUv; @@ -72,10 +72,9 @@ void main() { if (distToCenter < maxUvRadius) { float influence = 1.0 - smoothstep(0.0, maxUvRadius, distToCenter); - // dragVector도 정규화된 좌표이므로 픽셀로 변환 - vec2 distortion = (u_dragVectors[i] * u_resolution) * influence * u_distortionStrengths[i]; - // texCoord는 이미 정규화된 좌표이므로 정규화된 왜곡 적용 - texCoord += distortion / u_resolution; + // dragVector는 정규화된 좌표(0-1)이므로 바로 사용 (Flutter와 동일한 결과) + vec2 distortion = u_dragVectors[i] * influence * u_distortionStrengths[i]; + texCoord += distortion; texCoord = clamp(texCoord, 0.0, 1.0); } found = true; diff --git a/dist/index.css b/dist/index.css new file mode 100644 index 0000000..9598696 --- /dev/null +++ b/dist/index.css @@ -0,0 +1,298 @@ +/* src/editor/editor.css */ +.distortion-editor { + font-family: + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + sans-serif; + background: #1e1e1e; + color: #e0e0e0; + min-height: 100vh; + padding: 20px; +} +.editor-main { + display: flex; + gap: 20px; + max-width: 1600px; + margin: 0 auto; +} +.editor-canvas-container { + flex: 1; + background: #2a2a2a; + border-radius: 8px; + padding: 20px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} +.editor-canvas { + position: relative; + background: #000; + border-radius: 4px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5); +} +.editor-sidebar { + width: 320px; + display: flex; + flex-direction: column; + gap: 20px; +} +.area-list { + background: #2a2a2a; + border-radius: 8px; + padding: 16px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} +.area-list-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} +.area-list-header h3 { + margin: 0; + font-size: 16px; + font-weight: 600; + color: #fff; +} +.btn-add { + padding: 6px 12px; + background: #00aaff; + color: white; + border: none; + border-radius: 4px; + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: background 0.2s; +} +.btn-add:hover:not(:disabled) { + background: #0088cc; +} +.btn-add:disabled { + background: #555; + cursor: not-allowed; + opacity: 0.5; +} +.area-list-items { + display: flex; + flex-direction: column; + gap: 8px; + max-height: 300px; + overflow-y: auto; +} +.area-list-empty { + text-align: center; + color: #888; + padding: 20px; + font-size: 13px; +} +.area-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 12px; + background: #383838; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s; + border: 2px solid transparent; +} +.area-item:hover { + background: #404040; +} +.area-item.selected { + background: #2d5a7a; + border-color: #00aaff; +} +.area-item-info { + display: flex; + flex-direction: column; + gap: 4px; +} +.area-item-name { + font-size: 14px; + font-weight: 500; + color: #fff; +} +.area-item-strength { + font-size: 12px; + color: #aaa; +} +.btn-remove { + width: 24px; + height: 24px; + background: #ff4444; + color: white; + border: none; + border-radius: 4px; + font-size: 18px; + line-height: 1; + cursor: pointer; + transition: background 0.2s; + display: flex; + align-items: center; + justify-content: center; +} +.btn-remove:hover { + background: #cc0000; +} +.parameter-panel { + background: #2a2a2a; + border-radius: 8px; + padding: 16px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + flex: 1; + overflow-y: auto; +} +.parameter-panel h3 { + margin: 0 0 16px 0; + font-size: 16px; + font-weight: 600; + color: #fff; +} +.parameter-panel-empty { + text-align: center; + color: #888; + padding: 40px 20px; + font-size: 13px; +} +.parameter-group { + margin-bottom: 20px; +} +.parameter-group label { + display: block; + font-size: 13px; + font-weight: 500; + color: #ccc; + margin-bottom: 8px; +} +.slider { + width: 100%; + height: 6px; + border-radius: 3px; + background: #444; + outline: none; + -webkit-appearance: none; +} +.slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: #00aaff; + cursor: pointer; + transition: background 0.2s; +} +.slider::-webkit-slider-thumb:hover { + background: #0088cc; +} +.slider::-moz-range-thumb { + width: 16px; + height: 16px; + border-radius: 50%; + background: #00aaff; + cursor: pointer; + border: none; + transition: background 0.2s; +} +.slider::-moz-range-thumb:hover { + background: #0088cc; +} +.input-number { + width: 100%; + padding: 8px; + background: #383838; + border: 1px solid #555; + border-radius: 4px; + color: #fff; + font-size: 14px; + outline: none; + transition: border-color 0.2s; +} +.input-number:focus { + border-color: #00aaff; +} +.select { + width: 100%; + padding: 8px; + background: #383838; + border: 1px solid #555; + border-radius: 4px; + color: #fff; + font-size: 14px; + outline: none; + cursor: pointer; + transition: border-color 0.2s; +} +.select:focus { + border-color: #00aaff; +} +.points-display { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + margin-top: 8px; +} +.point-coord { + padding: 8px; + background: #383838; + border-radius: 4px; + font-size: 11px; + font-family: "Courier New", monospace; + color: #aaa; +} +.point-handle { + z-index: 10; + transition: transform 0.1s, box-shadow 0.1s; +} +.point-handle:hover { + transform: translate(-50%, -50%) scale(1.2); + box-shadow: 0 4px 8px rgba(0, 170, 255, 0.5); +} +.point-handle.dragging { + cursor: grabbing; + transform: translate(-50%, -50%) scale(1.3); + box-shadow: 0 6px 12px rgba(0, 170, 255, 0.7); +} +.area-list-items::-webkit-scrollbar, +.parameter-panel::-webkit-scrollbar { + width: 8px; +} +.area-list-items::-webkit-scrollbar-track, +.parameter-panel::-webkit-scrollbar-track { + background: #1e1e1e; + border-radius: 4px; +} +.area-list-items::-webkit-scrollbar-thumb, +.parameter-panel::-webkit-scrollbar-thumb { + background: #555; + border-radius: 4px; +} +.area-list-items::-webkit-scrollbar-thumb:hover, +.parameter-panel::-webkit-scrollbar-thumb:hover { + background: #666; +} +@media (max-width: 1200px) { + .editor-main { + flex-direction: column; + } + .editor-sidebar { + width: 100%; + flex-direction: row; + } + .area-list, + .parameter-panel { + flex: 1; + } +} +@media (max-width: 768px) { + .editor-sidebar { + flex-direction: column; + } + .points-display { + grid-template-columns: 1fr; + } +} +/*# sourceMappingURL=index.css.map */ \ No newline at end of file diff --git a/dist/index.css.map b/dist/index.css.map new file mode 100644 index 0000000..bd61791 --- /dev/null +++ b/dist/index.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/editor/editor.css"],"sourcesContent":["/* Distortion Editor 메인 레이아웃 */\n.distortion-editor {\n\tfont-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n\tbackground: #1e1e1e;\n\tcolor: #e0e0e0;\n\tmin-height: 100vh;\n\tpadding: 20px;\n}\n\n.editor-main {\n\tdisplay: flex;\n\tgap: 20px;\n\tmax-width: 1600px;\n\tmargin: 0 auto;\n}\n\n.editor-canvas-container {\n\tflex: 1;\n\tbackground: #2a2a2a;\n\tborder-radius: 8px;\n\tpadding: 20px;\n\tbox-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n}\n\n.editor-canvas {\n\tposition: relative;\n\tbackground: #000;\n\tborder-radius: 4px;\n\toverflow: hidden;\n\tbox-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);\n}\n\n.editor-sidebar {\n\twidth: 320px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 20px;\n}\n\n/* Area List */\n.area-list {\n\tbackground: #2a2a2a;\n\tborder-radius: 8px;\n\tpadding: 16px;\n\tbox-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n}\n\n.area-list-header {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tmargin-bottom: 12px;\n}\n\n.area-list-header h3 {\n\tmargin: 0;\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tcolor: #fff;\n}\n\n.btn-add {\n\tpadding: 6px 12px;\n\tbackground: #00aaff;\n\tcolor: white;\n\tborder: none;\n\tborder-radius: 4px;\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\ttransition: background 0.2s;\n}\n\n.btn-add:hover:not(:disabled) {\n\tbackground: #0088cc;\n}\n\n.btn-add:disabled {\n\tbackground: #555;\n\tcursor: not-allowed;\n\topacity: 0.5;\n}\n\n.area-list-items {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmax-height: 300px;\n\toverflow-y: auto;\n}\n\n.area-list-empty {\n\ttext-align: center;\n\tcolor: #888;\n\tpadding: 20px;\n\tfont-size: 13px;\n}\n\n.area-item {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tpadding: 10px 12px;\n\tbackground: #383838;\n\tborder-radius: 4px;\n\tcursor: pointer;\n\ttransition: all 0.2s;\n\tborder: 2px solid transparent;\n}\n\n.area-item:hover {\n\tbackground: #404040;\n}\n\n.area-item.selected {\n\tbackground: #2d5a7a;\n\tborder-color: #00aaff;\n}\n\n.area-item-info {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.area-item-name {\n\tfont-size: 14px;\n\tfont-weight: 500;\n\tcolor: #fff;\n}\n\n.area-item-strength {\n\tfont-size: 12px;\n\tcolor: #aaa;\n}\n\n.btn-remove {\n\twidth: 24px;\n\theight: 24px;\n\tbackground: #ff4444;\n\tcolor: white;\n\tborder: none;\n\tborder-radius: 4px;\n\tfont-size: 18px;\n\tline-height: 1;\n\tcursor: pointer;\n\ttransition: background 0.2s;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n.btn-remove:hover {\n\tbackground: #cc0000;\n}\n\n/* Parameter Panel */\n.parameter-panel {\n\tbackground: #2a2a2a;\n\tborder-radius: 8px;\n\tpadding: 16px;\n\tbox-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n\tflex: 1;\n\toverflow-y: auto;\n}\n\n.parameter-panel h3 {\n\tmargin: 0 0 16px 0;\n\tfont-size: 16px;\n\tfont-weight: 600;\n\tcolor: #fff;\n}\n\n.parameter-panel-empty {\n\ttext-align: center;\n\tcolor: #888;\n\tpadding: 40px 20px;\n\tfont-size: 13px;\n}\n\n.parameter-group {\n\tmargin-bottom: 20px;\n}\n\n.parameter-group label {\n\tdisplay: block;\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcolor: #ccc;\n\tmargin-bottom: 8px;\n}\n\n.slider {\n\twidth: 100%;\n\theight: 6px;\n\tborder-radius: 3px;\n\tbackground: #444;\n\toutline: none;\n\t-webkit-appearance: none;\n}\n\n.slider::-webkit-slider-thumb {\n\t-webkit-appearance: none;\n\tappearance: none;\n\twidth: 16px;\n\theight: 16px;\n\tborder-radius: 50%;\n\tbackground: #00aaff;\n\tcursor: pointer;\n\ttransition: background 0.2s;\n}\n\n.slider::-webkit-slider-thumb:hover {\n\tbackground: #0088cc;\n}\n\n.slider::-moz-range-thumb {\n\twidth: 16px;\n\theight: 16px;\n\tborder-radius: 50%;\n\tbackground: #00aaff;\n\tcursor: pointer;\n\tborder: none;\n\ttransition: background 0.2s;\n}\n\n.slider::-moz-range-thumb:hover {\n\tbackground: #0088cc;\n}\n\n.input-number {\n\twidth: 100%;\n\tpadding: 8px;\n\tbackground: #383838;\n\tborder: 1px solid #555;\n\tborder-radius: 4px;\n\tcolor: #fff;\n\tfont-size: 14px;\n\toutline: none;\n\ttransition: border-color 0.2s;\n}\n\n.input-number:focus {\n\tborder-color: #00aaff;\n}\n\n.select {\n\twidth: 100%;\n\tpadding: 8px;\n\tbackground: #383838;\n\tborder: 1px solid #555;\n\tborder-radius: 4px;\n\tcolor: #fff;\n\tfont-size: 14px;\n\toutline: none;\n\tcursor: pointer;\n\ttransition: border-color 0.2s;\n}\n\n.select:focus {\n\tborder-color: #00aaff;\n}\n\n.points-display {\n\tdisplay: grid;\n\tgrid-template-columns: 1fr 1fr;\n\tgap: 8px;\n\tmargin-top: 8px;\n}\n\n.point-coord {\n\tpadding: 8px;\n\tbackground: #383838;\n\tborder-radius: 4px;\n\tfont-size: 11px;\n\tfont-family: 'Courier New', monospace;\n\tcolor: #aaa;\n}\n\n/* Point Handle */\n.point-handle {\n\tz-index: 10;\n\ttransition: transform 0.1s, box-shadow 0.1s;\n}\n\n.point-handle:hover {\n\ttransform: translate(-50%, -50%) scale(1.2);\n\tbox-shadow: 0 4px 8px rgba(0, 170, 255, 0.5);\n}\n\n.point-handle.dragging {\n\tcursor: grabbing;\n\ttransform: translate(-50%, -50%) scale(1.3);\n\tbox-shadow: 0 6px 12px rgba(0, 170, 255, 0.7);\n}\n\n/* 스크롤바 스타일 */\n.area-list-items::-webkit-scrollbar,\n.parameter-panel::-webkit-scrollbar {\n\twidth: 8px;\n}\n\n.area-list-items::-webkit-scrollbar-track,\n.parameter-panel::-webkit-scrollbar-track {\n\tbackground: #1e1e1e;\n\tborder-radius: 4px;\n}\n\n.area-list-items::-webkit-scrollbar-thumb,\n.parameter-panel::-webkit-scrollbar-thumb {\n\tbackground: #555;\n\tborder-radius: 4px;\n}\n\n.area-list-items::-webkit-scrollbar-thumb:hover,\n.parameter-panel::-webkit-scrollbar-thumb:hover {\n\tbackground: #666;\n}\n\n/* 반응형 */\n@media (max-width: 1200px) {\n\t.editor-main {\n\t\tflex-direction: column;\n\t}\n\n\t.editor-sidebar {\n\t\twidth: 100%;\n\t\tflex-direction: row;\n\t}\n\n\t.area-list,\n\t.parameter-panel {\n\t\tflex: 1;\n\t}\n}\n\n@media (max-width: 768px) {\n\t.editor-sidebar {\n\t\tflex-direction: column;\n\t}\n\n\t.points-display {\n\t\tgrid-template-columns: 1fr;\n\t}\n}\n"],"mappings":"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","names":[]} \ No newline at end of file diff --git a/dist/index.d.mts b/dist/index.d.mts index 487732d..c2f32ee 100644 --- a/dist/index.d.mts +++ b/dist/index.d.mts @@ -132,6 +132,56 @@ interface ImageDistortionProps { */ declare const ImageDistortion: React.FC; +/** + * 에디터 편집 모드 + */ +type EditMode = 'normal' | 'point-edit' | 'parameter-edit'; +/** + * 에디터 상태 + */ +interface EditorState { + /** 현재 선택된 영역 ID */ + selectedAreaId: string | null; + /** 모든 왜곡 영역 */ + areas: DistortionArea[]; + /** 현재 편집 모드 */ + editMode: EditMode; + /** 드래그 중인 포인트 인덱스 (0-3) */ + draggingPointIndex: number | null; +} +/** + * 에디터 Props + */ +interface DistortionEditorProps { + /** 초기 영역 배열 */ + initialAreas?: DistortionArea[]; + /** 이미지 소스 */ + imageSrc: string; + /** 영역 변경 콜백 */ + onAreasChange?: (areas: DistortionArea[]) => void; + /** 선택된 영역 변경 콜백 */ + onSelectedAreaChange?: (areaId: string | null) => void; + /** 캔버스 너비 */ + width?: number; + /** 캔버스 높이 */ + height?: number; +} + +declare const DistortionEditor: React.FC; + +declare const useDistortionEditor: (initialAreas?: DistortionArea[]) => { + state: EditorState; + selectArea: (areaId: string | null) => void; + addArea: (area: DistortionArea) => void; + removeArea: (areaId: string) => void; + updateArea: (areaId: string, updates: Partial) => void; + updatePoint: (areaId: string, pointIndex: number, point: Point) => void; + startDragging: (pointIndex: number) => void; + stopDragging: () => void; + setEditMode: (mode: EditorState["editMode"]) => void; + getSelectedArea: () => DistortionArea | null; +}; + /** * 진행도에 이징 함수를 적용 * @param progress 진행도 (0.0 - 1.0) @@ -214,6 +264,13 @@ declare class ThreeScene { * 씬 렌더링 */ render(): void; + /** + * 현재 해상도 가져오기 + */ + getResolution(): { + x: number; + y: number; + }; /** * 리소스 정리 */ @@ -272,4 +329,4 @@ declare class AnimationLoop { */ declare const useAnimationFrame: (callback: (deltaTime: number) => void, isPlaying?: boolean) => void; -export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, DEFAULT_AREA, type DistortionArea, type DistortionMovement, type EasingFunction, ImageDistortion, type ImageDistortionProps, type Point, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, ThreeScene, applyEasing, useAnimationFrame }; +export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, DEFAULT_AREA, type DistortionArea, DistortionEditor, type DistortionEditorProps, type DistortionMovement, type EasingFunction, type EditMode, type EditorState, ImageDistortion, type ImageDistortionProps, type Point, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, ThreeScene, applyEasing, useAnimationFrame, useDistortionEditor }; diff --git a/dist/index.d.ts b/dist/index.d.ts index 487732d..c2f32ee 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -132,6 +132,56 @@ interface ImageDistortionProps { */ declare const ImageDistortion: React.FC; +/** + * 에디터 편집 모드 + */ +type EditMode = 'normal' | 'point-edit' | 'parameter-edit'; +/** + * 에디터 상태 + */ +interface EditorState { + /** 현재 선택된 영역 ID */ + selectedAreaId: string | null; + /** 모든 왜곡 영역 */ + areas: DistortionArea[]; + /** 현재 편집 모드 */ + editMode: EditMode; + /** 드래그 중인 포인트 인덱스 (0-3) */ + draggingPointIndex: number | null; +} +/** + * 에디터 Props + */ +interface DistortionEditorProps { + /** 초기 영역 배열 */ + initialAreas?: DistortionArea[]; + /** 이미지 소스 */ + imageSrc: string; + /** 영역 변경 콜백 */ + onAreasChange?: (areas: DistortionArea[]) => void; + /** 선택된 영역 변경 콜백 */ + onSelectedAreaChange?: (areaId: string | null) => void; + /** 캔버스 너비 */ + width?: number; + /** 캔버스 높이 */ + height?: number; +} + +declare const DistortionEditor: React.FC; + +declare const useDistortionEditor: (initialAreas?: DistortionArea[]) => { + state: EditorState; + selectArea: (areaId: string | null) => void; + addArea: (area: DistortionArea) => void; + removeArea: (areaId: string) => void; + updateArea: (areaId: string, updates: Partial) => void; + updatePoint: (areaId: string, pointIndex: number, point: Point) => void; + startDragging: (pointIndex: number) => void; + stopDragging: () => void; + setEditMode: (mode: EditorState["editMode"]) => void; + getSelectedArea: () => DistortionArea | null; +}; + /** * 진행도에 이징 함수를 적용 * @param progress 진행도 (0.0 - 1.0) @@ -214,6 +264,13 @@ declare class ThreeScene { * 씬 렌더링 */ render(): void; + /** + * 현재 해상도 가져오기 + */ + getResolution(): { + x: number; + y: number; + }; /** * 리소스 정리 */ @@ -272,4 +329,4 @@ declare class AnimationLoop { */ declare const useAnimationFrame: (callback: (deltaTime: number) => void, isPlaying?: boolean) => void; -export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, DEFAULT_AREA, type DistortionArea, type DistortionMovement, type EasingFunction, ImageDistortion, type ImageDistortionProps, type Point, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, ThreeScene, applyEasing, useAnimationFrame }; +export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, DEFAULT_AREA, type DistortionArea, DistortionEditor, type DistortionEditorProps, type DistortionMovement, type EasingFunction, type EditMode, type EditorState, ImageDistortion, type ImageDistortionProps, type Point, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, ThreeScene, applyEasing, useAnimationFrame, useDistortionEditor }; diff --git a/dist/index.js b/dist/index.js index 72d08b5..f67d8fe 100644 --- a/dist/index.js +++ b/dist/index.js @@ -33,12 +33,14 @@ __export(index_exports, { ANIMATION_CONFIG: () => ANIMATION_CONFIG, AnimationLoop: () => AnimationLoop, DEFAULT_AREA: () => DEFAULT_AREA, + DistortionEditor: () => DistortionEditor, ImageDistortion: () => ImageDistortion, SHADER_CONFIG: () => SHADER_CONFIG, ShaderManager: () => ShaderManager, ThreeScene: () => ThreeScene, applyEasing: () => applyEasing, - useAnimationFrame: () => useAnimationFrame + useAnimationFrame: () => useAnimationFrame, + useDistortionEditor: () => useDistortionEditor }); module.exports = __toCommonJS(index_exports); @@ -135,6 +137,15 @@ var ThreeScene = class { console.log("[ThreeScene] render() \uD638\uCD9C\uB428, mesh:", this.mesh); this.renderer.render(this.scene, this.camera); } + /** + * 현재 해상도 가져오기 + */ + getResolution() { + return { + x: this.uniforms.u_resolution.value.x, + y: this.uniforms.u_resolution.value.y + }; + } /** * 리소스 정리 */ @@ -426,19 +437,20 @@ var ImageDistortion = ({ }, [imageSrc, isReady]); (0, import_react2.useEffect)(() => { if (!sceneRef.current || !isReady) return; + const resolution = sceneRef.current.getResolution(); const points = new Float32Array(SHADER_CONFIG.MAX_POINTS * 2); currentAreas.forEach((area, areaIndex) => { area.basePoints.forEach((point, pointIndex) => { const index = (areaIndex * 4 + pointIndex) * 2; points[index] = point.x; - points[index + 1] = point.y; + points[index + 1] = 1 - point.y; }); }); const dragVectors = new Float32Array(SHADER_CONFIG.MAX_DRAG_VECTORS * 2); currentAreas.forEach((area, index) => { const baseIndex = index * 2; dragVectors[baseIndex] = area.dragVector.x; - dragVectors[baseIndex + 1] = area.dragVector.y; + dragVectors[baseIndex + 1] = -area.dragVector.y; }); const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS); currentAreas.forEach((area, index) => { @@ -491,16 +503,651 @@ var ImageDistortion = ({ } ); }; + +// src/editor/DistortionEditor.tsx +var import_react5 = require("react"); + +// src/editor/hooks/useDistortionEditor.ts +var import_react3 = require("react"); +var useDistortionEditor = (initialAreas = []) => { + const [state, setState] = (0, import_react3.useState)({ + selectedAreaId: initialAreas[0]?.id || null, + areas: initialAreas, + editMode: "normal", + draggingPointIndex: null + }); + const selectArea = (0, import_react3.useCallback)((areaId) => { + setState((prev) => ({ ...prev, selectedAreaId: areaId })); + }, []); + const addArea = (0, import_react3.useCallback)((area) => { + setState((prev) => ({ + ...prev, + areas: [...prev.areas, area], + selectedAreaId: area.id + })); + }, []); + const removeArea = (0, import_react3.useCallback)((areaId) => { + setState((prev) => { + const newAreas = prev.areas.filter((a) => a.id !== areaId); + return { + ...prev, + areas: newAreas, + selectedAreaId: prev.selectedAreaId === areaId ? newAreas[0]?.id || null : prev.selectedAreaId + }; + }); + }, []); + const updateArea = (0, import_react3.useCallback)((areaId, updates) => { + setState((prev) => ({ + ...prev, + areas: prev.areas.map((area) => area.id === areaId ? { ...area, ...updates } : area) + })); + }, []); + const updatePoint = (0, import_react3.useCallback)((areaId, pointIndex, point) => { + setState((prev) => ({ + ...prev, + areas: prev.areas.map((area) => { + if (area.id === areaId) { + const newPoints = [...area.basePoints]; + newPoints[pointIndex] = point; + return { ...area, basePoints: newPoints }; + } + return area; + }) + })); + }, []); + const startDragging = (0, import_react3.useCallback)((pointIndex) => { + setState((prev) => ({ ...prev, draggingPointIndex: pointIndex })); + }, []); + const stopDragging = (0, import_react3.useCallback)(() => { + setState((prev) => ({ ...prev, draggingPointIndex: null })); + }, []); + const setEditMode = (0, import_react3.useCallback)((mode) => { + setState((prev) => ({ ...prev, editMode: mode })); + }, []); + const getSelectedArea = (0, import_react3.useCallback)(() => { + return state.areas.find((a) => a.id === state.selectedAreaId) || null; + }, [state.areas, state.selectedAreaId]); + return { + state, + selectArea, + addArea, + removeArea, + updateArea, + updatePoint, + startDragging, + stopDragging, + setEditMode, + getSelectedArea + }; +}; + +// src/editor/components/EditorCanvas.tsx +var import_react4 = require("react"); +var import_jsx_runtime2 = require("react/jsx-runtime"); +var EditorCanvas = ({ + areas, + selectedAreaId, + imageSrc, + width, + height, + onUpdatePoint, + onUpdateArea, + draggingPointIndex, + onStartDragging, + onStopDragging +}) => { + 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); + (0, import_react4.useEffect)(() => { + if (!containerRef.current) return; + const rect = containerRef.current.getBoundingClientRect(); + setCanvasSize({ width: rect.width, height: rect.height }); + }, [width, height]); + const selectedArea = areas.find((a) => a.id === selectedAreaId); + const isPointInPolygon = (0, import_react4.useCallback)((point, polygon) => { + let inside = false; + for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { + const xi = polygon[i].x, yi = polygon[i].y; + const xj = polygon[j].x, yj = polygon[j].y; + const intersect = yi > point.y !== yj > point.y && point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi; + if (intersect) inside = !inside; + } + return inside; + }, []); + const handleMouseDown = (0, import_react4.useCallback)( + (pointIndex) => (e) => { + e.preventDefault(); + e.stopPropagation(); + onStartDragging(pointIndex); + }, + [onStartDragging] + ); + const handleCanvasMouseDown = (0, import_react4.useCallback)( + (e) => { + if (!selectedArea || !containerRef.current) return; + const rect = containerRef.current.getBoundingClientRect(); + const x = (e.clientX - rect.left) / rect.width; + const y = (e.clientY - rect.top) / rect.height; + const clickPoint = { x, y }; + if (isPointInPolygon(clickPoint, selectedArea.basePoints)) { + setIsDraggingArea(true); + setDragStartPos(clickPoint); + e.preventDefault(); + } + }, + [selectedArea, isPointInPolygon] + ); + const handleMouseMove = (0, import_react4.useCallback)( + (e) => { + if (!selectedArea || !containerRef.current) return; + const rect = containerRef.current.getBoundingClientRect(); + const x = (e.clientX - rect.left) / rect.width; + const y = (e.clientY - rect.top) / rect.height; + if (draggingPointIndex !== null) { + const clampedX = Math.max(0, Math.min(1, x)); + const clampedY = Math.max(0, Math.min(1, y)); + onUpdatePoint(selectedArea.id, draggingPointIndex, { x: clampedX, y: clampedY }); + } else if (isDraggingArea && dragStartPos) { + const deltaX = x - dragStartPos.x; + const deltaY = y - dragStartPos.y; + const newPoints = selectedArea.basePoints.map((point) => ({ + x: Math.max(0, Math.min(1, point.x + deltaX)), + y: Math.max(0, Math.min(1, point.y + deltaY)) + })); + onUpdateArea(selectedArea.id, { basePoints: newPoints }); + setDragStartPos({ x, y }); + } + }, + [draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea] + ); + const handleMouseUp = (0, import_react4.useCallback)(() => { + if (draggingPointIndex !== null) { + onStopDragging(); + } + if (isDraggingArea) { + setIsDraggingArea(false); + setDragStartPos(null); + } + }, [draggingPointIndex, isDraggingArea, onStopDragging]); + (0, import_react4.useEffect)(() => { + if (draggingPointIndex !== null || isDraggingArea) { + window.addEventListener("mouseup", handleMouseUp); + return () => window.removeEventListener("mouseup", handleMouseUp); + } + }, [draggingPointIndex, isDraggingArea, handleMouseUp]); + const uvToPixel = (u, v, points, canvasWidth, canvasHeight) => { + const [p0, p1, p2, p3] = points; + const leftX = p0.x * (1 - u) + p1.x * u; + const leftY = p0.y * (1 - u) + p1.y * u; + const rightX = p3.x * (1 - u) + p2.x * u; + const rightY = p3.y * (1 - u) + p2.y * u; + const posX = leftX * (1 - v) + rightX * v; + const posY = leftY * (1 - v) + rightY * v; + return { + x: posX * canvasWidth, + y: posY * canvasHeight + }; + }; + const drawDistortionCircle = (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 = []; + 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 pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight); + gradientPoints.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.closePath(); + const alpha = r / maxRadius; + ctx.strokeStyle = `rgba(255, 220, 100, ${0.3 * alpha})`; + ctx.lineWidth = 1; + ctx.setLineDash([3, 3]); + ctx.stroke(); + ctx.setLineDash([]); + } + 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(); + }; + const getCursorStyle = () => { + if (draggingPointIndex !== null) return "grabbing"; + if (isDraggingArea) return "grabbing"; + return "default"; + }; + return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)( + "div", + { + ref: containerRef, + className: "editor-canvas", + style: { width, height, position: "relative", cursor: getCursorStyle() }, + onMouseDown: handleCanvasMouseDown, + onMouseMove: handleMouseMove, + children: [ + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ImageDistortion, { imageSrc, areas, width, height }), + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( + "svg", + { + style: { + position: "absolute", + top: 0, + left: 0, + width: "100%", + height: "100%", + pointerEvents: "none" + }, + children: areas.map((area) => { + const isSelected = area.id === selectedAreaId; + const points = area.basePoints; + 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", + opacity: isSelected ? 1 : 0.5 + } + ) }, area.id); + }) + } + ), + selectedArea && canvasSize.width > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( + "canvas", + { + style: { + position: "absolute", + top: 0, + left: 0, + width: "100%", + height: "100%", + pointerEvents: "none" + }, + width: canvasSize.width, + height: canvasSize.height, + ref: (canvas) => { + if (canvas) { + const ctx = canvas.getContext("2d"); + if (ctx) { + ctx.clearRect(0, 0, canvasSize.width, canvasSize.height); + drawDistortionCircle(ctx, selectedArea.basePoints, canvasSize.width, canvasSize.height); + } + } + } + } + ), + 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)" + }, + 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 + )) + ] + } + ); +}; + +// src/editor/components/AreaList.tsx +var import_jsx_runtime3 = require("react/jsx-runtime"); +var AreaList = ({ + areas, + selectedAreaId, + onSelectArea, + onRemoveArea, + onAddArea +}) => { + return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "area-list", children: [ + /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "area-list-header", children: [ + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { children: "\uC65C\uACE1 \uC601\uC5ED" }), + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "button", + { + onClick: onAddArea, + disabled: areas.length >= 8, + className: "btn-add", + title: areas.length >= 8 ? "\uCD5C\uB300 8\uAC1C \uC601\uC5ED\uAE4C\uC9C0 \uC9C0\uC6D0" : "\uC0C8 \uC601\uC5ED \uCD94\uAC00", + children: "+ \uCD94\uAC00" + } + ) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "area-list-items", children: areas.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "area-list-empty", children: "\uC601\uC5ED\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. + \uCD94\uAC00 \uBC84\uD2BC\uC744 \uB20C\uB7EC\uC8FC\uC138\uC694." }) : areas.map((area, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)( + "div", + { + className: `area-item ${selectedAreaId === area.id ? "selected" : ""}`, + onClick: () => onSelectArea(area.id), + children: [ + /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "area-item-info", children: [ + /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "area-item-name", children: [ + "\uC601\uC5ED ", + index + 1 + ] }), + /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "area-item-strength", children: [ + "\uAC15\uB3C4: ", + (area.distortionStrength * 100).toFixed(0), + "%" + ] }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime3.jsx)( + "button", + { + onClick: (e) => { + e.stopPropagation(); + onRemoveArea(area.id); + }, + className: "btn-remove", + title: "\uC601\uC5ED \uC0AD\uC81C", + children: "\xD7" + } + ) + ] + }, + area.id + )) }) + ] }); +}; + +// src/editor/components/ParameterPanel.tsx +var import_jsx_runtime4 = require("react/jsx-runtime"); +var EASING_OPTIONS = [ + { value: "linear", label: "\uC120\uD615 (Linear)" }, + { value: "easeIn", label: "\uAC00\uC18D (Ease In)" }, + { value: "easeOut", label: "\uAC10\uC18D (Ease Out)" }, + { value: "easeInOut", label: "\uAC00\uAC10\uC18D (Ease In Out)" }, + { value: "easeInQuad", label: "\uAC00\uC18D\xB2 (Ease In Quad)" }, + { value: "easeOutQuad", label: "\uAC10\uC18D\xB2 (Ease Out Quad)" } +]; +var ParameterPanel = ({ area, onUpdateArea }) => { + if (!area) { + return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "parameter-panel", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "parameter-panel-empty", children: "\uC601\uC5ED\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694" }) }); + } + return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "parameter-panel", children: [ + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { children: "\uD30C\uB77C\uBBF8\uD130 \uD3B8\uC9D1" }), + /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "parameter-group", children: [ + /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { children: [ + "\uC65C\uACE1 \uAC15\uB3C4: ", + (area.distortionStrength * 100).toFixed(0), + "%" + ] }), + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( + "input", + { + type: "range", + min: "0", + max: "1", + step: "0.01", + value: area.distortionStrength, + onChange: (e) => onUpdateArea({ distortionStrength: parseFloat(e.target.value) }), + className: "slider" + } + ) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "parameter-group", children: [ + /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { children: [ + "\uC9C0\uC18D \uC2DC\uAC04: ", + area.movement.duration.toFixed(1), + "\uCD08" + ] }), + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( + "input", + { + type: "number", + min: "0.1", + max: "10", + step: "0.1", + value: area.movement.duration, + onChange: (e) => onUpdateArea({ + movement: { ...area.movement, duration: parseFloat(e.target.value) } + }), + className: "input-number" + } + ) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "parameter-group", children: [ + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { children: "\uC774\uC9D5 \uD568\uC218" }), + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( + "select", + { + value: area.movement.easing, + onChange: (e) => onUpdateArea({ + movement: { ...area.movement, easing: e.target.value } + }), + className: "select", + children: EASING_OPTIONS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: option.value, children: option.label }, option.value)) + } + ) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "parameter-group", children: [ + /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { children: [ + "\uBCA1\uD130 X: ", + area.movement.vectorA.x.toFixed(2) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( + "input", + { + type: "range", + min: "-1", + max: "1", + step: "0.01", + value: area.movement.vectorA.x, + onChange: (e) => onUpdateArea({ + movement: { + ...area.movement, + vectorA: { ...area.movement.vectorA, x: parseFloat(e.target.value) } + } + }), + className: "slider" + } + ) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "parameter-group", children: [ + /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { children: [ + "\uBCA1\uD130 Y: ", + area.movement.vectorA.y.toFixed(2) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)( + "input", + { + type: "range", + min: "-1", + max: "1", + step: "0.01", + value: area.movement.vectorA.y, + onChange: (e) => onUpdateArea({ + movement: { + ...area.movement, + vectorA: { ...area.movement.vectorA, y: parseFloat(e.target.value) } + } + }), + className: "slider" + } + ) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "parameter-group", children: [ + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { children: "\uD3EC\uC778\uD2B8 \uC88C\uD45C (\uCE94\uBC84\uC2A4\uC5D0\uC11C \uB4DC\uB798\uADF8)" }), + /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "points-display", children: area.basePoints.map((point, idx) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "point-coord", children: [ + "P", + idx + 1, + ": (", + point.x.toFixed(3), + ", ", + point.y.toFixed(3), + ")" + ] }, idx)) }) + ] }) + ] }); +}; + +// src/editor/DistortionEditor.tsx +var import_jsx_runtime5 = require("react/jsx-runtime"); +var DistortionEditor = ({ + initialAreas = [], + imageSrc, + onAreasChange, + onSelectedAreaChange, + width = 800, + height = 600 +}) => { + const { + state, + selectArea, + addArea, + removeArea, + updateArea, + updatePoint, + startDragging, + stopDragging, + getSelectedArea + } = useDistortionEditor(initialAreas); + (0, import_react5.useEffect)(() => { + onAreasChange?.(state.areas); + }, [state.areas, onAreasChange]); + (0, import_react5.useEffect)(() => { + onSelectedAreaChange?.(state.selectedAreaId); + }, [state.selectedAreaId, onSelectedAreaChange]); + const handleAddArea = () => { + const newArea = { + id: `area-${Date.now()}`, + basePoints: [ + { x: 0.3, y: 0.3 }, + { x: 0.7, y: 0.3 }, + { x: 0.7, y: 0.7 }, + { x: 0.3, y: 0.7 } + ], + movement: { + vectorA: { x: DEFAULT_AREA.VECTOR_A.x, y: DEFAULT_AREA.VECTOR_A.y }, + vectorB: { x: DEFAULT_AREA.VECTOR_B.x, y: DEFAULT_AREA.VECTOR_B.y }, + duration: DEFAULT_AREA.DURATION, + easing: DEFAULT_AREA.EASING + }, + distortionStrength: DEFAULT_AREA.DISTORTION_STRENGTH, + progress: 0, + dragVector: { x: 0, y: 0 } + }; + addArea(newArea); + }; + const handleUpdateArea = (updates) => { + if (state.selectedAreaId) { + updateArea(state.selectedAreaId, updates); + } + }; + const selectedArea = getSelectedArea(); + return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "distortion-editor", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "editor-main", children: [ + /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "editor-canvas-container", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( + EditorCanvas, + { + areas: state.areas, + selectedAreaId: state.selectedAreaId, + imageSrc, + width, + height, + onUpdatePoint: updatePoint, + onUpdateArea: updateArea, + draggingPointIndex: state.draggingPointIndex, + onStartDragging: startDragging, + onStopDragging: stopDragging + } + ) }), + /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "editor-sidebar", children: [ + /* @__PURE__ */ (0, import_jsx_runtime5.jsx)( + AreaList, + { + areas: state.areas, + selectedAreaId: state.selectedAreaId, + onSelectArea: selectArea, + onRemoveArea: removeArea, + onAddArea: handleAddArea + } + ), + /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ParameterPanel, { area: selectedArea, onUpdateArea: handleUpdateArea }) + ] }) + ] }) }); +}; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ANIMATION_CONFIG, AnimationLoop, DEFAULT_AREA, + DistortionEditor, ImageDistortion, SHADER_CONFIG, ShaderManager, ThreeScene, applyEasing, - useAnimationFrame + useAnimationFrame, + useDistortionEditor }); //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map index 2940dfc..33c1c26 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/index.ts","../src/components/ImageDistortion.tsx","../src/engine/ThreeScene.ts","../src/engine/ShaderManager.ts","../src/utils/easing.ts","../src/engine/AnimationLoop.ts","../src/hooks/useAnimationFrame.ts","../src/utils/constants.ts"],"sourcesContent":["// 메인 컴포넌트\nexport { ImageDistortion } from './components/ImageDistortion';\nexport type { ImageDistortionProps } from './components/ImageDistortion';\n\n// 타입 정의\nexport type {\n Point,\n EasingFunction,\n DistortionMovement,\n DistortionArea,\n AreaBounds,\n ShaderUniforms,\n ShaderConfig,\n AnimationState,\n AnimationTicker,\n} from './types';\n\n// 유틸리티 함수\nexport { applyEasing } from './utils/easing';\nexport { SHADER_CONFIG, ANIMATION_CONFIG, DEFAULT_AREA } from './utils/constants';\n\n// 엔진 클래스 (고급 사용자용)\nexport { ThreeScene } from './engine/ThreeScene';\nexport { ShaderManager } from './engine/ShaderManager';\nexport { AnimationLoop } from './engine/AnimationLoop';\n\n// 훅\nexport { useAnimationFrame } from './hooks/useAnimationFrame';","import React, { useEffect, useRef, useState, useCallback } from 'react';\nimport * as THREE from 'three';\nimport { 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 points = new Float32Array(SHADER_CONFIG.MAX_POINTS * 2);\n currentAreas.forEach((area, areaIndex) => {\n area.basePoints.forEach((point, pointIndex) => {\n const index = (areaIndex * 4 + pointIndex) * 2;\n points[index] = point.x;\n points[index + 1] = point.y;\n });\n });\n\n // 드래그 벡터 배열 생성\n const dragVectors = new Float32Array(SHADER_CONFIG.MAX_DRAG_VECTORS * 2);\n currentAreas.forEach((area, index) => {\n const baseIndex = index * 2;\n dragVectors[baseIndex] = area.dragVector.x;\n dragVectors[baseIndex + 1] = area.dragVector.y;\n });\n\n // 강도 배열 생성\n const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS);\n currentAreas.forEach((area, index) => {\n strengths[index] = area.distortionStrength;\n });\n\n sceneRef.current.updateUniforms({\n u_numAreas: { value: currentAreas.length },\n u_points: { value: points },\n u_dragVectors: { value: dragVectors },\n u_distortionStrengths: { value: strengths },\n });\n\n sceneRef.current.render();\n }, [currentAreas, isReady]);\n\n // 애니메이션 루프\n const animationCallback = useCallback((deltaTime: number) => {\n if (!isReady) return;\n\n setCurrentAreas((prevAreas) => {\n // 진행도 업데이트\n const updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);\n // 드래그 벡터 업데이트\n return AnimationLoop.updateAreaDragVectors(updatedAreas);\n });\n }, [isReady]);\n\n useAnimationFrame(animationCallback, isPlaying);\n\n return (\n \n {!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 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;"],"mappings":"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","names":["import_react","THREE"]} \ No newline at end of file +{"version":3,"sources":["../src/index.ts","../src/components/ImageDistortion.tsx","../src/engine/ThreeScene.ts","../src/engine/ShaderManager.ts","../src/utils/easing.ts","../src/engine/AnimationLoop.ts","../src/hooks/useAnimationFrame.ts","../src/utils/constants.ts","../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 diff --git a/dist/index.mjs b/dist/index.mjs index a101070..1919858 100644 --- a/dist/index.mjs +++ b/dist/index.mjs @@ -91,6 +91,15 @@ var ThreeScene = class { console.log("[ThreeScene] render() \uD638\uCD9C\uB428, mesh:", this.mesh); this.renderer.render(this.scene, this.camera); } + /** + * 현재 해상도 가져오기 + */ + getResolution() { + return { + x: this.uniforms.u_resolution.value.x, + y: this.uniforms.u_resolution.value.y + }; + } /** * 리소스 정리 */ @@ -382,19 +391,20 @@ var ImageDistortion = ({ }, [imageSrc, isReady]); useEffect2(() => { if (!sceneRef.current || !isReady) return; + const resolution = sceneRef.current.getResolution(); const points = new Float32Array(SHADER_CONFIG.MAX_POINTS * 2); currentAreas.forEach((area, areaIndex) => { area.basePoints.forEach((point, pointIndex) => { const index = (areaIndex * 4 + pointIndex) * 2; points[index] = point.x; - points[index + 1] = point.y; + points[index + 1] = 1 - point.y; }); }); const dragVectors = new Float32Array(SHADER_CONFIG.MAX_DRAG_VECTORS * 2); currentAreas.forEach((area, index) => { const baseIndex = index * 2; dragVectors[baseIndex] = area.dragVector.x; - dragVectors[baseIndex + 1] = area.dragVector.y; + dragVectors[baseIndex + 1] = -area.dragVector.y; }); const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS); currentAreas.forEach((area, index) => { @@ -447,15 +457,650 @@ var ImageDistortion = ({ } ); }; + +// src/editor/DistortionEditor.tsx +import { useEffect as useEffect4 } from "react"; + +// src/editor/hooks/useDistortionEditor.ts +import { useState as useState2, useCallback as useCallback2 } from "react"; +var useDistortionEditor = (initialAreas = []) => { + const [state, setState] = useState2({ + selectedAreaId: initialAreas[0]?.id || null, + areas: initialAreas, + editMode: "normal", + draggingPointIndex: null + }); + const selectArea = useCallback2((areaId) => { + setState((prev) => ({ ...prev, selectedAreaId: areaId })); + }, []); + const addArea = useCallback2((area) => { + setState((prev) => ({ + ...prev, + areas: [...prev.areas, area], + selectedAreaId: area.id + })); + }, []); + const removeArea = useCallback2((areaId) => { + setState((prev) => { + const newAreas = prev.areas.filter((a) => a.id !== areaId); + return { + ...prev, + areas: newAreas, + selectedAreaId: prev.selectedAreaId === areaId ? newAreas[0]?.id || null : prev.selectedAreaId + }; + }); + }, []); + const updateArea = useCallback2((areaId, updates) => { + setState((prev) => ({ + ...prev, + areas: prev.areas.map((area) => area.id === areaId ? { ...area, ...updates } : area) + })); + }, []); + const updatePoint = useCallback2((areaId, pointIndex, point) => { + setState((prev) => ({ + ...prev, + areas: prev.areas.map((area) => { + if (area.id === areaId) { + const newPoints = [...area.basePoints]; + newPoints[pointIndex] = point; + return { ...area, basePoints: newPoints }; + } + return area; + }) + })); + }, []); + const startDragging = useCallback2((pointIndex) => { + setState((prev) => ({ ...prev, draggingPointIndex: pointIndex })); + }, []); + const stopDragging = useCallback2(() => { + setState((prev) => ({ ...prev, draggingPointIndex: null })); + }, []); + const setEditMode = useCallback2((mode) => { + setState((prev) => ({ ...prev, editMode: mode })); + }, []); + const getSelectedArea = useCallback2(() => { + return state.areas.find((a) => a.id === state.selectedAreaId) || null; + }, [state.areas, state.selectedAreaId]); + return { + state, + selectArea, + addArea, + removeArea, + updateArea, + updatePoint, + startDragging, + stopDragging, + setEditMode, + getSelectedArea + }; +}; + +// src/editor/components/EditorCanvas.tsx +import { useRef as useRef3, useEffect as useEffect3, useState as useState3, useCallback as useCallback3 } from "react"; +import { jsx as jsx2, jsxs } from "react/jsx-runtime"; +var EditorCanvas = ({ + areas, + selectedAreaId, + imageSrc, + width, + height, + onUpdatePoint, + onUpdateArea, + draggingPointIndex, + onStartDragging, + onStopDragging +}) => { + const containerRef = useRef3(null); + const [canvasSize, setCanvasSize] = useState3({ width: 0, height: 0 }); + const [isDraggingArea, setIsDraggingArea] = useState3(false); + const [dragStartPos, setDragStartPos] = useState3(null); + useEffect3(() => { + if (!containerRef.current) return; + const rect = containerRef.current.getBoundingClientRect(); + setCanvasSize({ width: rect.width, height: rect.height }); + }, [width, height]); + const selectedArea = areas.find((a) => a.id === selectedAreaId); + const isPointInPolygon = useCallback3((point, polygon) => { + let inside = false; + for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { + const xi = polygon[i].x, yi = polygon[i].y; + const xj = polygon[j].x, yj = polygon[j].y; + const intersect = yi > point.y !== yj > point.y && point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi; + if (intersect) inside = !inside; + } + return inside; + }, []); + const handleMouseDown = useCallback3( + (pointIndex) => (e) => { + e.preventDefault(); + e.stopPropagation(); + onStartDragging(pointIndex); + }, + [onStartDragging] + ); + const handleCanvasMouseDown = useCallback3( + (e) => { + if (!selectedArea || !containerRef.current) return; + const rect = containerRef.current.getBoundingClientRect(); + const x = (e.clientX - rect.left) / rect.width; + const y = (e.clientY - rect.top) / rect.height; + const clickPoint = { x, y }; + if (isPointInPolygon(clickPoint, selectedArea.basePoints)) { + setIsDraggingArea(true); + setDragStartPos(clickPoint); + e.preventDefault(); + } + }, + [selectedArea, isPointInPolygon] + ); + const handleMouseMove = useCallback3( + (e) => { + if (!selectedArea || !containerRef.current) return; + const rect = containerRef.current.getBoundingClientRect(); + const x = (e.clientX - rect.left) / rect.width; + const y = (e.clientY - rect.top) / rect.height; + if (draggingPointIndex !== null) { + const clampedX = Math.max(0, Math.min(1, x)); + const clampedY = Math.max(0, Math.min(1, y)); + onUpdatePoint(selectedArea.id, draggingPointIndex, { x: clampedX, y: clampedY }); + } else if (isDraggingArea && dragStartPos) { + const deltaX = x - dragStartPos.x; + const deltaY = y - dragStartPos.y; + const newPoints = selectedArea.basePoints.map((point) => ({ + x: Math.max(0, Math.min(1, point.x + deltaX)), + y: Math.max(0, Math.min(1, point.y + deltaY)) + })); + onUpdateArea(selectedArea.id, { basePoints: newPoints }); + setDragStartPos({ x, y }); + } + }, + [draggingPointIndex, isDraggingArea, dragStartPos, selectedArea, onUpdatePoint, onUpdateArea] + ); + const handleMouseUp = useCallback3(() => { + if (draggingPointIndex !== null) { + onStopDragging(); + } + if (isDraggingArea) { + setIsDraggingArea(false); + setDragStartPos(null); + } + }, [draggingPointIndex, isDraggingArea, onStopDragging]); + useEffect3(() => { + if (draggingPointIndex !== null || isDraggingArea) { + window.addEventListener("mouseup", handleMouseUp); + return () => window.removeEventListener("mouseup", handleMouseUp); + } + }, [draggingPointIndex, isDraggingArea, handleMouseUp]); + const uvToPixel = (u, v, points, canvasWidth, canvasHeight) => { + const [p0, p1, p2, p3] = points; + const leftX = p0.x * (1 - u) + p1.x * u; + const leftY = p0.y * (1 - u) + p1.y * u; + const rightX = p3.x * (1 - u) + p2.x * u; + const rightY = p3.y * (1 - u) + p2.y * u; + const posX = leftX * (1 - v) + rightX * v; + const posY = leftY * (1 - v) + rightY * v; + return { + x: posX * canvasWidth, + y: posY * canvasHeight + }; + }; + const drawDistortionCircle = (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 = []; + 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 pixelPos = uvToPixel(u, v, points, canvasWidth, canvasHeight); + gradientPoints.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.closePath(); + const alpha = r / maxRadius; + ctx.strokeStyle = `rgba(255, 220, 100, ${0.3 * alpha})`; + ctx.lineWidth = 1; + ctx.setLineDash([3, 3]); + ctx.stroke(); + ctx.setLineDash([]); + } + 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(); + }; + const getCursorStyle = () => { + if (draggingPointIndex !== null) return "grabbing"; + if (isDraggingArea) return "grabbing"; + return "default"; + }; + return /* @__PURE__ */ jsxs( + "div", + { + ref: containerRef, + className: "editor-canvas", + style: { width, height, position: "relative", cursor: getCursorStyle() }, + onMouseDown: handleCanvasMouseDown, + onMouseMove: handleMouseMove, + children: [ + /* @__PURE__ */ jsx2(ImageDistortion, { imageSrc, areas, width, height }), + /* @__PURE__ */ jsx2( + "svg", + { + style: { + position: "absolute", + top: 0, + left: 0, + width: "100%", + height: "100%", + pointerEvents: "none" + }, + children: areas.map((area) => { + const isSelected = area.id === selectedAreaId; + const points = area.basePoints; + 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", + opacity: isSelected ? 1 : 0.5 + } + ) }, area.id); + }) + } + ), + selectedArea && canvasSize.width > 0 && /* @__PURE__ */ jsx2( + "canvas", + { + style: { + position: "absolute", + top: 0, + left: 0, + width: "100%", + height: "100%", + pointerEvents: "none" + }, + width: canvasSize.width, + height: canvasSize.height, + ref: (canvas) => { + if (canvas) { + const ctx = canvas.getContext("2d"); + if (ctx) { + ctx.clearRect(0, 0, canvasSize.width, canvasSize.height); + drawDistortionCircle(ctx, selectedArea.basePoints, canvasSize.width, canvasSize.height); + } + } + } + } + ), + 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)" + }, + 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 + )) + ] + } + ); +}; + +// src/editor/components/AreaList.tsx +import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime"; +var AreaList = ({ + areas, + selectedAreaId, + onSelectArea, + onRemoveArea, + onAddArea +}) => { + return /* @__PURE__ */ jsxs2("div", { className: "area-list", children: [ + /* @__PURE__ */ jsxs2("div", { className: "area-list-header", children: [ + /* @__PURE__ */ jsx3("h3", { children: "\uC65C\uACE1 \uC601\uC5ED" }), + /* @__PURE__ */ jsx3( + "button", + { + onClick: onAddArea, + disabled: areas.length >= 8, + className: "btn-add", + title: areas.length >= 8 ? "\uCD5C\uB300 8\uAC1C \uC601\uC5ED\uAE4C\uC9C0 \uC9C0\uC6D0" : "\uC0C8 \uC601\uC5ED \uCD94\uAC00", + children: "+ \uCD94\uAC00" + } + ) + ] }), + /* @__PURE__ */ jsx3("div", { className: "area-list-items", children: areas.length === 0 ? /* @__PURE__ */ jsx3("div", { className: "area-list-empty", children: "\uC601\uC5ED\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. + \uCD94\uAC00 \uBC84\uD2BC\uC744 \uB20C\uB7EC\uC8FC\uC138\uC694." }) : areas.map((area, index) => /* @__PURE__ */ jsxs2( + "div", + { + className: `area-item ${selectedAreaId === area.id ? "selected" : ""}`, + onClick: () => onSelectArea(area.id), + children: [ + /* @__PURE__ */ jsxs2("div", { className: "area-item-info", children: [ + /* @__PURE__ */ jsxs2("span", { className: "area-item-name", children: [ + "\uC601\uC5ED ", + index + 1 + ] }), + /* @__PURE__ */ jsxs2("span", { className: "area-item-strength", children: [ + "\uAC15\uB3C4: ", + (area.distortionStrength * 100).toFixed(0), + "%" + ] }) + ] }), + /* @__PURE__ */ jsx3( + "button", + { + onClick: (e) => { + e.stopPropagation(); + onRemoveArea(area.id); + }, + className: "btn-remove", + title: "\uC601\uC5ED \uC0AD\uC81C", + children: "\xD7" + } + ) + ] + }, + area.id + )) }) + ] }); +}; + +// src/editor/components/ParameterPanel.tsx +import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime"; +var EASING_OPTIONS = [ + { value: "linear", label: "\uC120\uD615 (Linear)" }, + { value: "easeIn", label: "\uAC00\uC18D (Ease In)" }, + { value: "easeOut", label: "\uAC10\uC18D (Ease Out)" }, + { value: "easeInOut", label: "\uAC00\uAC10\uC18D (Ease In Out)" }, + { value: "easeInQuad", label: "\uAC00\uC18D\xB2 (Ease In Quad)" }, + { value: "easeOutQuad", label: "\uAC10\uC18D\xB2 (Ease Out Quad)" } +]; +var ParameterPanel = ({ area, onUpdateArea }) => { + if (!area) { + return /* @__PURE__ */ jsx4("div", { className: "parameter-panel", children: /* @__PURE__ */ jsx4("div", { className: "parameter-panel-empty", children: "\uC601\uC5ED\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694" }) }); + } + return /* @__PURE__ */ jsxs3("div", { className: "parameter-panel", children: [ + /* @__PURE__ */ jsx4("h3", { children: "\uD30C\uB77C\uBBF8\uD130 \uD3B8\uC9D1" }), + /* @__PURE__ */ jsxs3("div", { className: "parameter-group", children: [ + /* @__PURE__ */ jsxs3("label", { children: [ + "\uC65C\uACE1 \uAC15\uB3C4: ", + (area.distortionStrength * 100).toFixed(0), + "%" + ] }), + /* @__PURE__ */ jsx4( + "input", + { + type: "range", + min: "0", + max: "1", + step: "0.01", + value: area.distortionStrength, + onChange: (e) => onUpdateArea({ distortionStrength: parseFloat(e.target.value) }), + className: "slider" + } + ) + ] }), + /* @__PURE__ */ jsxs3("div", { className: "parameter-group", children: [ + /* @__PURE__ */ jsxs3("label", { children: [ + "\uC9C0\uC18D \uC2DC\uAC04: ", + area.movement.duration.toFixed(1), + "\uCD08" + ] }), + /* @__PURE__ */ jsx4( + "input", + { + type: "number", + min: "0.1", + max: "10", + step: "0.1", + value: area.movement.duration, + onChange: (e) => onUpdateArea({ + movement: { ...area.movement, duration: parseFloat(e.target.value) } + }), + className: "input-number" + } + ) + ] }), + /* @__PURE__ */ jsxs3("div", { className: "parameter-group", children: [ + /* @__PURE__ */ jsx4("label", { children: "\uC774\uC9D5 \uD568\uC218" }), + /* @__PURE__ */ jsx4( + "select", + { + value: area.movement.easing, + onChange: (e) => onUpdateArea({ + movement: { ...area.movement, easing: e.target.value } + }), + className: "select", + children: EASING_OPTIONS.map((option) => /* @__PURE__ */ jsx4("option", { value: option.value, children: option.label }, option.value)) + } + ) + ] }), + /* @__PURE__ */ jsxs3("div", { className: "parameter-group", children: [ + /* @__PURE__ */ jsxs3("label", { children: [ + "\uBCA1\uD130 X: ", + area.movement.vectorA.x.toFixed(2) + ] }), + /* @__PURE__ */ jsx4( + "input", + { + type: "range", + min: "-1", + max: "1", + step: "0.01", + value: area.movement.vectorA.x, + onChange: (e) => onUpdateArea({ + movement: { + ...area.movement, + vectorA: { ...area.movement.vectorA, x: parseFloat(e.target.value) } + } + }), + className: "slider" + } + ) + ] }), + /* @__PURE__ */ jsxs3("div", { className: "parameter-group", children: [ + /* @__PURE__ */ jsxs3("label", { children: [ + "\uBCA1\uD130 Y: ", + area.movement.vectorA.y.toFixed(2) + ] }), + /* @__PURE__ */ jsx4( + "input", + { + type: "range", + min: "-1", + max: "1", + step: "0.01", + value: area.movement.vectorA.y, + onChange: (e) => onUpdateArea({ + movement: { + ...area.movement, + vectorA: { ...area.movement.vectorA, y: parseFloat(e.target.value) } + } + }), + className: "slider" + } + ) + ] }), + /* @__PURE__ */ jsxs3("div", { className: "parameter-group", children: [ + /* @__PURE__ */ jsx4("label", { children: "\uD3EC\uC778\uD2B8 \uC88C\uD45C (\uCE94\uBC84\uC2A4\uC5D0\uC11C \uB4DC\uB798\uADF8)" }), + /* @__PURE__ */ jsx4("div", { className: "points-display", children: area.basePoints.map((point, idx) => /* @__PURE__ */ jsxs3("div", { className: "point-coord", children: [ + "P", + idx + 1, + ": (", + point.x.toFixed(3), + ", ", + point.y.toFixed(3), + ")" + ] }, idx)) }) + ] }) + ] }); +}; + +// src/editor/DistortionEditor.tsx +import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime"; +var DistortionEditor = ({ + initialAreas = [], + imageSrc, + onAreasChange, + onSelectedAreaChange, + width = 800, + height = 600 +}) => { + const { + state, + selectArea, + addArea, + removeArea, + updateArea, + updatePoint, + startDragging, + stopDragging, + getSelectedArea + } = useDistortionEditor(initialAreas); + useEffect4(() => { + onAreasChange?.(state.areas); + }, [state.areas, onAreasChange]); + useEffect4(() => { + onSelectedAreaChange?.(state.selectedAreaId); + }, [state.selectedAreaId, onSelectedAreaChange]); + const handleAddArea = () => { + const newArea = { + id: `area-${Date.now()}`, + basePoints: [ + { x: 0.3, y: 0.3 }, + { x: 0.7, y: 0.3 }, + { x: 0.7, y: 0.7 }, + { x: 0.3, y: 0.7 } + ], + movement: { + vectorA: { x: DEFAULT_AREA.VECTOR_A.x, y: DEFAULT_AREA.VECTOR_A.y }, + vectorB: { x: DEFAULT_AREA.VECTOR_B.x, y: DEFAULT_AREA.VECTOR_B.y }, + duration: DEFAULT_AREA.DURATION, + easing: DEFAULT_AREA.EASING + }, + distortionStrength: DEFAULT_AREA.DISTORTION_STRENGTH, + progress: 0, + dragVector: { x: 0, y: 0 } + }; + addArea(newArea); + }; + const handleUpdateArea = (updates) => { + if (state.selectedAreaId) { + updateArea(state.selectedAreaId, updates); + } + }; + const selectedArea = getSelectedArea(); + return /* @__PURE__ */ jsx5("div", { className: "distortion-editor", children: /* @__PURE__ */ jsxs4("div", { className: "editor-main", children: [ + /* @__PURE__ */ jsx5("div", { className: "editor-canvas-container", children: /* @__PURE__ */ jsx5( + EditorCanvas, + { + areas: state.areas, + selectedAreaId: state.selectedAreaId, + imageSrc, + width, + height, + onUpdatePoint: updatePoint, + onUpdateArea: updateArea, + draggingPointIndex: state.draggingPointIndex, + onStartDragging: startDragging, + onStopDragging: stopDragging + } + ) }), + /* @__PURE__ */ jsxs4("div", { className: "editor-sidebar", children: [ + /* @__PURE__ */ jsx5( + AreaList, + { + areas: state.areas, + selectedAreaId: state.selectedAreaId, + onSelectArea: selectArea, + onRemoveArea: removeArea, + onAddArea: handleAddArea + } + ), + /* @__PURE__ */ jsx5(ParameterPanel, { area: selectedArea, onUpdateArea: handleUpdateArea }) + ] }) + ] }) }); +}; export { ANIMATION_CONFIG, AnimationLoop, DEFAULT_AREA, + DistortionEditor, ImageDistortion, SHADER_CONFIG, ShaderManager, ThreeScene, applyEasing, - useAnimationFrame + useAnimationFrame, + useDistortionEditor }; //# sourceMappingURL=index.mjs.map \ No newline at end of file diff --git a/dist/index.mjs.map b/dist/index.mjs.map index 03918ca..68d285f 100644 --- a/dist/index.mjs.map +++ b/dist/index.mjs.map @@ -1 +1 @@ -{"version":3,"sources":["../src/components/ImageDistortion.tsx","../src/engine/ThreeScene.ts","../src/engine/ShaderManager.ts","../src/utils/easing.ts","../src/engine/AnimationLoop.ts","../src/hooks/useAnimationFrame.ts","../src/utils/constants.ts"],"sourcesContent":["import React, { useEffect, useRef, useState, useCallback } from 'react';\nimport * as THREE from 'three';\nimport { 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 points = new Float32Array(SHADER_CONFIG.MAX_POINTS * 2);\n currentAreas.forEach((area, areaIndex) => {\n area.basePoints.forEach((point, pointIndex) => {\n const index = (areaIndex * 4 + pointIndex) * 2;\n points[index] = point.x;\n points[index + 1] = point.y;\n });\n });\n\n // 드래그 벡터 배열 생성\n const dragVectors = new Float32Array(SHADER_CONFIG.MAX_DRAG_VECTORS * 2);\n currentAreas.forEach((area, index) => {\n const baseIndex = index * 2;\n dragVectors[baseIndex] = area.dragVector.x;\n dragVectors[baseIndex + 1] = area.dragVector.y;\n });\n\n // 강도 배열 생성\n const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS);\n currentAreas.forEach((area, index) => {\n strengths[index] = area.distortionStrength;\n });\n\n sceneRef.current.updateUniforms({\n u_numAreas: { value: currentAreas.length },\n u_points: { value: points },\n u_dragVectors: { value: dragVectors },\n u_distortionStrengths: { value: strengths },\n });\n\n sceneRef.current.render();\n }, [currentAreas, isReady]);\n\n // 애니메이션 루프\n const animationCallback = useCallback((deltaTime: number) => {\n if (!isReady) return;\n\n setCurrentAreas((prevAreas) => {\n // 진행도 업데이트\n const updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);\n // 드래그 벡터 업데이트\n return AnimationLoop.updateAreaDragVectors(updatedAreas);\n });\n }, [isReady]);\n\n useAnimationFrame(animationCallback, isPlaying);\n\n return (\n \n {!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 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;"],"mappings":"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","names":["useEffect","useRef","THREE","useRef","useEffect"]} \ No newline at end of file +{"version":3,"sources":["../src/components/ImageDistortion.tsx","../src/engine/ThreeScene.ts","../src/engine/ShaderManager.ts","../src/utils/easing.ts","../src/engine/AnimationLoop.ts","../src/hooks/useAnimationFrame.ts","../src/utils/constants.ts","../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