"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { ANIMATION_CONFIG: () => ANIMATION_CONFIG, AnimationLoop: () => AnimationLoop, DEFAULT_AREA: () => DEFAULT_AREA, ImageDistortion: () => ImageDistortion, SHADER_CONFIG: () => SHADER_CONFIG, ShaderManager: () => ShaderManager, ThreeScene: () => ThreeScene, applyEasing: () => applyEasing, useAnimationFrame: () => useAnimationFrame }); module.exports = __toCommonJS(index_exports); // src/components/ImageDistortion.tsx var import_react2 = require("react"); var THREE2 = __toESM(require("three")); // src/engine/ThreeScene.ts var THREE = __toESM(require("three")); var ThreeScene = class { constructor(container) { this.container = container; this.mesh = null; /** * 윈도우 리사이즈 핸들러 */ this.handleResize = () => { const width = this.container.clientWidth; const height = this.container.clientHeight; this.renderer.setSize(width, height); this.uniforms.u_resolution.value.set(width, height); if (this.mesh) { this.render(); } }; this.scene = new THREE.Scene(); this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false }); this.renderer.setPixelRatio(window.devicePixelRatio); this.container.appendChild(this.renderer.domElement); this.uniforms = { u_resolution: { value: new THREE.Vector2() }, u_texture: { value: null }, u_points: { value: new Float32Array(64) }, // 32포인트 × 2(x,y) u_numAreas: { value: 0 }, u_dragVectors: { value: new Float32Array(16) }, // 8벡터 × 2(x,y) u_distortionStrengths: { value: new Float32Array(8) } }; this.handleResize(); window.addEventListener("resize", this.handleResize); } /** * 셰이더 머티리얼 설정 * @param vertexShader 버텍스 셰이더 소스 * @param fragmentShader 프래그먼트 셰이더 소스 */ setShaderMaterial(vertexShader, fragmentShader) { const geometry = new THREE.PlaneGeometry(2, 2); const material = new THREE.ShaderMaterial({ uniforms: this.uniforms, vertexShader, fragmentShader }); if (this.mesh) { this.scene.remove(this.mesh); } this.mesh = new THREE.Mesh(geometry, material); this.scene.add(this.mesh); } /** * 유니폼 값 업데이트 * @param updates 업데이트할 유니폼 값들 */ updateUniforms(updates) { Object.keys(updates).forEach((key) => { const uniformKey = key; this.uniforms[uniformKey].value = updates[uniformKey].value; }); } /** * 씬 렌더링 */ render() { this.renderer.render(this.scene, this.camera); } /** * 리소스 정리 */ dispose() { window.removeEventListener("resize", this.handleResize); this.renderer.dispose(); if (this.mesh) { this.mesh.geometry.dispose(); this.mesh.material.dispose(); } if (this.container.contains(this.renderer.domElement)) { this.container.removeChild(this.renderer.domElement); } } }; // src/engine/ShaderManager.ts var ShaderManager = class { constructor() { this.vertexShaderSource = null; this.fragmentShaderSource = null; } /** * 셰이더 파일들을 비동기로 로드 * @param vertexPath 버텍스 셰이더 파일 경로 * @param fragmentPath 프래그먼트 셰이더 파일 경로 * @returns 로드된 셰이더 소스 코드 */ async loadShaders(vertexPath, fragmentPath) { try { const [vertexResponse, fragmentResponse] = await Promise.all([ fetch(vertexPath), fetch(fragmentPath) ]); if (!vertexResponse.ok) { throw new Error(`\uBC84\uD14D\uC2A4 \uC170\uC774\uB354 \uB85C\uB4DC \uC2E4\uD328: ${vertexResponse.statusText}`); } if (!fragmentResponse.ok) { throw new Error(`\uD504\uB798\uADF8\uBA3C\uD2B8 \uC170\uC774\uB354 \uB85C\uB4DC \uC2E4\uD328: ${fragmentResponse.statusText}`); } this.vertexShaderSource = await vertexResponse.text(); this.fragmentShaderSource = await fragmentResponse.text(); return { vertex: this.vertexShaderSource, fragment: this.fragmentShaderSource }; } catch (error) { console.error("\uC170\uC774\uB354 \uB85C\uB4DC \uC2E4\uD328:", error); throw new Error("\uC170\uC774\uB354 \uB85C\uB529\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4"); } } /** * 버텍스 셰이더 소스 코드 반환 */ getVertexShader() { if (!this.vertexShaderSource) { throw new Error("\uBC84\uD14D\uC2A4 \uC170\uC774\uB354\uAC00 \uB85C\uB4DC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4"); } return this.vertexShaderSource; } /** * 프래그먼트 셰이더 소스 코드 반환 */ getFragmentShader() { if (!this.fragmentShaderSource) { throw new Error("\uD504\uB798\uADF8\uBA3C\uD2B8 \uC170\uC774\uB354\uAC00 \uB85C\uB4DC\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4"); } return this.fragmentShaderSource; } }; // src/utils/easing.ts var easingFunctions = { linear: (t) => t, easeIn: (t) => t * t, easeOut: (t) => t * (2 - t), easeInOut: (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t, easeInQuad: (t) => t * t, easeOutQuad: (t) => t * (2 - t) }; var applyEasing = (progress, easingType) => { const clampedProgress = Math.max(0, Math.min(1, progress)); return easingFunctions[easingType](clampedProgress); }; // src/engine/AnimationLoop.ts var AnimationLoop = class { /** * 영역들의 드래그 벡터를 현재 진행도에 따라 업데이트 * @param areas 왜곡 영역 배열 * @returns 업데이트된 영역 배열 */ static updateAreaDragVectors(areas) { return areas.map((area) => { const { progress, movement } = area; const easedProgress = applyEasing(progress, movement.easing); let dragVector; if (easedProgress < 0.5) { const t = easedProgress * 2; dragVector = { x: movement.vectorA.x * t, y: movement.vectorA.y * t }; } else { const t = (easedProgress - 0.5) * 2; dragVector = { x: movement.vectorA.x * (1 - t), y: movement.vectorA.y * (1 - t) }; } return { ...area, dragVector }; }); } /** * 모든 영역의 진행도를 델타 타임만큼 업데이트 * @param areas 왜곡 영역 배열 * @param deltaTime 델타 타임 (초) * @returns 업데이트된 영역 배열 */ static updateProgress(areas, deltaTime) { return areas.map((area) => { let newProgress = area.progress + deltaTime / area.movement.duration; newProgress %= 1; return { ...area, progress: newProgress }; }); } }; // src/hooks/useAnimationFrame.ts var import_react = require("react"); var useAnimationFrame = (callback, isPlaying = true) => { const requestRef = (0, import_react.useRef)(void 0); const previousTimeRef = (0, import_react.useRef)(void 0); (0, import_react.useEffect)(() => { if (!isPlaying) return; const animate = (time) => { if (previousTimeRef.current !== void 0) { const deltaTime = (time - previousTimeRef.current) / 1e3; callback(deltaTime); } previousTimeRef.current = time; requestRef.current = requestAnimationFrame(animate); }; requestRef.current = requestAnimationFrame(animate); return () => { if (requestRef.current) { cancelAnimationFrame(requestRef.current); } }; }, [callback, isPlaying]); }; // src/utils/constants.ts var SHADER_CONFIG = { /** 최대 영역 개수 */ MAX_AREAS: 8, /** 최대 포인트 개수 (8영역 × 4포인트) */ MAX_POINTS: 32, /** 최대 드래그 벡터 개수 */ MAX_DRAG_VECTORS: 8, /** 최대 강도 배열 크기 */ MAX_STRENGTHS: 8 }; var ANIMATION_CONFIG = { /** 목표 FPS */ TARGET_FPS: 60, /** 델타 타임 (약 16.67ms) */ DELTA_TIME: 1 / 60 }; var DEFAULT_AREA = { /** 기본 왜곡 강도 */ DISTORTION_STRENGTH: 0.5, /** 기본 애니메이션 지속 시간 (초) */ DURATION: 2, /** 기본 이징 함수 */ EASING: "easeInOut", /** 기본 벡터 A */ VECTOR_A: { x: 0.1, y: 0.1 }, /** 기본 벡터 B */ VECTOR_B: { x: -0.1, y: -0.1 } }; // src/components/ImageDistortion.tsx var import_jsx_runtime = require("react/jsx-runtime"); var ImageDistortion = ({ imageSrc, areas, vertexShaderPath, fragmentShaderPath, isPlaying = true, style, className }) => { const containerRef = (0, import_react2.useRef)(null); const sceneRef = (0, import_react2.useRef)(null); const shaderManagerRef = (0, import_react2.useRef)(new ShaderManager()); const textureRef = (0, import_react2.useRef)(null); const [isReady, setIsReady] = (0, import_react2.useState)(false); const [currentAreas, setCurrentAreas] = (0, import_react2.useState)(areas); (0, import_react2.useEffect)(() => { setCurrentAreas(areas); }, [areas]); (0, import_react2.useEffect)(() => { console.log("[ImageDistortion] useEffect \uC2E4\uD589, containerRef.current:", containerRef.current); if (!containerRef.current) { console.warn("[ImageDistortion] containerRef.current\uAC00 null\uC785\uB2C8\uB2E4. \uCEF4\uD3EC\uB10C\uD2B8\uAC00 \uC81C\uB300\uB85C \uB9C8\uC6B4\uD2B8\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4."); return; } console.log("[ImageDistortion] \uCD08\uAE30\uD654 \uC2DC\uC791"); const scene = new ThreeScene(containerRef.current); sceneRef.current = scene; const vertPath = vertexShaderPath || "/shaders/distortion.vert.glsl"; const fragPath = fragmentShaderPath || "/shaders/distortion.frag.glsl"; console.log("[ImageDistortion] \uC170\uC774\uB354 \uB85C\uB4DC \uC2DC\uB3C4:", { vertPath, fragPath }); shaderManagerRef.current.loadShaders(vertPath, fragPath).then(({ vertex, fragment }) => { console.log("[ImageDistortion] \uC170\uC774\uB354 \uB85C\uB4DC \uC131\uACF5"); scene.setShaderMaterial(vertex, fragment); setIsReady(true); }).catch((error) => { console.error("[ImageDistortion] \uC170\uC774\uB354 \uB85C\uB4DC \uC2E4\uD328:", error); }); return () => { scene.dispose(); if (textureRef.current) { textureRef.current.dispose(); } }; }, [vertexShaderPath, fragmentShaderPath]); (0, import_react2.useEffect)(() => { if (!imageSrc || !isReady) { console.log("[ImageDistortion] \uC774\uBBF8\uC9C0 \uB85C\uB4DC \uC2A4\uD0B5:", { imageSrc, isReady }); return; } console.log("[ImageDistortion] \uC774\uBBF8\uC9C0 \uB85C\uB4DC \uC2DC\uC791:", imageSrc); const loader = new THREE2.TextureLoader(); loader.load( imageSrc, (texture) => { console.log("[ImageDistortion] \uC774\uBBF8\uC9C0 \uB85C\uB4DC \uC131\uACF5"); textureRef.current = texture; if (sceneRef.current) { sceneRef.current.updateUniforms({ u_texture: { value: texture } }); sceneRef.current.render(); } }, void 0, (error) => { console.error("[ImageDistortion] \uC774\uBBF8\uC9C0 \uB85C\uB4DC \uC2E4\uD328:", error); } ); return () => { if (textureRef.current) { textureRef.current.dispose(); textureRef.current = null; } }; }, [imageSrc, isReady]); (0, import_react2.useEffect)(() => { if (!sceneRef.current || !isReady) return; 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; }); }); 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; }); const strengths = new Float32Array(SHADER_CONFIG.MAX_STRENGTHS); currentAreas.forEach((area, index) => { strengths[index] = area.distortionStrength; }); sceneRef.current.updateUniforms({ u_numAreas: { value: currentAreas.length }, u_points: { value: points }, u_dragVectors: { value: dragVectors }, u_distortionStrengths: { value: strengths } }); sceneRef.current.render(); }, [currentAreas, isReady]); const animationCallback = (0, import_react2.useCallback)((deltaTime) => { if (!isReady) return; setCurrentAreas((prevAreas) => { const updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime); return AnimationLoop.updateAreaDragVectors(updatedAreas); }); }, [isReady]); useAnimationFrame(animationCallback, isPlaying); return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "div", { ref: containerRef, style: { width: "100%", height: "100%", position: "relative", ...style }, className } ); }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ANIMATION_CONFIG, AnimationLoop, DEFAULT_AREA, ImageDistortion, SHADER_CONFIG, ShaderManager, ThreeScene, applyEasing, useAnimationFrame }); //# sourceMappingURL=index.js.map