Compare commits

..

No commits in common. "6d9dd082c16c24fa23d55a67e7eccdd8cb3a2246" and "6b6c8d8fd0a236a28832c8f5e93428f090d14977" have entirely different histories.

18 changed files with 47 additions and 265 deletions

1
.npmrc
View File

@ -1 +0,0 @@
//git.bnovalab.com/api/packages/baekryang/npm/:_authToken=a2ed709f39e95662493a92305555a4bf70f6fe10

View File

@ -4,7 +4,6 @@ uniform vec2 u_points[32]; // 최대 8영역 × 4포인트 (정규화된
uniform int u_numAreas; uniform int u_numAreas;
uniform vec2 u_dragVectors[8]; // 드래그 벡터 (정규화된 좌표 0-1) uniform vec2 u_dragVectors[8]; // 드래그 벡터 (정규화된 좌표 0-1)
uniform float u_distortionStrengths[8]; uniform float u_distortionStrengths[8];
uniform float u_lensEffects[8];
varying vec2 vUv; varying vec2 vUv;
@ -75,15 +74,6 @@ void main() {
// dragVector는 정규화된 좌표(0-1)이므로 바로 사용 // dragVector는 정규화된 좌표(0-1)이므로 바로 사용
vec2 distortion = u_dragVectors[i] * influence * u_distortionStrengths[i]; vec2 distortion = u_dragVectors[i] * influence * u_distortionStrengths[i];
texCoord += distortion; texCoord += distortion;
// 렌즈 왜곡 효과 (방사형 UV 왜곡)
if (abs(u_lensEffects[i]) > 0.001) {
vec2 centered = uv_local - vec2(0.5);
float dist2 = dot(centered, centered);
float lensK = u_lensEffects[i] * 2.0; // 강도 스케일링
vec2 lensDistortion = centered * lensK * dist2;
texCoord += lensDistortion * u_distortionStrengths[i];
}
} }
} }
} }

15
dist/index.d.mts vendored
View File

@ -11,7 +11,7 @@ interface Point {
/** /**
* *
*/ */
type EasingFunction = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'easeInQuad' | 'easeOutQuad' | 'easeInCubic' | 'easeOutCubic' | 'steps2' | 'steps3' | 'steps4' | 'steps5' | 'steps6' | 'steps8' | 'steps10'; type EasingFunction = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'easeInQuad' | 'easeOutQuad' | 'easeInCubic' | 'easeOutCubic';
/** /**
* *
*/ */
@ -68,11 +68,6 @@ interface DistortionArea {
influenceRadius: number; influenceRadius: number;
maxStrength: number; maxStrength: number;
}; };
/** 렌즈 효과 설정 (선택사항) */
lensEffect?: {
/** 렌즈 강도 (양수: 볼록, 음수: 오목, 0: 없음, 범위: -1.0 ~ 1.0) */
strength: number;
};
} }
/** /**
* *
@ -101,8 +96,6 @@ interface ShaderUniforms {
u_dragVectors: THREE.IUniform<Float32Array>; u_dragVectors: THREE.IUniform<Float32Array>;
/** 각 영역의 왜곡 강도 배열 */ /** 각 영역의 왜곡 강도 배열 */
u_distortionStrengths: THREE.IUniform<Float32Array>; u_distortionStrengths: THREE.IUniform<Float32Array>;
/** 각 영역의 렌즈 효과 강도 배열 */
u_lensEffects: THREE.IUniform<Float32Array>;
} }
/** /**
* *
@ -212,6 +205,8 @@ interface ImageDistortionProps {
vertexShaderPath?: string; vertexShaderPath?: string;
/** 프래그먼트 셰이더 경로 (선택사항) */ /** 프래그먼트 셰이더 경로 (선택사항) */
fragmentShaderPath?: string; fragmentShaderPath?: string;
/** 애니메이션 재생 여부 */
isPlaying?: boolean;
/** 컨테이너 스타일 */ /** 컨테이너 스타일 */
style?: React$1.CSSProperties; style?: React$1.CSSProperties;
/** 컨테이너 클래스명 */ /** 컨테이너 클래스명 */
@ -395,8 +390,6 @@ declare const SHADER_CONFIG: {
readonly MAX_DRAG_VECTORS: 8; readonly MAX_DRAG_VECTORS: 8;
/** 최대 강도 배열 크기 */ /** 최대 강도 배열 크기 */
readonly MAX_STRENGTHS: 8; readonly MAX_STRENGTHS: 8;
/** 최대 렌즈 효과 배열 크기 */
readonly MAX_LENS_EFFECTS: 8;
}; };
/** /**
* *
@ -427,8 +420,6 @@ declare const DEFAULT_AREA: {
readonly x: -0.1; readonly x: -0.1;
readonly y: -0.1; readonly y: -0.1;
}; };
/** 기본 렌즈 효과 강도 */
readonly LENS_STRENGTH: 0;
}; };
/** /**

15
dist/index.d.ts vendored
View File

@ -11,7 +11,7 @@ interface Point {
/** /**
* *
*/ */
type EasingFunction = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'easeInQuad' | 'easeOutQuad' | 'easeInCubic' | 'easeOutCubic' | 'steps2' | 'steps3' | 'steps4' | 'steps5' | 'steps6' | 'steps8' | 'steps10'; type EasingFunction = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'easeInQuad' | 'easeOutQuad' | 'easeInCubic' | 'easeOutCubic';
/** /**
* *
*/ */
@ -68,11 +68,6 @@ interface DistortionArea {
influenceRadius: number; influenceRadius: number;
maxStrength: number; maxStrength: number;
}; };
/** 렌즈 효과 설정 (선택사항) */
lensEffect?: {
/** 렌즈 강도 (양수: 볼록, 음수: 오목, 0: 없음, 범위: -1.0 ~ 1.0) */
strength: number;
};
} }
/** /**
* *
@ -101,8 +96,6 @@ interface ShaderUniforms {
u_dragVectors: THREE.IUniform<Float32Array>; u_dragVectors: THREE.IUniform<Float32Array>;
/** 각 영역의 왜곡 강도 배열 */ /** 각 영역의 왜곡 강도 배열 */
u_distortionStrengths: THREE.IUniform<Float32Array>; u_distortionStrengths: THREE.IUniform<Float32Array>;
/** 각 영역의 렌즈 효과 강도 배열 */
u_lensEffects: THREE.IUniform<Float32Array>;
} }
/** /**
* *
@ -212,6 +205,8 @@ interface ImageDistortionProps {
vertexShaderPath?: string; vertexShaderPath?: string;
/** 프래그먼트 셰이더 경로 (선택사항) */ /** 프래그먼트 셰이더 경로 (선택사항) */
fragmentShaderPath?: string; fragmentShaderPath?: string;
/** 애니메이션 재생 여부 */
isPlaying?: boolean;
/** 컨테이너 스타일 */ /** 컨테이너 스타일 */
style?: React$1.CSSProperties; style?: React$1.CSSProperties;
/** 컨테이너 클래스명 */ /** 컨테이너 클래스명 */
@ -395,8 +390,6 @@ declare const SHADER_CONFIG: {
readonly MAX_DRAG_VECTORS: 8; readonly MAX_DRAG_VECTORS: 8;
/** 최대 강도 배열 크기 */ /** 최대 강도 배열 크기 */
readonly MAX_STRENGTHS: 8; readonly MAX_STRENGTHS: 8;
/** 최대 렌즈 효과 배열 크기 */
readonly MAX_LENS_EFFECTS: 8;
}; };
/** /**
* *
@ -427,8 +420,6 @@ declare const DEFAULT_AREA: {
readonly x: -0.1; readonly x: -0.1;
readonly y: -0.1; readonly y: -0.1;
}; };
/** 기본 렌즈 효과 강도 */
readonly LENS_STRENGTH: 0;
}; };
/** /**

79
dist/index.js vendored
View File

@ -96,8 +96,7 @@ var ThreeScene = class {
u_numAreas: { value: 0 }, u_numAreas: { value: 0 },
u_dragVectors: { value: new Float32Array(16) }, u_dragVectors: { value: new Float32Array(16) },
// 8벡터 × 2(x,y) // 8벡터 × 2(x,y)
u_distortionStrengths: { value: new Float32Array(8) }, u_distortionStrengths: { value: new Float32Array(8) }
u_lensEffects: { value: new Float32Array(8) }
}; };
this.handleResize(); this.handleResize();
window.addEventListener("resize", this.handleResize); window.addEventListener("resize", this.handleResize);
@ -243,7 +242,6 @@ var ShaderManager = class {
}; };
// src/utils/easing.ts // src/utils/easing.ts
var createStepEasing = (steps) => (t) => Math.floor(t * steps) / steps;
var easingFunctions = { var easingFunctions = {
linear: (t) => t, linear: (t) => t,
easeIn: (t) => t * t, easeIn: (t) => t * t,
@ -252,14 +250,7 @@ var easingFunctions = {
easeInQuad: (t) => t * t, easeInQuad: (t) => t * t,
easeOutQuad: (t) => t * (2 - t), easeOutQuad: (t) => t * (2 - t),
easeInCubic: (t) => t * t * t, easeInCubic: (t) => t * t * t,
easeOutCubic: (t) => 1 - Math.pow(1 - t, 3), easeOutCubic: (t) => 1 - Math.pow(1 - t, 3)
steps2: createStepEasing(2),
steps3: createStepEasing(3),
steps4: createStepEasing(4),
steps5: createStepEasing(5),
steps6: createStepEasing(6),
steps8: createStepEasing(8),
steps10: createStepEasing(10)
}; };
var applyEasing = (progress, easingType) => { var applyEasing = (progress, easingType) => {
const clampedProgress = Math.max(0, Math.min(1, progress)); const clampedProgress = Math.max(0, Math.min(1, progress));
@ -817,9 +808,7 @@ var SHADER_CONFIG = {
/** 최대 드래그 벡터 개수 */ /** 최대 드래그 벡터 개수 */
MAX_DRAG_VECTORS: 8, MAX_DRAG_VECTORS: 8,
/** 최대 강도 배열 크기 */ /** 최대 강도 배열 크기 */
MAX_STRENGTHS: 8, MAX_STRENGTHS: 8
/** 최대 렌즈 효과 배열 크기 */
MAX_LENS_EFFECTS: 8
}; };
var ANIMATION_CONFIG = { var ANIMATION_CONFIG = {
/** 목표 FPS */ /** 목표 FPS */
@ -837,9 +826,7 @@ var DEFAULT_AREA = {
/** 기본 벡터 A */ /** 기본 벡터 A */
VECTOR_A: { x: 0.1, y: 0.1 }, VECTOR_A: { x: 0.1, y: 0.1 },
/** 기본 벡터 B */ /** 기본 벡터 B */
VECTOR_B: { x: -0.1, y: -0.1 }, VECTOR_B: { x: -0.1, y: -0.1 }
/** 기본 렌즈 효과 강도 */
LENS_STRENGTH: 0
}; };
// src/components/ImageDistortion.tsx // src/components/ImageDistortion.tsx
@ -849,6 +836,7 @@ var ImageDistortion = ({
areas, areas,
vertexShaderPath, vertexShaderPath,
fragmentShaderPath, fragmentShaderPath,
isPlaying = true,
style, style,
className, className,
mouseInteraction mouseInteraction
@ -971,16 +959,11 @@ var ImageDistortion = ({
currentAreas.forEach((area, index) => { currentAreas.forEach((area, index) => {
strengths[index] = area.distortionStrength; strengths[index] = area.distortionStrength;
}); });
const lensEffects = new Float32Array(SHADER_CONFIG.MAX_LENS_EFFECTS);
currentAreas.forEach((area, index) => {
lensEffects[index] = area.lensEffect?.strength ?? 0;
});
sceneRef.current.updateUniforms({ sceneRef.current.updateUniforms({
u_numAreas: { value: currentAreas.length }, u_numAreas: { value: currentAreas.length },
u_points: { value: points }, u_points: { value: points },
u_dragVectors: { value: dragVectors }, u_dragVectors: { value: dragVectors },
u_distortionStrengths: { value: strengths }, u_distortionStrengths: { value: strengths }
u_lensEffects: { value: lensEffects }
}); });
sceneRef.current.render(); sceneRef.current.render();
}, [currentAreas, isReady]); }, [currentAreas, isReady]);
@ -1007,7 +990,7 @@ var ImageDistortion = ({
return updatedAreas; return updatedAreas;
}); });
}, [isReady, mouseInteraction, mouseInteractionHook]); }, [isReady, mouseInteraction, mouseInteractionHook]);
useAnimationFrame(animationCallback, true); useAnimationFrame(animationCallback, isPlaying || mouseInteraction?.enabled || false);
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"div", "div",
{ {
@ -1110,14 +1093,7 @@ var EASING_OPTIONS = [
{ value: "easeOut", label: "\uAC10\uC18D (Ease Out)" }, { value: "easeOut", label: "\uAC10\uC18D (Ease Out)" },
{ value: "easeInOut", label: "\uAC00\uAC10\uC18D (Ease In Out)" }, { value: "easeInOut", label: "\uAC00\uAC10\uC18D (Ease In Out)" },
{ value: "easeInQuad", label: "\uAC00\uC18D\xB2 (Ease In Quad)" }, { value: "easeInQuad", label: "\uAC00\uC18D\xB2 (Ease In Quad)" },
{ value: "easeOutQuad", label: "\uAC10\uC18D\xB2 (Ease Out Quad)" }, { value: "easeOutQuad", label: "\uAC10\uC18D\xB2 (Ease Out Quad)" }
{ value: "steps2", label: "2\uB2E8\uACC4 \uC2A4\uD15D" },
{ value: "steps3", label: "3\uB2E8\uACC4 \uC2A4\uD15D" },
{ value: "steps4", label: "4\uB2E8\uACC4 \uC2A4\uD15D" },
{ value: "steps5", label: "5\uB2E8\uACC4 \uC2A4\uD15D" },
{ value: "steps6", label: "6\uB2E8\uACC4 \uC2A4\uD15D" },
{ value: "steps8", label: "8\uB2E8\uACC4 \uC2A4\uD15D" },
{ value: "steps10", label: "10\uB2E8\uACC4 \uC2A4\uD15D" }
]; ];
var ParameterPanel = ({ area, onUpdateArea }) => { var ParameterPanel = ({ area, onUpdateArea }) => {
if (!area) { if (!area) {
@ -1179,26 +1155,6 @@ var ParameterPanel = ({ area, onUpdateArea }) => {
} }
) )
] }), ] }),
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "parameter-group", children: [
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { children: [
"\uB80C\uC988 \uD6A8\uACFC: ",
(area.lensEffect?.strength ?? 0) > 0 ? "\uBCFC\uB85D " : (area.lensEffect?.strength ?? 0) < 0 ? "\uC624\uBAA9 " : "",
((area.lensEffect?.strength ?? 0) * 100).toFixed(0),
"%"
] }),
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
"input",
{
type: "range",
min: "-1",
max: "1",
step: "0.01",
value: area.lensEffect?.strength ?? 0,
onChange: (e) => onUpdateArea({ lensEffect: { strength: parseFloat(e.target.value) } }),
className: "slider"
}
)
] }),
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "parameter-group", children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "parameter-group", children: [
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { children: "\uD3EC\uC778\uD2B8 \uC88C\uD45C (\uCE94\uBC84\uC2A4\uC5D0\uC11C \uB4DC\uB798\uADF8)" }), /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { children: "\uD3EC\uC778\uD2B8 \uC88C\uD45C (\uCE94\uBC84\uC2A4\uC5D0\uC11C \uB4DC\uB798\uADF8)" }),
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "points-display", children: area.basePoints.map((point, idx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "point-coord", children: [ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "points-display", children: area.basePoints.map((point, idx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "point-coord", children: [
@ -1386,18 +1342,9 @@ var EditorCanvas = ({
}), [customStyle]); }), [customStyle]);
(0, import_react6.useEffect)(() => { (0, import_react6.useEffect)(() => {
if (!containerRef.current) return; if (!containerRef.current) return;
const updateSize = () => { const rect = containerRef.current.getBoundingClientRect();
if (!containerRef.current) return; setCanvasSize({ width: rect.width, height: rect.height });
const rect = containerRef.current.getBoundingClientRect(); }, [width, height]);
setCanvasSize({ width: rect.width, height: rect.height });
};
updateSize();
const resizeObserver = new ResizeObserver(updateSize);
resizeObserver.observe(containerRef.current);
return () => {
resizeObserver.disconnect();
};
}, []);
const selectedArea = areas.find((a) => a.id === selectedAreaId); const selectedArea = areas.find((a) => a.id === selectedAreaId);
const isPointInPolygon2 = (0, import_react6.useCallback)((point, polygon) => { const isPointInPolygon2 = (0, import_react6.useCallback)((point, polygon) => {
let inside = false; let inside = false;
@ -1583,8 +1530,8 @@ var EditorCanvas = ({
ref: containerRef, ref: containerRef,
className: "editor-canvas", className: "editor-canvas",
style: { style: {
width: "100%", width,
height: "100%", height,
position: "relative", position: "relative",
cursor: showEditor ? getCursorStyle() : "default", cursor: showEditor ? getCursorStyle() : "default",
pointerEvents: showEditor ? "auto" : "none", pointerEvents: showEditor ? "auto" : "none",

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

79
dist/index.mjs vendored
View File

@ -36,8 +36,7 @@ var ThreeScene = class {
u_numAreas: { value: 0 }, u_numAreas: { value: 0 },
u_dragVectors: { value: new Float32Array(16) }, u_dragVectors: { value: new Float32Array(16) },
// 8벡터 × 2(x,y) // 8벡터 × 2(x,y)
u_distortionStrengths: { value: new Float32Array(8) }, u_distortionStrengths: { value: new Float32Array(8) }
u_lensEffects: { value: new Float32Array(8) }
}; };
this.handleResize(); this.handleResize();
window.addEventListener("resize", this.handleResize); window.addEventListener("resize", this.handleResize);
@ -183,7 +182,6 @@ var ShaderManager = class {
}; };
// src/utils/easing.ts // src/utils/easing.ts
var createStepEasing = (steps) => (t) => Math.floor(t * steps) / steps;
var easingFunctions = { var easingFunctions = {
linear: (t) => t, linear: (t) => t,
easeIn: (t) => t * t, easeIn: (t) => t * t,
@ -192,14 +190,7 @@ var easingFunctions = {
easeInQuad: (t) => t * t, easeInQuad: (t) => t * t,
easeOutQuad: (t) => t * (2 - t), easeOutQuad: (t) => t * (2 - t),
easeInCubic: (t) => t * t * t, easeInCubic: (t) => t * t * t,
easeOutCubic: (t) => 1 - Math.pow(1 - t, 3), easeOutCubic: (t) => 1 - Math.pow(1 - t, 3)
steps2: createStepEasing(2),
steps3: createStepEasing(3),
steps4: createStepEasing(4),
steps5: createStepEasing(5),
steps6: createStepEasing(6),
steps8: createStepEasing(8),
steps10: createStepEasing(10)
}; };
var applyEasing = (progress, easingType) => { var applyEasing = (progress, easingType) => {
const clampedProgress = Math.max(0, Math.min(1, progress)); const clampedProgress = Math.max(0, Math.min(1, progress));
@ -757,9 +748,7 @@ var SHADER_CONFIG = {
/** 최대 드래그 벡터 개수 */ /** 최대 드래그 벡터 개수 */
MAX_DRAG_VECTORS: 8, MAX_DRAG_VECTORS: 8,
/** 최대 강도 배열 크기 */ /** 최대 강도 배열 크기 */
MAX_STRENGTHS: 8, MAX_STRENGTHS: 8
/** 최대 렌즈 효과 배열 크기 */
MAX_LENS_EFFECTS: 8
}; };
var ANIMATION_CONFIG = { var ANIMATION_CONFIG = {
/** 목표 FPS */ /** 목표 FPS */
@ -777,9 +766,7 @@ var DEFAULT_AREA = {
/** 기본 벡터 A */ /** 기본 벡터 A */
VECTOR_A: { x: 0.1, y: 0.1 }, VECTOR_A: { x: 0.1, y: 0.1 },
/** 기본 벡터 B */ /** 기본 벡터 B */
VECTOR_B: { x: -0.1, y: -0.1 }, VECTOR_B: { x: -0.1, y: -0.1 }
/** 기본 렌즈 효과 강도 */
LENS_STRENGTH: 0
}; };
// src/components/ImageDistortion.tsx // src/components/ImageDistortion.tsx
@ -789,6 +776,7 @@ var ImageDistortion = ({
areas, areas,
vertexShaderPath, vertexShaderPath,
fragmentShaderPath, fragmentShaderPath,
isPlaying = true,
style, style,
className, className,
mouseInteraction mouseInteraction
@ -911,16 +899,11 @@ var ImageDistortion = ({
currentAreas.forEach((area, index) => { currentAreas.forEach((area, index) => {
strengths[index] = area.distortionStrength; strengths[index] = area.distortionStrength;
}); });
const lensEffects = new Float32Array(SHADER_CONFIG.MAX_LENS_EFFECTS);
currentAreas.forEach((area, index) => {
lensEffects[index] = area.lensEffect?.strength ?? 0;
});
sceneRef.current.updateUniforms({ sceneRef.current.updateUniforms({
u_numAreas: { value: currentAreas.length }, u_numAreas: { value: currentAreas.length },
u_points: { value: points }, u_points: { value: points },
u_dragVectors: { value: dragVectors }, u_dragVectors: { value: dragVectors },
u_distortionStrengths: { value: strengths }, u_distortionStrengths: { value: strengths }
u_lensEffects: { value: lensEffects }
}); });
sceneRef.current.render(); sceneRef.current.render();
}, [currentAreas, isReady]); }, [currentAreas, isReady]);
@ -947,7 +930,7 @@ var ImageDistortion = ({
return updatedAreas; return updatedAreas;
}); });
}, [isReady, mouseInteraction, mouseInteractionHook]); }, [isReady, mouseInteraction, mouseInteractionHook]);
useAnimationFrame(animationCallback, true); useAnimationFrame(animationCallback, isPlaying || mouseInteraction?.enabled || false);
return /* @__PURE__ */ jsx( return /* @__PURE__ */ jsx(
"div", "div",
{ {
@ -1050,14 +1033,7 @@ var EASING_OPTIONS = [
{ value: "easeOut", label: "\uAC10\uC18D (Ease Out)" }, { value: "easeOut", label: "\uAC10\uC18D (Ease Out)" },
{ value: "easeInOut", label: "\uAC00\uAC10\uC18D (Ease In Out)" }, { value: "easeInOut", label: "\uAC00\uAC10\uC18D (Ease In Out)" },
{ value: "easeInQuad", label: "\uAC00\uC18D\xB2 (Ease In Quad)" }, { value: "easeInQuad", label: "\uAC00\uC18D\xB2 (Ease In Quad)" },
{ value: "easeOutQuad", label: "\uAC10\uC18D\xB2 (Ease Out Quad)" }, { value: "easeOutQuad", label: "\uAC10\uC18D\xB2 (Ease Out Quad)" }
{ value: "steps2", label: "2\uB2E8\uACC4 \uC2A4\uD15D" },
{ value: "steps3", label: "3\uB2E8\uACC4 \uC2A4\uD15D" },
{ value: "steps4", label: "4\uB2E8\uACC4 \uC2A4\uD15D" },
{ value: "steps5", label: "5\uB2E8\uACC4 \uC2A4\uD15D" },
{ value: "steps6", label: "6\uB2E8\uACC4 \uC2A4\uD15D" },
{ value: "steps8", label: "8\uB2E8\uACC4 \uC2A4\uD15D" },
{ value: "steps10", label: "10\uB2E8\uACC4 \uC2A4\uD15D" }
]; ];
var ParameterPanel = ({ area, onUpdateArea }) => { var ParameterPanel = ({ area, onUpdateArea }) => {
if (!area) { if (!area) {
@ -1119,26 +1095,6 @@ var ParameterPanel = ({ area, onUpdateArea }) => {
} }
) )
] }), ] }),
/* @__PURE__ */ jsxs2("div", { className: "parameter-group", children: [
/* @__PURE__ */ jsxs2("label", { children: [
"\uB80C\uC988 \uD6A8\uACFC: ",
(area.lensEffect?.strength ?? 0) > 0 ? "\uBCFC\uB85D " : (area.lensEffect?.strength ?? 0) < 0 ? "\uC624\uBAA9 " : "",
((area.lensEffect?.strength ?? 0) * 100).toFixed(0),
"%"
] }),
/* @__PURE__ */ jsx3(
"input",
{
type: "range",
min: "-1",
max: "1",
step: "0.01",
value: area.lensEffect?.strength ?? 0,
onChange: (e) => onUpdateArea({ lensEffect: { strength: parseFloat(e.target.value) } }),
className: "slider"
}
)
] }),
/* @__PURE__ */ jsxs2("div", { className: "parameter-group", children: [ /* @__PURE__ */ jsxs2("div", { className: "parameter-group", children: [
/* @__PURE__ */ jsx3("label", { children: "\uD3EC\uC778\uD2B8 \uC88C\uD45C (\uCE94\uBC84\uC2A4\uC5D0\uC11C \uB4DC\uB798\uADF8)" }), /* @__PURE__ */ jsx3("label", { children: "\uD3EC\uC778\uD2B8 \uC88C\uD45C (\uCE94\uBC84\uC2A4\uC5D0\uC11C \uB4DC\uB798\uADF8)" }),
/* @__PURE__ */ jsx3("div", { className: "points-display", children: area.basePoints.map((point, idx) => /* @__PURE__ */ jsxs2("div", { className: "point-coord", children: [ /* @__PURE__ */ jsx3("div", { className: "points-display", children: area.basePoints.map((point, idx) => /* @__PURE__ */ jsxs2("div", { className: "point-coord", children: [
@ -1326,18 +1282,9 @@ var EditorCanvas = ({
}), [customStyle]); }), [customStyle]);
useEffect4(() => { useEffect4(() => {
if (!containerRef.current) return; if (!containerRef.current) return;
const updateSize = () => { const rect = containerRef.current.getBoundingClientRect();
if (!containerRef.current) return; setCanvasSize({ width: rect.width, height: rect.height });
const rect = containerRef.current.getBoundingClientRect(); }, [width, height]);
setCanvasSize({ width: rect.width, height: rect.height });
};
updateSize();
const resizeObserver = new ResizeObserver(updateSize);
resizeObserver.observe(containerRef.current);
return () => {
resizeObserver.disconnect();
};
}, []);
const selectedArea = areas.find((a) => a.id === selectedAreaId); const selectedArea = areas.find((a) => a.id === selectedAreaId);
const isPointInPolygon2 = useCallback5((point, polygon) => { const isPointInPolygon2 = useCallback5((point, polygon) => {
let inside = false; let inside = false;
@ -1523,8 +1470,8 @@ var EditorCanvas = ({
ref: containerRef, ref: containerRef,
className: "editor-canvas", className: "editor-canvas",
style: { style: {
width: "100%", width,
height: "100%", height,
position: "relative", position: "relative",
cursor: showEditor ? getCursorStyle() : "default", cursor: showEditor ? getCursorStyle() : "default",
pointerEvents: showEditor ? "auto" : "none", pointerEvents: showEditor ? "auto" : "none",

2
dist/index.mjs.map vendored

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"name": "@baekryang/responsive-image-canvas", "name": "@baekryang/responsive-image-canvas",
"version": "1.2.7", "version": "1.2.1",
"publishConfig": { "publishConfig": {
"registry": "https://git.bnovalab.com/api/packages/baekryang/npm/" "registry": "https://git.bnovalab.com/api/packages/baekryang/npm/"
}, },

View File

@ -21,6 +21,8 @@ export interface ImageDistortionProps {
vertexShaderPath?: string; vertexShaderPath?: string;
/** 프래그먼트 셰이더 경로 (선택사항) */ /** 프래그먼트 셰이더 경로 (선택사항) */
fragmentShaderPath?: string; fragmentShaderPath?: string;
/** 애니메이션 재생 여부 */
isPlaying?: boolean;
/** 컨테이너 스타일 */ /** 컨테이너 스타일 */
style?: React.CSSProperties; style?: React.CSSProperties;
/** 컨테이너 클래스명 */ /** 컨테이너 클래스명 */
@ -38,6 +40,7 @@ export const ImageDistortion: React.FC<ImageDistortionProps> = ({
areas, areas,
vertexShaderPath, vertexShaderPath,
fragmentShaderPath, fragmentShaderPath,
isPlaying = true,
style, style,
className, className,
mouseInteraction, mouseInteraction,
@ -196,18 +199,11 @@ export const ImageDistortion: React.FC<ImageDistortionProps> = ({
strengths[index] = area.distortionStrength; strengths[index] = area.distortionStrength;
}); });
// 렌즈 효과 배열 생성
const lensEffects = new Float32Array(SHADER_CONFIG.MAX_LENS_EFFECTS);
currentAreas.forEach((area, index) => {
lensEffects[index] = area.lensEffect?.strength ?? 0;
});
sceneRef.current.updateUniforms({ sceneRef.current.updateUniforms({
u_numAreas: { value: currentAreas.length }, u_numAreas: { value: currentAreas.length },
u_points: { value: points }, u_points: { value: points },
u_dragVectors: { value: dragVectors }, u_dragVectors: { value: dragVectors },
u_distortionStrengths: { value: strengths }, u_distortionStrengths: { value: strengths },
u_lensEffects: { value: lensEffects },
}); });
sceneRef.current.render(); sceneRef.current.render();
@ -247,8 +243,8 @@ export const ImageDistortion: React.FC<ImageDistortionProps> = ({
}); });
}, [isReady, mouseInteraction, mouseInteractionHook]); }, [isReady, mouseInteraction, mouseInteractionHook]);
// 애니메이션 루프 실행 // 애니메이션은 항상 실행 (마우스 인터랙션 포함)
useAnimationFrame(animationCallback, true); useAnimationFrame(animationCallback, isPlaying || mouseInteraction?.enabled || false);
return ( return (
<div <div

View File

@ -62,27 +62,12 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
}, },
}), [customStyle]); }), [customStyle]);
// 컨테이너 크기 측정 (ResizeObserver 사용) // 컨테이너 크기 측정
useEffect(() => { useEffect(() => {
if (!containerRef.current) return; if (!containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const updateSize = () => { setCanvasSize({width: rect.width, height: rect.height});
if (!containerRef.current) return; }, [width, height]);
const rect = containerRef.current.getBoundingClientRect();
setCanvasSize({width: rect.width, height: rect.height});
};
// 초기 크기 설정
updateSize();
// ResizeObserver로 크기 변경 감지
const resizeObserver = new ResizeObserver(updateSize);
resizeObserver.observe(containerRef.current);
return () => {
resizeObserver.disconnect();
};
}, []);
// 선택된 영역 찾기 // 선택된 영역 찾기
const selectedArea = areas.find((a) => a.id === selectedAreaId); const selectedArea = areas.find((a) => a.id === selectedAreaId);
@ -345,8 +330,8 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
ref={containerRef} ref={containerRef}
className="editor-canvas" className="editor-canvas"
style={{ style={{
width: '100%', width,
height: '100%', height,
position: 'relative', position: 'relative',
cursor: showEditor ? getCursorStyle() : 'default', cursor: showEditor ? getCursorStyle() : 'default',
pointerEvents: showEditor ? 'auto' : 'none', pointerEvents: showEditor ? 'auto' : 'none',

View File

@ -13,13 +13,6 @@ const EASING_OPTIONS: { value: EasingFunction; label: string }[] = [
{ value: 'easeInOut', label: '가감속 (Ease In Out)' }, { value: 'easeInOut', label: '가감속 (Ease In Out)' },
{ value: 'easeInQuad', label: '가속² (Ease In Quad)' }, { value: 'easeInQuad', label: '가속² (Ease In Quad)' },
{ value: 'easeOutQuad', label: '감속² (Ease Out Quad)' }, { value: 'easeOutQuad', label: '감속² (Ease Out Quad)' },
{ value: 'steps2', label: '2단계 스텝' },
{ value: 'steps3', label: '3단계 스텝' },
{ value: 'steps4', label: '4단계 스텝' },
{ value: 'steps5', label: '5단계 스텝' },
{ value: 'steps6', label: '6단계 스텝' },
{ value: 'steps8', label: '8단계 스텝' },
{ value: 'steps10', label: '10단계 스텝' },
]; ];
export const ParameterPanel: React.FC<ParameterPanelProps> = ({ area, onUpdateArea }) => { export const ParameterPanel: React.FC<ParameterPanelProps> = ({ area, onUpdateArea }) => {
@ -91,22 +84,6 @@ export const ParameterPanel: React.FC<ParameterPanelProps> = ({ area, onUpdateAr
</select> </select>
</div> </div>
{/* 렌즈 효과 */}
<div className="parameter-group">
<label>
: {((area.lensEffect?.strength ?? 0) > 0 ? '볼록 ' : (area.lensEffect?.strength ?? 0) < 0 ? '오목 ' : '')}{((area.lensEffect?.strength ?? 0) * 100).toFixed(0)}%
</label>
<input
type="range"
min="-1"
max="1"
step="0.01"
value={area.lensEffect?.strength ?? 0}
onChange={(e) => onUpdateArea({ lensEffect: { strength: parseFloat(e.target.value) } })}
className="slider"
/>
</div>
{/* 포인트 좌표 (읽기 전용 표시) */} {/* 포인트 좌표 (읽기 전용 표시) */}
<div className="parameter-group"> <div className="parameter-group">
<label> ( )</label> <label> ( )</label>

View File

@ -34,7 +34,6 @@ export class ThreeScene {
u_numAreas: { value: 0 }, u_numAreas: { value: 0 },
u_dragVectors: { value: new Float32Array(16) }, // 8벡터 × 2(x,y) u_dragVectors: { value: new Float32Array(16) }, // 8벡터 × 2(x,y)
u_distortionStrengths: { value: new Float32Array(8) }, u_distortionStrengths: { value: new Float32Array(8) },
u_lensEffects: { value: new Float32Array(8) },
}; };
this.handleResize(); this.handleResize();

View File

@ -4,7 +4,6 @@ uniform vec2 u_points[32]; // 최대 8영역 × 4포인트 (정규화된
uniform int u_numAreas; uniform int u_numAreas;
uniform vec2 u_dragVectors[8]; // 드래그 벡터 (정규화된 좌표 0-1) uniform vec2 u_dragVectors[8]; // 드래그 벡터 (정규화된 좌표 0-1)
uniform float u_distortionStrengths[8]; uniform float u_distortionStrengths[8];
uniform float u_lensEffects[8];
varying vec2 vUv; varying vec2 vUv;
@ -75,15 +74,6 @@ void main() {
// dragVector는 정규화된 좌표(0-1)이므로 바로 사용 // dragVector는 정규화된 좌표(0-1)이므로 바로 사용
vec2 distortion = u_dragVectors[i] * influence * u_distortionStrengths[i]; vec2 distortion = u_dragVectors[i] * influence * u_distortionStrengths[i];
texCoord += distortion; texCoord += distortion;
// 렌즈 왜곡 효과 (방사형 UV 왜곡)
if (abs(u_lensEffects[i]) > 0.001) {
vec2 centered = uv_local - vec2(0.5);
float dist2 = dot(centered, centered);
float lensK = u_lensEffects[i] * 2.0; // 강도 스케일링
vec2 lensDistortion = centered * lensK * dist2;
texCoord += lensDistortion * u_distortionStrengths[i];
}
} }
} }
} }

View File

@ -17,14 +17,7 @@ export type EasingFunction =
| 'easeInQuad' | 'easeInQuad'
| 'easeOutQuad' | 'easeOutQuad'
| 'easeInCubic' | 'easeInCubic'
| 'easeOutCubic' | 'easeOutCubic';
| 'steps2'
| 'steps3'
| 'steps4'
| 'steps5'
| 'steps6'
| 'steps8'
| 'steps10';
/** /**
* *
@ -99,11 +92,6 @@ export interface DistortionArea {
influenceRadius: number; influenceRadius: number;
maxStrength: number; maxStrength: number;
}; };
/** 렌즈 효과 설정 (선택사항) */
lensEffect?: {
/** 렌즈 강도 (양수: 볼록, 음수: 오목, 0: 없음, 범위: -1.0 ~ 1.0) */
strength: number;
};
} }
/** /**

View File

@ -17,8 +17,6 @@ export interface ShaderUniforms {
u_dragVectors: THREE.IUniform<Float32Array>; u_dragVectors: THREE.IUniform<Float32Array>;
/** 각 영역의 왜곡 강도 배열 */ /** 각 영역의 왜곡 강도 배열 */
u_distortionStrengths: THREE.IUniform<Float32Array>; u_distortionStrengths: THREE.IUniform<Float32Array>;
/** 각 영역의 렌즈 효과 강도 배열 */
u_lensEffects: THREE.IUniform<Float32Array>;
} }
/** /**

View File

@ -10,8 +10,6 @@ export const SHADER_CONFIG = {
MAX_DRAG_VECTORS: 8, MAX_DRAG_VECTORS: 8,
/** 최대 강도 배열 크기 */ /** 최대 강도 배열 크기 */
MAX_STRENGTHS: 8, MAX_STRENGTHS: 8,
/** 최대 렌즈 효과 배열 크기 */
MAX_LENS_EFFECTS: 8,
} as const; } as const;
/** /**
@ -38,6 +36,4 @@ export const DEFAULT_AREA = {
VECTOR_A: { x: 0.1, y: 0.1 }, VECTOR_A: { x: 0.1, y: 0.1 },
/** 기본 벡터 B */ /** 기본 벡터 B */
VECTOR_B: { x: -0.1, y: -0.1 }, VECTOR_B: { x: -0.1, y: -0.1 },
/** 기본 렌즈 효과 강도 */
LENS_STRENGTH: 0,
} as const; } as const;

View File

@ -2,10 +2,6 @@ import { type EasingFunction } from '../types';
type EasingFunc = (t: number) => number; type EasingFunc = (t: number) => number;
/** 스텝 이징 헬퍼: floor(t * n) / n → n단계로 양자화 */
const createStepEasing = (steps: number): EasingFunc =>
(t) => Math.floor(t * steps) / steps;
/** /**
* *
*/ */
@ -21,14 +17,6 @@ const easingFunctions: Record<EasingFunction, EasingFunc> = {
easeInCubic: (t) => t * t * t, easeInCubic: (t) => t * t * t,
easeOutCubic: (t) => 1 - Math.pow(1 - t, 3), easeOutCubic: (t) => 1 - Math.pow(1 - t, 3),
steps2: createStepEasing(2),
steps3: createStepEasing(3),
steps4: createStepEasing(4),
steps5: createStepEasing(5),
steps6: createStepEasing(6),
steps8: createStepEasing(8),
steps10: createStepEasing(10),
}; };
/** /**