feat: Add per-area physics configuration
- 영역별 물리 설정 `physics` 옵션을 추가했습니다. - `useMouseInteraction` 훅에서 영역별 물리 설정을 적용하도록 수정했습니다. - `ImageDistortion` 컴포넌트에서 `selectedAreaId` prop을 제거하고, 마우스 인터랙션 시 `interactingAreaIndices`를 활용하도록 변경했습니다. - 셰이더에서 텍스처 좌표가 범위를 벗어날 때 부드럽게 페이드 아웃되도록 수정했습니다.
This commit is contained in:
parent
61952ce79c
commit
e08f34caab
17
dist/distortion.frag.glsl
vendored
17
dist/distortion.frag.glsl
vendored
@ -74,12 +74,21 @@ 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;
|
||||||
// clamp를 루프 내에서 제거: 모든 왜곡을 완전히 누적한 후 마지막에 한 번만 clamp
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 모든 왜곡을 누적한 후 최종적으로 한 번만 clamp
|
// 경계 근처에서 부드럽게 페이드 아웃
|
||||||
texCoord = clamp(texCoord, 0.0, 1.0);
|
// 텍스처 좌표가 0~1 범위를 벗어나면 알파값을 줄여서 자연스럽게 처리
|
||||||
gl_FragColor = texture2D(u_texture, texCoord);
|
vec2 edgeDist = min(texCoord, 1.0 - texCoord);
|
||||||
|
float edgeFade = smoothstep(0.0, 0.05, min(edgeDist.x, edgeDist.y));
|
||||||
|
|
||||||
|
// 범위를 벗어난 좌표는 fract로 래핑하여 반복 효과 (더 자연스러움)
|
||||||
|
vec2 wrappedCoord = fract(texCoord);
|
||||||
|
|
||||||
|
vec4 color = texture2D(u_texture, wrappedCoord);
|
||||||
|
// 경계에서 페이드 아웃 적용
|
||||||
|
color.a *= edgeFade;
|
||||||
|
|
||||||
|
gl_FragColor = color;
|
||||||
}
|
}
|
||||||
11
dist/index.d.mts
vendored
11
dist/index.d.mts
vendored
@ -49,6 +49,14 @@ interface DistortionArea {
|
|||||||
progress: number;
|
progress: number;
|
||||||
/** 현재 드래그 벡터 (progress로부터 계산됨) */
|
/** 현재 드래그 벡터 (progress로부터 계산됨) */
|
||||||
dragVector: Point;
|
dragVector: Point;
|
||||||
|
/** 영역별 물리 설정 (선택사항, 마우스 인터랙션 시 사용) */
|
||||||
|
physics?: {
|
||||||
|
stiffness: number;
|
||||||
|
damping: number;
|
||||||
|
mass: number;
|
||||||
|
influenceRadius: number;
|
||||||
|
maxStrength: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 영역 충돌 감지를 위한 경계 상자
|
* 영역 충돌 감지를 위한 경계 상자
|
||||||
@ -194,8 +202,6 @@ interface ImageDistortionProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
/** 마우스 인터랙션 설정 */
|
/** 마우스 인터랙션 설정 */
|
||||||
mouseInteraction?: MouseInteractionConfig;
|
mouseInteraction?: MouseInteractionConfig;
|
||||||
/** 선택된 영역 ID (이 영역은 자동 애니메이션 제외) */
|
|
||||||
selectedAreaId?: string | null;
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* GPU 가속 이미지 왜곡 컴포넌트
|
* GPU 가속 이미지 왜곡 컴포넌트
|
||||||
@ -560,6 +566,7 @@ declare const useMouseInteraction: (containerRef: React.RefObject<HTMLElement |
|
|||||||
updateConfig: (newConfig: Partial<MouseInteractionConfig>) => void;
|
updateConfig: (newConfig: Partial<MouseInteractionConfig>) => void;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
isDragging: () => boolean;
|
isDragging: () => boolean;
|
||||||
|
getInteractingAreaIndices: () => Set<number>;
|
||||||
};
|
};
|
||||||
|
|
||||||
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 MotionPreset, type MouseInteractionConfig, type MouseState, type Point, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, isRotationPreset, presetToVector, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity };
|
export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, DEFAULT_AREA, type DistortionArea, DistortionEditor, type DistortionEditorProps, type DistortionMovement, type EasingFunction, type EditMode, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MouseInteractionConfig, type MouseState, type Point, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, isRotationPreset, presetToVector, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity };
|
||||||
|
|||||||
11
dist/index.d.ts
vendored
11
dist/index.d.ts
vendored
@ -49,6 +49,14 @@ interface DistortionArea {
|
|||||||
progress: number;
|
progress: number;
|
||||||
/** 현재 드래그 벡터 (progress로부터 계산됨) */
|
/** 현재 드래그 벡터 (progress로부터 계산됨) */
|
||||||
dragVector: Point;
|
dragVector: Point;
|
||||||
|
/** 영역별 물리 설정 (선택사항, 마우스 인터랙션 시 사용) */
|
||||||
|
physics?: {
|
||||||
|
stiffness: number;
|
||||||
|
damping: number;
|
||||||
|
mass: number;
|
||||||
|
influenceRadius: number;
|
||||||
|
maxStrength: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 영역 충돌 감지를 위한 경계 상자
|
* 영역 충돌 감지를 위한 경계 상자
|
||||||
@ -194,8 +202,6 @@ interface ImageDistortionProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
/** 마우스 인터랙션 설정 */
|
/** 마우스 인터랙션 설정 */
|
||||||
mouseInteraction?: MouseInteractionConfig;
|
mouseInteraction?: MouseInteractionConfig;
|
||||||
/** 선택된 영역 ID (이 영역은 자동 애니메이션 제외) */
|
|
||||||
selectedAreaId?: string | null;
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* GPU 가속 이미지 왜곡 컴포넌트
|
* GPU 가속 이미지 왜곡 컴포넌트
|
||||||
@ -560,6 +566,7 @@ declare const useMouseInteraction: (containerRef: React.RefObject<HTMLElement |
|
|||||||
updateConfig: (newConfig: Partial<MouseInteractionConfig>) => void;
|
updateConfig: (newConfig: Partial<MouseInteractionConfig>) => void;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
isDragging: () => boolean;
|
isDragging: () => boolean;
|
||||||
|
getInteractingAreaIndices: () => Set<number>;
|
||||||
};
|
};
|
||||||
|
|
||||||
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 MotionPreset, type MouseInteractionConfig, type MouseState, type Point, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, isRotationPreset, presetToVector, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity };
|
export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, DEFAULT_AREA, type DistortionArea, DistortionEditor, type DistortionEditorProps, type DistortionMovement, type EasingFunction, type EditMode, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MouseInteractionConfig, type MouseState, type Point, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, isRotationPreset, presetToVector, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity };
|
||||||
|
|||||||
50
dist/index.js
vendored
50
dist/index.js
vendored
@ -633,14 +633,21 @@ var useMouseInteraction = (containerRef, config) => {
|
|||||||
const { getState } = useMouseVelocity(containerRef);
|
const { getState } = useMouseVelocity(containerRef);
|
||||||
const [interactingAreaIndices, setInteractingAreaIndices] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
|
const [interactingAreaIndices, setInteractingAreaIndices] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
|
||||||
const springPhysicsMapRef = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
|
const springPhysicsMapRef = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
|
||||||
const getSpringPhysics = (0, import_react3.useCallback)((areaIndex) => {
|
const getSpringPhysics = (0, import_react3.useCallback)((areaIndex, area) => {
|
||||||
if (!springPhysicsMapRef.current.has(areaIndex)) {
|
if (!springPhysicsMapRef.current.has(areaIndex)) {
|
||||||
springPhysicsMapRef.current.set(areaIndex, new SpringPhysics(config.physics));
|
const physicsConfig = area?.physics || config.physics;
|
||||||
|
springPhysicsMapRef.current.set(areaIndex, new SpringPhysics(physicsConfig));
|
||||||
}
|
}
|
||||||
return springPhysicsMapRef.current.get(areaIndex);
|
return springPhysicsMapRef.current.get(areaIndex);
|
||||||
}, [config.physics]);
|
}, [config.physics]);
|
||||||
const updateInteraction = (0, import_react3.useCallback)((areas, deltaTime) => {
|
const updateInteraction = (0, import_react3.useCallback)((areas, deltaTime) => {
|
||||||
if (!config.enabled) return areas;
|
if (!config.enabled) return areas;
|
||||||
|
areas.forEach((area, index) => {
|
||||||
|
if (area.physics && springPhysicsMapRef.current.has(index)) {
|
||||||
|
const spring = springPhysicsMapRef.current.get(index);
|
||||||
|
spring.setConfig(area.physics);
|
||||||
|
}
|
||||||
|
});
|
||||||
const mouseState = getState();
|
const mouseState = getState();
|
||||||
if (mouseState.isDragging && mouseState.position) {
|
if (mouseState.isDragging && mouseState.position) {
|
||||||
const currentlyInAreas = /* @__PURE__ */ new Set();
|
const currentlyInAreas = /* @__PURE__ */ new Set();
|
||||||
@ -648,13 +655,13 @@ var useMouseInteraction = (containerRef, config) => {
|
|||||||
if (isPointInPolygon(mouseState.position, areas[i].basePoints)) {
|
if (isPointInPolygon(mouseState.position, areas[i].basePoints)) {
|
||||||
currentlyInAreas.add(i);
|
currentlyInAreas.add(i);
|
||||||
if (!interactingAreaIndices.has(i)) {
|
if (!interactingAreaIndices.has(i)) {
|
||||||
getSpringPhysics(i).reset();
|
getSpringPhysics(i, areas[i]).reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
interactingAreaIndices.forEach((areaIndex) => {
|
interactingAreaIndices.forEach((areaIndex) => {
|
||||||
if (!currentlyInAreas.has(areaIndex)) {
|
if (!currentlyInAreas.has(areaIndex)) {
|
||||||
getSpringPhysics(areaIndex).returnToEquilibrium();
|
getSpringPhysics(areaIndex, areas[areaIndex]).returnToEquilibrium();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setInteractingAreaIndices(currentlyInAreas);
|
setInteractingAreaIndices(currentlyInAreas);
|
||||||
@ -673,7 +680,7 @@ var useMouseInteraction = (containerRef, config) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
currentlyInAreas.forEach((areaIndex) => {
|
currentlyInAreas.forEach((areaIndex) => {
|
||||||
const spring = getSpringPhysics(areaIndex);
|
const spring = getSpringPhysics(areaIndex, areas[areaIndex]);
|
||||||
if (velocityMag >= minVel) {
|
if (velocityMag >= minVel) {
|
||||||
spring.setTarget(clampedVelocity, velocityMult);
|
spring.setTarget(clampedVelocity, velocityMult);
|
||||||
} else {
|
} else {
|
||||||
@ -696,7 +703,7 @@ var useMouseInteraction = (containerRef, config) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
interactingAreaIndices.forEach((areaIndex) => {
|
interactingAreaIndices.forEach((areaIndex) => {
|
||||||
const spring = getSpringPhysics(areaIndex);
|
const spring = getSpringPhysics(areaIndex, areas[areaIndex]);
|
||||||
spring.setInitialVelocity(clampedVelocity, velocityMult);
|
spring.setInitialVelocity(clampedVelocity, velocityMult);
|
||||||
});
|
});
|
||||||
setInteractingAreaIndices(/* @__PURE__ */ new Set());
|
setInteractingAreaIndices(/* @__PURE__ */ new Set());
|
||||||
@ -743,11 +750,15 @@ var useMouseInteraction = (containerRef, config) => {
|
|||||||
const mouseState = getState();
|
const mouseState = getState();
|
||||||
return mouseState.isDragging;
|
return mouseState.isDragging;
|
||||||
}, [getState]);
|
}, [getState]);
|
||||||
|
const getInteractingAreaIndices = (0, import_react3.useCallback)(() => {
|
||||||
|
return interactingAreaIndices;
|
||||||
|
}, [interactingAreaIndices]);
|
||||||
return {
|
return {
|
||||||
updateInteraction,
|
updateInteraction,
|
||||||
updateConfig,
|
updateConfig,
|
||||||
reset,
|
reset,
|
||||||
isDragging
|
isDragging,
|
||||||
|
getInteractingAreaIndices
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -791,8 +802,7 @@ var ImageDistortion = ({
|
|||||||
isPlaying = true,
|
isPlaying = true,
|
||||||
style,
|
style,
|
||||||
className,
|
className,
|
||||||
mouseInteraction,
|
mouseInteraction
|
||||||
selectedAreaId
|
|
||||||
}) => {
|
}) => {
|
||||||
const containerRef = (0, import_react4.useRef)(null);
|
const containerRef = (0, import_react4.useRef)(null);
|
||||||
const sceneRef = (0, import_react4.useRef)(null);
|
const sceneRef = (0, import_react4.useRef)(null);
|
||||||
@ -923,32 +933,26 @@ var ImageDistortion = ({
|
|||||||
const animationCallback = (0, import_react4.useCallback)((deltaTime) => {
|
const animationCallback = (0, import_react4.useCallback)((deltaTime) => {
|
||||||
if (!isReady) return;
|
if (!isReady) return;
|
||||||
setCurrentAreas((prevAreas) => {
|
setCurrentAreas((prevAreas) => {
|
||||||
const isDragging = mouseInteractionHook.isDragging?.();
|
const interactingIndices = mouseInteractionHook.getInteractingAreaIndices?.() || /* @__PURE__ */ new Set();
|
||||||
let updatedAreas = prevAreas;
|
let updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);
|
||||||
if (!isDragging) {
|
updatedAreas = AnimationLoop.updateAreaDragVectors(updatedAreas);
|
||||||
updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);
|
if (interactingIndices.size > 0) {
|
||||||
updatedAreas = updatedAreas.map((area) => {
|
updatedAreas = updatedAreas.map((area, index) => {
|
||||||
if (selectedAreaId && area.id === selectedAreaId) {
|
if (interactingIndices.has(index)) {
|
||||||
return {
|
return {
|
||||||
...area,
|
...area,
|
||||||
dragVector: { x: 0, y: 0 }
|
dragVector: { x: 0, y: 0 }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const updatedArea = AnimationLoop.updateAreaDragVectors([area]);
|
return area;
|
||||||
return updatedArea[0];
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
updatedAreas = prevAreas.map((area) => ({
|
|
||||||
...area,
|
|
||||||
dragVector: { x: 0, y: 0 }
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
if (mouseInteraction?.enabled) {
|
if (mouseInteraction?.enabled) {
|
||||||
updatedAreas = mouseInteractionHook.updateInteraction(updatedAreas, deltaTime);
|
updatedAreas = mouseInteractionHook.updateInteraction(updatedAreas, deltaTime);
|
||||||
}
|
}
|
||||||
return updatedAreas;
|
return updatedAreas;
|
||||||
});
|
});
|
||||||
}, [isReady, mouseInteraction, mouseInteractionHook, selectedAreaId]);
|
}, [isReady, mouseInteraction, mouseInteractionHook]);
|
||||||
useAnimationFrame(animationCallback, isPlaying || mouseInteraction?.enabled || false);
|
useAnimationFrame(animationCallback, isPlaying || mouseInteraction?.enabled || false);
|
||||||
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
||||||
"div",
|
"div",
|
||||||
|
|||||||
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
50
dist/index.mjs
vendored
50
dist/index.mjs
vendored
@ -582,14 +582,21 @@ var useMouseInteraction = (containerRef, config) => {
|
|||||||
const { getState } = useMouseVelocity(containerRef);
|
const { getState } = useMouseVelocity(containerRef);
|
||||||
const [interactingAreaIndices, setInteractingAreaIndices] = useState(/* @__PURE__ */ new Set());
|
const [interactingAreaIndices, setInteractingAreaIndices] = useState(/* @__PURE__ */ new Set());
|
||||||
const springPhysicsMapRef = useRef3(/* @__PURE__ */ new Map());
|
const springPhysicsMapRef = useRef3(/* @__PURE__ */ new Map());
|
||||||
const getSpringPhysics = useCallback2((areaIndex) => {
|
const getSpringPhysics = useCallback2((areaIndex, area) => {
|
||||||
if (!springPhysicsMapRef.current.has(areaIndex)) {
|
if (!springPhysicsMapRef.current.has(areaIndex)) {
|
||||||
springPhysicsMapRef.current.set(areaIndex, new SpringPhysics(config.physics));
|
const physicsConfig = area?.physics || config.physics;
|
||||||
|
springPhysicsMapRef.current.set(areaIndex, new SpringPhysics(physicsConfig));
|
||||||
}
|
}
|
||||||
return springPhysicsMapRef.current.get(areaIndex);
|
return springPhysicsMapRef.current.get(areaIndex);
|
||||||
}, [config.physics]);
|
}, [config.physics]);
|
||||||
const updateInteraction = useCallback2((areas, deltaTime) => {
|
const updateInteraction = useCallback2((areas, deltaTime) => {
|
||||||
if (!config.enabled) return areas;
|
if (!config.enabled) return areas;
|
||||||
|
areas.forEach((area, index) => {
|
||||||
|
if (area.physics && springPhysicsMapRef.current.has(index)) {
|
||||||
|
const spring = springPhysicsMapRef.current.get(index);
|
||||||
|
spring.setConfig(area.physics);
|
||||||
|
}
|
||||||
|
});
|
||||||
const mouseState = getState();
|
const mouseState = getState();
|
||||||
if (mouseState.isDragging && mouseState.position) {
|
if (mouseState.isDragging && mouseState.position) {
|
||||||
const currentlyInAreas = /* @__PURE__ */ new Set();
|
const currentlyInAreas = /* @__PURE__ */ new Set();
|
||||||
@ -597,13 +604,13 @@ var useMouseInteraction = (containerRef, config) => {
|
|||||||
if (isPointInPolygon(mouseState.position, areas[i].basePoints)) {
|
if (isPointInPolygon(mouseState.position, areas[i].basePoints)) {
|
||||||
currentlyInAreas.add(i);
|
currentlyInAreas.add(i);
|
||||||
if (!interactingAreaIndices.has(i)) {
|
if (!interactingAreaIndices.has(i)) {
|
||||||
getSpringPhysics(i).reset();
|
getSpringPhysics(i, areas[i]).reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
interactingAreaIndices.forEach((areaIndex) => {
|
interactingAreaIndices.forEach((areaIndex) => {
|
||||||
if (!currentlyInAreas.has(areaIndex)) {
|
if (!currentlyInAreas.has(areaIndex)) {
|
||||||
getSpringPhysics(areaIndex).returnToEquilibrium();
|
getSpringPhysics(areaIndex, areas[areaIndex]).returnToEquilibrium();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setInteractingAreaIndices(currentlyInAreas);
|
setInteractingAreaIndices(currentlyInAreas);
|
||||||
@ -622,7 +629,7 @@ var useMouseInteraction = (containerRef, config) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
currentlyInAreas.forEach((areaIndex) => {
|
currentlyInAreas.forEach((areaIndex) => {
|
||||||
const spring = getSpringPhysics(areaIndex);
|
const spring = getSpringPhysics(areaIndex, areas[areaIndex]);
|
||||||
if (velocityMag >= minVel) {
|
if (velocityMag >= minVel) {
|
||||||
spring.setTarget(clampedVelocity, velocityMult);
|
spring.setTarget(clampedVelocity, velocityMult);
|
||||||
} else {
|
} else {
|
||||||
@ -645,7 +652,7 @@ var useMouseInteraction = (containerRef, config) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
interactingAreaIndices.forEach((areaIndex) => {
|
interactingAreaIndices.forEach((areaIndex) => {
|
||||||
const spring = getSpringPhysics(areaIndex);
|
const spring = getSpringPhysics(areaIndex, areas[areaIndex]);
|
||||||
spring.setInitialVelocity(clampedVelocity, velocityMult);
|
spring.setInitialVelocity(clampedVelocity, velocityMult);
|
||||||
});
|
});
|
||||||
setInteractingAreaIndices(/* @__PURE__ */ new Set());
|
setInteractingAreaIndices(/* @__PURE__ */ new Set());
|
||||||
@ -692,11 +699,15 @@ var useMouseInteraction = (containerRef, config) => {
|
|||||||
const mouseState = getState();
|
const mouseState = getState();
|
||||||
return mouseState.isDragging;
|
return mouseState.isDragging;
|
||||||
}, [getState]);
|
}, [getState]);
|
||||||
|
const getInteractingAreaIndices = useCallback2(() => {
|
||||||
|
return interactingAreaIndices;
|
||||||
|
}, [interactingAreaIndices]);
|
||||||
return {
|
return {
|
||||||
updateInteraction,
|
updateInteraction,
|
||||||
updateConfig,
|
updateConfig,
|
||||||
reset,
|
reset,
|
||||||
isDragging
|
isDragging,
|
||||||
|
getInteractingAreaIndices
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -740,8 +751,7 @@ var ImageDistortion = ({
|
|||||||
isPlaying = true,
|
isPlaying = true,
|
||||||
style,
|
style,
|
||||||
className,
|
className,
|
||||||
mouseInteraction,
|
mouseInteraction
|
||||||
selectedAreaId
|
|
||||||
}) => {
|
}) => {
|
||||||
const containerRef = useRef4(null);
|
const containerRef = useRef4(null);
|
||||||
const sceneRef = useRef4(null);
|
const sceneRef = useRef4(null);
|
||||||
@ -872,32 +882,26 @@ var ImageDistortion = ({
|
|||||||
const animationCallback = useCallback3((deltaTime) => {
|
const animationCallback = useCallback3((deltaTime) => {
|
||||||
if (!isReady) return;
|
if (!isReady) return;
|
||||||
setCurrentAreas((prevAreas) => {
|
setCurrentAreas((prevAreas) => {
|
||||||
const isDragging = mouseInteractionHook.isDragging?.();
|
const interactingIndices = mouseInteractionHook.getInteractingAreaIndices?.() || /* @__PURE__ */ new Set();
|
||||||
let updatedAreas = prevAreas;
|
let updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);
|
||||||
if (!isDragging) {
|
updatedAreas = AnimationLoop.updateAreaDragVectors(updatedAreas);
|
||||||
updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);
|
if (interactingIndices.size > 0) {
|
||||||
updatedAreas = updatedAreas.map((area) => {
|
updatedAreas = updatedAreas.map((area, index) => {
|
||||||
if (selectedAreaId && area.id === selectedAreaId) {
|
if (interactingIndices.has(index)) {
|
||||||
return {
|
return {
|
||||||
...area,
|
...area,
|
||||||
dragVector: { x: 0, y: 0 }
|
dragVector: { x: 0, y: 0 }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const updatedArea = AnimationLoop.updateAreaDragVectors([area]);
|
return area;
|
||||||
return updatedArea[0];
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
updatedAreas = prevAreas.map((area) => ({
|
|
||||||
...area,
|
|
||||||
dragVector: { x: 0, y: 0 }
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
if (mouseInteraction?.enabled) {
|
if (mouseInteraction?.enabled) {
|
||||||
updatedAreas = mouseInteractionHook.updateInteraction(updatedAreas, deltaTime);
|
updatedAreas = mouseInteractionHook.updateInteraction(updatedAreas, deltaTime);
|
||||||
}
|
}
|
||||||
return updatedAreas;
|
return updatedAreas;
|
||||||
});
|
});
|
||||||
}, [isReady, mouseInteraction, mouseInteractionHook, selectedAreaId]);
|
}, [isReady, mouseInteraction, mouseInteractionHook]);
|
||||||
useAnimationFrame(animationCallback, isPlaying || mouseInteraction?.enabled || false);
|
useAnimationFrame(animationCallback, isPlaying || mouseInteraction?.enabled || false);
|
||||||
return /* @__PURE__ */ jsx(
|
return /* @__PURE__ */ jsx(
|
||||||
"div",
|
"div",
|
||||||
|
|||||||
2
dist/index.mjs.map
vendored
2
dist/index.mjs.map
vendored
File diff suppressed because one or more lines are too long
@ -214,20 +214,24 @@ export const ImageDistortion: React.FC<ImageDistortionProps> = ({
|
|||||||
if (!isReady) return;
|
if (!isReady) return;
|
||||||
|
|
||||||
setCurrentAreas((prevAreas) => {
|
setCurrentAreas((prevAreas) => {
|
||||||
// 마우스가 드래그 중인지 확인
|
// 현재 인터랙션 중인 영역 인덱스 가져오기
|
||||||
const isDragging = mouseInteractionHook.isDragging?.();
|
const interactingIndices = mouseInteractionHook.getInteractingAreaIndices?.() || new Set<number>();
|
||||||
|
|
||||||
// 1. 자동 애니메이션 업데이트 (마우스 드래그 중이 아닐 때만)
|
// 1. 자동 애니메이션 업데이트
|
||||||
let updatedAreas = prevAreas;
|
let updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);
|
||||||
if (!isDragging) {
|
updatedAreas = AnimationLoop.updateAreaDragVectors(updatedAreas);
|
||||||
updatedAreas = AnimationLoop.updateProgress(prevAreas, deltaTime);
|
|
||||||
updatedAreas = AnimationLoop.updateAreaDragVectors(updatedAreas);
|
// 인터랙션 중인 영역만 dragVector를 0으로 설정
|
||||||
} else {
|
if (interactingIndices.size > 0) {
|
||||||
// 드래그 중일 때는 자동 애니메이션 dragVector를 0으로 설정
|
updatedAreas = updatedAreas.map((area, index) => {
|
||||||
updatedAreas = prevAreas.map(area => ({
|
if (interactingIndices.has(index)) {
|
||||||
...area,
|
return {
|
||||||
dragVector: { x: 0, y: 0 }
|
...area,
|
||||||
}));
|
dragVector: { x: 0, y: 0 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return area;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 마우스 인터랙션 적용 (기존 dragVector에 스프링 변위 추가)
|
// 2. 마우스 인터랙션 적용 (기존 dragVector에 스프링 변위 추가)
|
||||||
|
|||||||
@ -34,9 +34,11 @@ export const useMouseInteraction = (
|
|||||||
/**
|
/**
|
||||||
* 특정 영역의 스프링 물리 엔진 가져오기 (없으면 생성)
|
* 특정 영역의 스프링 물리 엔진 가져오기 (없으면 생성)
|
||||||
*/
|
*/
|
||||||
const getSpringPhysics = useCallback((areaIndex: number): SpringPhysics => {
|
const getSpringPhysics = useCallback((areaIndex: number, area?: DistortionArea): SpringPhysics => {
|
||||||
if (!springPhysicsMapRef.current.has(areaIndex)) {
|
if (!springPhysicsMapRef.current.has(areaIndex)) {
|
||||||
springPhysicsMapRef.current.set(areaIndex, new SpringPhysics(config.physics));
|
// 영역별 물리 설정이 있으면 사용, 없으면 전역 설정 사용
|
||||||
|
const physicsConfig = area?.physics || config.physics;
|
||||||
|
springPhysicsMapRef.current.set(areaIndex, new SpringPhysics(physicsConfig));
|
||||||
}
|
}
|
||||||
return springPhysicsMapRef.current.get(areaIndex)!;
|
return springPhysicsMapRef.current.get(areaIndex)!;
|
||||||
}, [config.physics]);
|
}, [config.physics]);
|
||||||
@ -47,6 +49,14 @@ export const useMouseInteraction = (
|
|||||||
const updateInteraction = useCallback((areas: DistortionArea[], deltaTime: number): DistortionArea[] => {
|
const updateInteraction = useCallback((areas: DistortionArea[], deltaTime: number): DistortionArea[] => {
|
||||||
if (!config.enabled) return areas;
|
if (!config.enabled) return areas;
|
||||||
|
|
||||||
|
// 영역별 물리 설정이 변경되었을 수 있으므로 모든 스프링 업데이트
|
||||||
|
areas.forEach((area, index) => {
|
||||||
|
if (area.physics && springPhysicsMapRef.current.has(index)) {
|
||||||
|
const spring = springPhysicsMapRef.current.get(index)!;
|
||||||
|
spring.setConfig(area.physics);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const mouseState = getState();
|
const mouseState = getState();
|
||||||
|
|
||||||
// 마우스 클릭/드래그 중이고 위치가 있으면
|
// 마우스 클릭/드래그 중이고 위치가 있으면
|
||||||
@ -59,7 +69,7 @@ export const useMouseInteraction = (
|
|||||||
|
|
||||||
// 새로 진입한 영역이면 스프링 리셋
|
// 새로 진입한 영역이면 스프링 리셋
|
||||||
if (!interactingAreaIndices.has(i)) {
|
if (!interactingAreaIndices.has(i)) {
|
||||||
getSpringPhysics(i).reset();
|
getSpringPhysics(i, areas[i]).reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,7 +77,7 @@ export const useMouseInteraction = (
|
|||||||
// 이전에 인터랙션하던 영역에서 벗어났으면 평형으로 복귀
|
// 이전에 인터랙션하던 영역에서 벗어났으면 평형으로 복귀
|
||||||
interactingAreaIndices.forEach((areaIndex) => {
|
interactingAreaIndices.forEach((areaIndex) => {
|
||||||
if (!currentlyInAreas.has(areaIndex)) {
|
if (!currentlyInAreas.has(areaIndex)) {
|
||||||
getSpringPhysics(areaIndex).returnToEquilibrium();
|
getSpringPhysics(areaIndex, areas[areaIndex]).returnToEquilibrium();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -93,7 +103,7 @@ export const useMouseInteraction = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
currentlyInAreas.forEach((areaIndex) => {
|
currentlyInAreas.forEach((areaIndex) => {
|
||||||
const spring = getSpringPhysics(areaIndex);
|
const spring = getSpringPhysics(areaIndex, areas[areaIndex]);
|
||||||
|
|
||||||
if (velocityMag >= minVel) {
|
if (velocityMag >= minVel) {
|
||||||
// 드래그 중: 마우스 속도를 목표로 설정
|
// 드래그 중: 마우스 속도를 목표로 설정
|
||||||
@ -124,7 +134,7 @@ export const useMouseInteraction = (
|
|||||||
|
|
||||||
// 모든 인터랙션 영역에 초기 속도 설정
|
// 모든 인터랙션 영역에 초기 속도 설정
|
||||||
interactingAreaIndices.forEach((areaIndex) => {
|
interactingAreaIndices.forEach((areaIndex) => {
|
||||||
const spring = getSpringPhysics(areaIndex);
|
const spring = getSpringPhysics(areaIndex, areas[areaIndex]);
|
||||||
spring.setInitialVelocity(clampedVelocity, velocityMult);
|
spring.setInitialVelocity(clampedVelocity, velocityMult);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -202,10 +212,18 @@ export const useMouseInteraction = (
|
|||||||
return mouseState.isDragging;
|
return mouseState.isDragging;
|
||||||
}, [getState]);
|
}, [getState]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 인터랙션 중인 영역 인덱스 가져오기
|
||||||
|
*/
|
||||||
|
const getInteractingAreaIndices = useCallback((): Set<number> => {
|
||||||
|
return interactingAreaIndices;
|
||||||
|
}, [interactingAreaIndices]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
updateInteraction,
|
updateInteraction,
|
||||||
updateConfig,
|
updateConfig,
|
||||||
reset,
|
reset,
|
||||||
isDragging,
|
isDragging,
|
||||||
|
getInteractingAreaIndices,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -74,12 +74,21 @@ 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;
|
||||||
// clamp를 루프 내에서 제거: 모든 왜곡을 완전히 누적한 후 마지막에 한 번만 clamp
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 모든 왜곡을 누적한 후 최종적으로 한 번만 clamp
|
// 경계 근처에서 부드럽게 페이드 아웃
|
||||||
texCoord = clamp(texCoord, 0.0, 1.0);
|
// 텍스처 좌표가 0~1 범위를 벗어나면 알파값을 줄여서 자연스럽게 처리
|
||||||
gl_FragColor = texture2D(u_texture, texCoord);
|
vec2 edgeDist = min(texCoord, 1.0 - texCoord);
|
||||||
|
float edgeFade = smoothstep(0.0, 0.05, min(edgeDist.x, edgeDist.y));
|
||||||
|
|
||||||
|
// 범위를 벗어난 좌표는 fract로 래핑하여 반복 효과 (더 자연스러움)
|
||||||
|
vec2 wrappedCoord = fract(texCoord);
|
||||||
|
|
||||||
|
vec4 color = texture2D(u_texture, wrappedCoord);
|
||||||
|
// 경계에서 페이드 아웃 적용
|
||||||
|
color.a *= edgeFade;
|
||||||
|
|
||||||
|
gl_FragColor = color;
|
||||||
}
|
}
|
||||||
@ -64,6 +64,14 @@ export interface DistortionArea {
|
|||||||
progress: number;
|
progress: number;
|
||||||
/** 현재 드래그 벡터 (progress로부터 계산됨) */
|
/** 현재 드래그 벡터 (progress로부터 계산됨) */
|
||||||
dragVector: Point;
|
dragVector: Point;
|
||||||
|
/** 영역별 물리 설정 (선택사항, 마우스 인터랙션 시 사용) */
|
||||||
|
physics?: {
|
||||||
|
stiffness: number;
|
||||||
|
damping: number;
|
||||||
|
mass: number;
|
||||||
|
influenceRadius: number;
|
||||||
|
maxStrength: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user