Compare commits
3 Commits
5f6e780b40
...
6b6c8d8fd0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b6c8d8fd0 | ||
|
|
317c7c5c92 | ||
|
|
4db9839f28 |
77
dist/index.d.mts
vendored
77
dist/index.d.mts
vendored
@ -11,11 +11,22 @@ interface Point {
|
||||
/**
|
||||
* 애니메이션 이징 함수 타입
|
||||
*/
|
||||
type EasingFunction = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'easeInQuad' | 'easeOutQuad';
|
||||
type EasingFunction = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'easeInQuad' | 'easeOutQuad' | 'easeInCubic' | 'easeOutCubic';
|
||||
/**
|
||||
* 모션 프리셋 타입
|
||||
* 내장 모션 프리셋 타입
|
||||
*/
|
||||
type MotionPreset = 'none' | 'horizontal' | 'vertical' | 'rotate-cw' | 'rotate-ccw' | 'pulse' | 'diagonal-1' | 'diagonal-2';
|
||||
type BuiltInMotionPreset = 'none' | 'horizontal' | 'vertical' | 'rotate-cw' | 'rotate-ccw' | 'pulse' | 'diagonal-1' | 'diagonal-2';
|
||||
/**
|
||||
* 모션 프리셋 타입 (내장 + 커스텀)
|
||||
* 커스텀 프리셋은 registerMotionPreset()으로 등록 후 사용
|
||||
*/
|
||||
type MotionPreset = BuiltInMotionPreset | (string & {});
|
||||
/**
|
||||
* 모션 프리셋 정의
|
||||
* @param strength 모션 강도 (기본값: 0.1)
|
||||
* @returns x, y 벡터값
|
||||
*/
|
||||
type MotionPresetDefinition = (strength: number) => Point;
|
||||
/**
|
||||
* 왜곡 애니메이션 움직임 설정
|
||||
*/
|
||||
@ -321,6 +332,8 @@ interface EditorCanvasProps {
|
||||
style?: EditorCanvasStyle;
|
||||
/** 에디터 UI 표시 여부 (기본값: true) */
|
||||
showEditor?: boolean;
|
||||
/** 영역 선택 콜백 (비선택 영역 클릭 시) */
|
||||
onSelectArea?: (areaId: string) => void;
|
||||
}
|
||||
declare const EditorCanvas: React$1.FC<EditorCanvasProps>;
|
||||
|
||||
@ -409,6 +422,62 @@ declare const DEFAULT_AREA: {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 커스텀 모션 프리셋 등록
|
||||
* @param name 프리셋 이름
|
||||
* @param definition 프리셋 정의 함수 (strength를 받아 Point 반환)
|
||||
* @param options 추가 옵션
|
||||
* @param options.isRotation 회전 애니메이션 여부 (true면 원운동)
|
||||
*
|
||||
* @example
|
||||
* // 좌우 진짜 왕복 (좌↔우)
|
||||
* registerMotionPreset('horizontal-full', (strength) => ({
|
||||
* x: strength * 2, // 진폭 2배
|
||||
* y: 0
|
||||
* }));
|
||||
*
|
||||
* // 8자 모양 운동 (회전)
|
||||
* registerMotionPreset('figure-8', (strength) => ({
|
||||
* x: strength,
|
||||
* y: strength * 0.5
|
||||
* }), { isRotation: true });
|
||||
*/
|
||||
declare function registerMotionPreset(name: string, definition: MotionPresetDefinition, options?: {
|
||||
isRotation?: boolean;
|
||||
}): void;
|
||||
/**
|
||||
* 여러 프리셋을 한번에 등록
|
||||
* @param presets 프리셋 맵 (이름 → 정의)
|
||||
* @param rotationPresetNames 회전 프리셋 이름 목록
|
||||
*
|
||||
* @example
|
||||
* registerMotionPresets({
|
||||
* 'horizontal-full': (s) => ({x: s * 2, y: 0}),
|
||||
* 'wave': (s) => ({x: s, y: s * 0.3}),
|
||||
* }, ['wave']); // wave는 회전 애니메이션
|
||||
*/
|
||||
declare function registerMotionPresets(presets: Record<string, MotionPresetDefinition>, rotationPresetNames?: string[]): void;
|
||||
/**
|
||||
* 프리셋 등록 해제
|
||||
* @param name 프리셋 이름
|
||||
* @returns 해제 성공 여부
|
||||
*/
|
||||
declare function unregisterMotionPreset(name: string): boolean;
|
||||
/**
|
||||
* 등록된 모든 프리셋 이름 조회
|
||||
* @returns 프리셋 이름 배열
|
||||
*/
|
||||
declare function getRegisteredPresets(): string[];
|
||||
/**
|
||||
* 프리셋 존재 여부 확인
|
||||
* @param name 프리셋 이름
|
||||
* @returns 존재 여부
|
||||
*/
|
||||
declare function hasPreset(name: string): boolean;
|
||||
/**
|
||||
* 내장 프리셋으로 초기화 (커스텀 프리셋 모두 제거)
|
||||
*/
|
||||
declare function resetToBuiltInPresets(): void;
|
||||
/**
|
||||
* 모션 프리셋을 벡터로 변환
|
||||
* @param preset 모션 프리셋
|
||||
@ -586,4 +655,4 @@ declare const useMouseInteraction: (containerRef: React.RefObject<HTMLElement |
|
||||
getInteractingAreaIndices: () => Set<number>;
|
||||
};
|
||||
|
||||
export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, AreaList, type AreaListProps, type AreaOutlineStyle, type CenterPointStyle, type CircleLevelStyle, DEFAULT_AREA, DEFAULT_EDITOR_CANVAS_STYLE, type DistortionArea, type DistortionMovement, type EasingFunction, type EditMode, EditorCanvas, type EditorCanvasProps, type EditorCanvasStyle, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MouseInteractionConfig, type MouseState, ParameterPanel, type ParameterPanelProps, type Point, type PointHandleStyle, 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, AreaList, type AreaListProps, type AreaOutlineStyle, type BuiltInMotionPreset, type CenterPointStyle, type CircleLevelStyle, DEFAULT_AREA, DEFAULT_EDITOR_CANVAS_STYLE, type DistortionArea, type DistortionMovement, type EasingFunction, type EditMode, EditorCanvas, type EditorCanvasProps, type EditorCanvasStyle, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MotionPresetDefinition, type MouseInteractionConfig, type MouseState, ParameterPanel, type ParameterPanelProps, type Point, type PointHandleStyle, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, getRegisteredPresets, hasPreset, isRotationPreset, presetToVector, registerMotionPreset, registerMotionPresets, resetToBuiltInPresets, unregisterMotionPreset, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity };
|
||||
|
||||
77
dist/index.d.ts
vendored
77
dist/index.d.ts
vendored
@ -11,11 +11,22 @@ interface Point {
|
||||
/**
|
||||
* 애니메이션 이징 함수 타입
|
||||
*/
|
||||
type EasingFunction = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'easeInQuad' | 'easeOutQuad';
|
||||
type EasingFunction = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'easeInQuad' | 'easeOutQuad' | 'easeInCubic' | 'easeOutCubic';
|
||||
/**
|
||||
* 모션 프리셋 타입
|
||||
* 내장 모션 프리셋 타입
|
||||
*/
|
||||
type MotionPreset = 'none' | 'horizontal' | 'vertical' | 'rotate-cw' | 'rotate-ccw' | 'pulse' | 'diagonal-1' | 'diagonal-2';
|
||||
type BuiltInMotionPreset = 'none' | 'horizontal' | 'vertical' | 'rotate-cw' | 'rotate-ccw' | 'pulse' | 'diagonal-1' | 'diagonal-2';
|
||||
/**
|
||||
* 모션 프리셋 타입 (내장 + 커스텀)
|
||||
* 커스텀 프리셋은 registerMotionPreset()으로 등록 후 사용
|
||||
*/
|
||||
type MotionPreset = BuiltInMotionPreset | (string & {});
|
||||
/**
|
||||
* 모션 프리셋 정의
|
||||
* @param strength 모션 강도 (기본값: 0.1)
|
||||
* @returns x, y 벡터값
|
||||
*/
|
||||
type MotionPresetDefinition = (strength: number) => Point;
|
||||
/**
|
||||
* 왜곡 애니메이션 움직임 설정
|
||||
*/
|
||||
@ -321,6 +332,8 @@ interface EditorCanvasProps {
|
||||
style?: EditorCanvasStyle;
|
||||
/** 에디터 UI 표시 여부 (기본값: true) */
|
||||
showEditor?: boolean;
|
||||
/** 영역 선택 콜백 (비선택 영역 클릭 시) */
|
||||
onSelectArea?: (areaId: string) => void;
|
||||
}
|
||||
declare const EditorCanvas: React$1.FC<EditorCanvasProps>;
|
||||
|
||||
@ -409,6 +422,62 @@ declare const DEFAULT_AREA: {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 커스텀 모션 프리셋 등록
|
||||
* @param name 프리셋 이름
|
||||
* @param definition 프리셋 정의 함수 (strength를 받아 Point 반환)
|
||||
* @param options 추가 옵션
|
||||
* @param options.isRotation 회전 애니메이션 여부 (true면 원운동)
|
||||
*
|
||||
* @example
|
||||
* // 좌우 진짜 왕복 (좌↔우)
|
||||
* registerMotionPreset('horizontal-full', (strength) => ({
|
||||
* x: strength * 2, // 진폭 2배
|
||||
* y: 0
|
||||
* }));
|
||||
*
|
||||
* // 8자 모양 운동 (회전)
|
||||
* registerMotionPreset('figure-8', (strength) => ({
|
||||
* x: strength,
|
||||
* y: strength * 0.5
|
||||
* }), { isRotation: true });
|
||||
*/
|
||||
declare function registerMotionPreset(name: string, definition: MotionPresetDefinition, options?: {
|
||||
isRotation?: boolean;
|
||||
}): void;
|
||||
/**
|
||||
* 여러 프리셋을 한번에 등록
|
||||
* @param presets 프리셋 맵 (이름 → 정의)
|
||||
* @param rotationPresetNames 회전 프리셋 이름 목록
|
||||
*
|
||||
* @example
|
||||
* registerMotionPresets({
|
||||
* 'horizontal-full': (s) => ({x: s * 2, y: 0}),
|
||||
* 'wave': (s) => ({x: s, y: s * 0.3}),
|
||||
* }, ['wave']); // wave는 회전 애니메이션
|
||||
*/
|
||||
declare function registerMotionPresets(presets: Record<string, MotionPresetDefinition>, rotationPresetNames?: string[]): void;
|
||||
/**
|
||||
* 프리셋 등록 해제
|
||||
* @param name 프리셋 이름
|
||||
* @returns 해제 성공 여부
|
||||
*/
|
||||
declare function unregisterMotionPreset(name: string): boolean;
|
||||
/**
|
||||
* 등록된 모든 프리셋 이름 조회
|
||||
* @returns 프리셋 이름 배열
|
||||
*/
|
||||
declare function getRegisteredPresets(): string[];
|
||||
/**
|
||||
* 프리셋 존재 여부 확인
|
||||
* @param name 프리셋 이름
|
||||
* @returns 존재 여부
|
||||
*/
|
||||
declare function hasPreset(name: string): boolean;
|
||||
/**
|
||||
* 내장 프리셋으로 초기화 (커스텀 프리셋 모두 제거)
|
||||
*/
|
||||
declare function resetToBuiltInPresets(): void;
|
||||
/**
|
||||
* 모션 프리셋을 벡터로 변환
|
||||
* @param preset 모션 프리셋
|
||||
@ -586,4 +655,4 @@ declare const useMouseInteraction: (containerRef: React.RefObject<HTMLElement |
|
||||
getInteractingAreaIndices: () => Set<number>;
|
||||
};
|
||||
|
||||
export { ANIMATION_CONFIG, AnimationLoop, type AnimationState, type AnimationTicker, type AreaBounds, AreaList, type AreaListProps, type AreaOutlineStyle, type CenterPointStyle, type CircleLevelStyle, DEFAULT_AREA, DEFAULT_EDITOR_CANVAS_STYLE, type DistortionArea, type DistortionMovement, type EasingFunction, type EditMode, EditorCanvas, type EditorCanvasProps, type EditorCanvasStyle, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MouseInteractionConfig, type MouseState, ParameterPanel, type ParameterPanelProps, type Point, type PointHandleStyle, 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, AreaList, type AreaListProps, type AreaOutlineStyle, type BuiltInMotionPreset, type CenterPointStyle, type CircleLevelStyle, DEFAULT_AREA, DEFAULT_EDITOR_CANVAS_STYLE, type DistortionArea, type DistortionMovement, type EasingFunction, type EditMode, EditorCanvas, type EditorCanvasProps, type EditorCanvasStyle, type EditorState, ImageDistortion, type ImageDistortionProps, type MotionPreset, type MotionPresetDefinition, type MouseInteractionConfig, type MouseState, ParameterPanel, type ParameterPanelProps, type Point, type PointHandleStyle, SHADER_CONFIG, type ShaderConfig, ShaderManager, type ShaderUniforms, SpringPhysics, type SpringPhysicsConfig, type SpringState, ThreeScene, applyEasing, getRegisteredPresets, hasPreset, isRotationPreset, presetToVector, registerMotionPreset, registerMotionPresets, resetToBuiltInPresets, unregisterMotionPreset, useAnimationFrame, useDistortionEditor, useMouseInteraction, useMouseVelocity };
|
||||
|
||||
128
dist/index.js
vendored
128
dist/index.js
vendored
@ -43,8 +43,14 @@ __export(index_exports, {
|
||||
SpringPhysics: () => SpringPhysics,
|
||||
ThreeScene: () => ThreeScene,
|
||||
applyEasing: () => applyEasing,
|
||||
getRegisteredPresets: () => getRegisteredPresets,
|
||||
hasPreset: () => hasPreset,
|
||||
isRotationPreset: () => isRotationPreset,
|
||||
presetToVector: () => presetToVector,
|
||||
registerMotionPreset: () => registerMotionPreset,
|
||||
registerMotionPresets: () => registerMotionPresets,
|
||||
resetToBuiltInPresets: () => resetToBuiltInPresets,
|
||||
unregisterMotionPreset: () => unregisterMotionPreset,
|
||||
useAnimationFrame: () => useAnimationFrame,
|
||||
useDistortionEditor: () => useDistortionEditor,
|
||||
useMouseInteraction: () => useMouseInteraction,
|
||||
@ -242,7 +248,9 @@ var easingFunctions = {
|
||||
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)
|
||||
easeOutQuad: (t) => t * (2 - t),
|
||||
easeInCubic: (t) => t * t * t,
|
||||
easeOutCubic: (t) => 1 - Math.pow(1 - t, 3)
|
||||
};
|
||||
var applyEasing = (progress, easingType) => {
|
||||
const clampedProgress = Math.max(0, Math.min(1, progress));
|
||||
@ -250,31 +258,65 @@ var applyEasing = (progress, easingType) => {
|
||||
};
|
||||
|
||||
// src/utils/motionPresets.ts
|
||||
function presetToVector(preset, strength = 0.1) {
|
||||
switch (preset) {
|
||||
case "none":
|
||||
return { x: 0, y: 0 };
|
||||
case "horizontal":
|
||||
return { x: strength, y: 0 };
|
||||
case "vertical":
|
||||
return { x: 0, y: strength };
|
||||
case "rotate-cw":
|
||||
return { x: strength, y: 0 };
|
||||
case "rotate-ccw":
|
||||
return { x: -strength, y: 0 };
|
||||
case "pulse":
|
||||
return { x: strength, y: strength };
|
||||
case "diagonal-1":
|
||||
return { x: strength * 0.707, y: strength * 0.707 };
|
||||
// √2/2 ≈ 0.707
|
||||
case "diagonal-2":
|
||||
return { x: strength * 0.707, y: -strength * 0.707 };
|
||||
default:
|
||||
return { x: 0, y: 0 };
|
||||
var presetRegistry = /* @__PURE__ */ new Map();
|
||||
var rotationPresets = /* @__PURE__ */ new Set(["rotate-cw", "rotate-ccw"]);
|
||||
var BUILT_IN_PRESETS = {
|
||||
"none": () => ({ x: 0, y: 0 }),
|
||||
"horizontal": (strength) => ({ x: strength, y: 0 }),
|
||||
"vertical": (strength) => ({ x: 0, y: strength }),
|
||||
"rotate-cw": (strength) => ({ x: strength, y: 0 }),
|
||||
"rotate-ccw": (strength) => ({ x: -strength, y: 0 }),
|
||||
"pulse": (strength) => ({ x: strength, y: strength }),
|
||||
"diagonal-1": (strength) => ({ x: strength * 0.707, y: strength * 0.707 }),
|
||||
"diagonal-2": (strength) => ({ x: strength * 0.707, y: -strength * 0.707 })
|
||||
};
|
||||
Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => {
|
||||
presetRegistry.set(name, definition);
|
||||
});
|
||||
function registerMotionPreset(name, definition, options) {
|
||||
presetRegistry.set(name, definition);
|
||||
if (options?.isRotation) {
|
||||
rotationPresets.add(name);
|
||||
} else {
|
||||
rotationPresets.delete(name);
|
||||
}
|
||||
}
|
||||
function registerMotionPresets(presets, rotationPresetNames) {
|
||||
Object.entries(presets).forEach(([name, definition]) => {
|
||||
presetRegistry.set(name, definition);
|
||||
});
|
||||
rotationPresetNames?.forEach((name) => rotationPresets.add(name));
|
||||
}
|
||||
function unregisterMotionPreset(name) {
|
||||
rotationPresets.delete(name);
|
||||
return presetRegistry.delete(name);
|
||||
}
|
||||
function getRegisteredPresets() {
|
||||
return Array.from(presetRegistry.keys());
|
||||
}
|
||||
function hasPreset(name) {
|
||||
return presetRegistry.has(name);
|
||||
}
|
||||
function resetToBuiltInPresets() {
|
||||
presetRegistry.clear();
|
||||
rotationPresets.clear();
|
||||
Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => {
|
||||
presetRegistry.set(name, definition);
|
||||
});
|
||||
rotationPresets.add("rotate-cw");
|
||||
rotationPresets.add("rotate-ccw");
|
||||
}
|
||||
function presetToVector(preset, strength = 0.1) {
|
||||
const definition = presetRegistry.get(preset);
|
||||
if (definition) {
|
||||
return definition(strength);
|
||||
}
|
||||
console.warn(`Unknown motion preset: "${preset}". Falling back to "none".`);
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
function isRotationPreset(preset) {
|
||||
return preset === "rotate-cw" || preset === "rotate-ccw";
|
||||
if (!preset) return false;
|
||||
return rotationPresets.has(preset);
|
||||
}
|
||||
|
||||
// src/engine/AnimationLoop.ts
|
||||
@ -311,19 +353,11 @@ var AnimationLoop = class {
|
||||
y: Math.sin(angle * direction) * radius
|
||||
};
|
||||
} else {
|
||||
if (easedProgress < 0.5) {
|
||||
const t = easedProgress * 2;
|
||||
const oscillation = Math.sin(easedProgress * Math.PI * 2);
|
||||
dragVector = {
|
||||
x: baseVector.x * t,
|
||||
y: baseVector.y * t
|
||||
x: baseVector.x * oscillation,
|
||||
y: baseVector.y * oscillation
|
||||
};
|
||||
} else {
|
||||
const t = (easedProgress - 0.5) * 2;
|
||||
dragVector = {
|
||||
x: baseVector.x * (1 - t),
|
||||
y: baseVector.y * (1 - t)
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
...area,
|
||||
@ -1282,7 +1316,8 @@ var EditorCanvas = ({
|
||||
onStartDragging,
|
||||
onStopDragging,
|
||||
style: customStyle,
|
||||
showEditor = true
|
||||
showEditor = true,
|
||||
onSelectArea
|
||||
}) => {
|
||||
const containerRef = (0, import_react6.useRef)(null);
|
||||
const [canvasSize, setCanvasSize] = (0, import_react6.useState)({ width: 0, height: 0 });
|
||||
@ -1331,7 +1366,7 @@ var EditorCanvas = ({
|
||||
);
|
||||
const handleCanvasDown = (0, import_react6.useCallback)(
|
||||
(e) => {
|
||||
if (!showEditor || !selectedArea || !containerRef.current) return;
|
||||
if (!showEditor || !containerRef.current) return;
|
||||
const rect = containerRef.current.getBoundingClientRect();
|
||||
let clientX, clientY;
|
||||
if ("touches" in e) {
|
||||
@ -1345,13 +1380,24 @@ var EditorCanvas = ({
|
||||
const x = (clientX - rect.left) / rect.width;
|
||||
const y = (clientY - rect.top) / rect.height;
|
||||
const clickPoint = { x, y };
|
||||
if (isPointInPolygon2(clickPoint, selectedArea.basePoints)) {
|
||||
if (selectedArea && isPointInPolygon2(clickPoint, selectedArea.basePoints)) {
|
||||
setIsDraggingArea(true);
|
||||
setDragStartPos(clickPoint);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (onSelectArea) {
|
||||
for (let i = areas.length - 1; i >= 0; i--) {
|
||||
const area = areas[i];
|
||||
if (area.id !== selectedAreaId && isPointInPolygon2(clickPoint, area.basePoints)) {
|
||||
onSelectArea(area.id);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[showEditor, selectedArea, isPointInPolygon2]
|
||||
[showEditor, selectedArea, selectedAreaId, areas, isPointInPolygon2, onSelectArea]
|
||||
);
|
||||
const handleMove = (0, import_react6.useCallback)(
|
||||
(e) => {
|
||||
@ -1616,8 +1662,14 @@ var EditorCanvas = ({
|
||||
SpringPhysics,
|
||||
ThreeScene,
|
||||
applyEasing,
|
||||
getRegisteredPresets,
|
||||
hasPreset,
|
||||
isRotationPreset,
|
||||
presetToVector,
|
||||
registerMotionPreset,
|
||||
registerMotionPresets,
|
||||
resetToBuiltInPresets,
|
||||
unregisterMotionPreset,
|
||||
useAnimationFrame,
|
||||
useDistortionEditor,
|
||||
useMouseInteraction,
|
||||
|
||||
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
122
dist/index.mjs
vendored
122
dist/index.mjs
vendored
@ -188,7 +188,9 @@ var easingFunctions = {
|
||||
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)
|
||||
easeOutQuad: (t) => t * (2 - t),
|
||||
easeInCubic: (t) => t * t * t,
|
||||
easeOutCubic: (t) => 1 - Math.pow(1 - t, 3)
|
||||
};
|
||||
var applyEasing = (progress, easingType) => {
|
||||
const clampedProgress = Math.max(0, Math.min(1, progress));
|
||||
@ -196,31 +198,65 @@ var applyEasing = (progress, easingType) => {
|
||||
};
|
||||
|
||||
// src/utils/motionPresets.ts
|
||||
function presetToVector(preset, strength = 0.1) {
|
||||
switch (preset) {
|
||||
case "none":
|
||||
return { x: 0, y: 0 };
|
||||
case "horizontal":
|
||||
return { x: strength, y: 0 };
|
||||
case "vertical":
|
||||
return { x: 0, y: strength };
|
||||
case "rotate-cw":
|
||||
return { x: strength, y: 0 };
|
||||
case "rotate-ccw":
|
||||
return { x: -strength, y: 0 };
|
||||
case "pulse":
|
||||
return { x: strength, y: strength };
|
||||
case "diagonal-1":
|
||||
return { x: strength * 0.707, y: strength * 0.707 };
|
||||
// √2/2 ≈ 0.707
|
||||
case "diagonal-2":
|
||||
return { x: strength * 0.707, y: -strength * 0.707 };
|
||||
default:
|
||||
return { x: 0, y: 0 };
|
||||
var presetRegistry = /* @__PURE__ */ new Map();
|
||||
var rotationPresets = /* @__PURE__ */ new Set(["rotate-cw", "rotate-ccw"]);
|
||||
var BUILT_IN_PRESETS = {
|
||||
"none": () => ({ x: 0, y: 0 }),
|
||||
"horizontal": (strength) => ({ x: strength, y: 0 }),
|
||||
"vertical": (strength) => ({ x: 0, y: strength }),
|
||||
"rotate-cw": (strength) => ({ x: strength, y: 0 }),
|
||||
"rotate-ccw": (strength) => ({ x: -strength, y: 0 }),
|
||||
"pulse": (strength) => ({ x: strength, y: strength }),
|
||||
"diagonal-1": (strength) => ({ x: strength * 0.707, y: strength * 0.707 }),
|
||||
"diagonal-2": (strength) => ({ x: strength * 0.707, y: -strength * 0.707 })
|
||||
};
|
||||
Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => {
|
||||
presetRegistry.set(name, definition);
|
||||
});
|
||||
function registerMotionPreset(name, definition, options) {
|
||||
presetRegistry.set(name, definition);
|
||||
if (options?.isRotation) {
|
||||
rotationPresets.add(name);
|
||||
} else {
|
||||
rotationPresets.delete(name);
|
||||
}
|
||||
}
|
||||
function registerMotionPresets(presets, rotationPresetNames) {
|
||||
Object.entries(presets).forEach(([name, definition]) => {
|
||||
presetRegistry.set(name, definition);
|
||||
});
|
||||
rotationPresetNames?.forEach((name) => rotationPresets.add(name));
|
||||
}
|
||||
function unregisterMotionPreset(name) {
|
||||
rotationPresets.delete(name);
|
||||
return presetRegistry.delete(name);
|
||||
}
|
||||
function getRegisteredPresets() {
|
||||
return Array.from(presetRegistry.keys());
|
||||
}
|
||||
function hasPreset(name) {
|
||||
return presetRegistry.has(name);
|
||||
}
|
||||
function resetToBuiltInPresets() {
|
||||
presetRegistry.clear();
|
||||
rotationPresets.clear();
|
||||
Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => {
|
||||
presetRegistry.set(name, definition);
|
||||
});
|
||||
rotationPresets.add("rotate-cw");
|
||||
rotationPresets.add("rotate-ccw");
|
||||
}
|
||||
function presetToVector(preset, strength = 0.1) {
|
||||
const definition = presetRegistry.get(preset);
|
||||
if (definition) {
|
||||
return definition(strength);
|
||||
}
|
||||
console.warn(`Unknown motion preset: "${preset}". Falling back to "none".`);
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
function isRotationPreset(preset) {
|
||||
return preset === "rotate-cw" || preset === "rotate-ccw";
|
||||
if (!preset) return false;
|
||||
return rotationPresets.has(preset);
|
||||
}
|
||||
|
||||
// src/engine/AnimationLoop.ts
|
||||
@ -257,19 +293,11 @@ var AnimationLoop = class {
|
||||
y: Math.sin(angle * direction) * radius
|
||||
};
|
||||
} else {
|
||||
if (easedProgress < 0.5) {
|
||||
const t = easedProgress * 2;
|
||||
const oscillation = Math.sin(easedProgress * Math.PI * 2);
|
||||
dragVector = {
|
||||
x: baseVector.x * t,
|
||||
y: baseVector.y * t
|
||||
x: baseVector.x * oscillation,
|
||||
y: baseVector.y * oscillation
|
||||
};
|
||||
} else {
|
||||
const t = (easedProgress - 0.5) * 2;
|
||||
dragVector = {
|
||||
x: baseVector.x * (1 - t),
|
||||
y: baseVector.y * (1 - t)
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
...area,
|
||||
@ -1228,7 +1256,8 @@ var EditorCanvas = ({
|
||||
onStartDragging,
|
||||
onStopDragging,
|
||||
style: customStyle,
|
||||
showEditor = true
|
||||
showEditor = true,
|
||||
onSelectArea
|
||||
}) => {
|
||||
const containerRef = useRef5(null);
|
||||
const [canvasSize, setCanvasSize] = useState4({ width: 0, height: 0 });
|
||||
@ -1277,7 +1306,7 @@ var EditorCanvas = ({
|
||||
);
|
||||
const handleCanvasDown = useCallback5(
|
||||
(e) => {
|
||||
if (!showEditor || !selectedArea || !containerRef.current) return;
|
||||
if (!showEditor || !containerRef.current) return;
|
||||
const rect = containerRef.current.getBoundingClientRect();
|
||||
let clientX, clientY;
|
||||
if ("touches" in e) {
|
||||
@ -1291,13 +1320,24 @@ var EditorCanvas = ({
|
||||
const x = (clientX - rect.left) / rect.width;
|
||||
const y = (clientY - rect.top) / rect.height;
|
||||
const clickPoint = { x, y };
|
||||
if (isPointInPolygon2(clickPoint, selectedArea.basePoints)) {
|
||||
if (selectedArea && isPointInPolygon2(clickPoint, selectedArea.basePoints)) {
|
||||
setIsDraggingArea(true);
|
||||
setDragStartPos(clickPoint);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (onSelectArea) {
|
||||
for (let i = areas.length - 1; i >= 0; i--) {
|
||||
const area = areas[i];
|
||||
if (area.id !== selectedAreaId && isPointInPolygon2(clickPoint, area.basePoints)) {
|
||||
onSelectArea(area.id);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[showEditor, selectedArea, isPointInPolygon2]
|
||||
[showEditor, selectedArea, selectedAreaId, areas, isPointInPolygon2, onSelectArea]
|
||||
);
|
||||
const handleMove = useCallback5(
|
||||
(e) => {
|
||||
@ -1561,8 +1601,14 @@ export {
|
||||
SpringPhysics,
|
||||
ThreeScene,
|
||||
applyEasing,
|
||||
getRegisteredPresets,
|
||||
hasPreset,
|
||||
isRotationPreset,
|
||||
presetToVector,
|
||||
registerMotionPreset,
|
||||
registerMotionPresets,
|
||||
resetToBuiltInPresets,
|
||||
unregisterMotionPreset,
|
||||
useAnimationFrame,
|
||||
useDistortionEditor,
|
||||
useMouseInteraction,
|
||||
|
||||
2
dist/index.mjs.map
vendored
2
dist/index.mjs.map
vendored
File diff suppressed because one or more lines are too long
14
package-lock.json
generated
14
package-lock.json
generated
@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "responsive-image-canvas",
|
||||
"version": "1.0.0",
|
||||
"name": "@baekryang/responsive-image-canvas",
|
||||
"version": "1.0.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "responsive-image-canvas",
|
||||
"version": "1.0.0",
|
||||
"name": "@baekryang/responsive-image-canvas",
|
||||
"version": "1.0.5",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
@ -16,6 +17,11 @@
|
||||
"three": "^0.181.0",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "^5.5.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0",
|
||||
"three": ">=0.150.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dimforge/rapier3d-compat": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@baekryang/responsive-image-canvas",
|
||||
"version": "1.0.5",
|
||||
"version": "1.2.1",
|
||||
"publishConfig": {
|
||||
"registry": "https://git.bnovalab.com/api/packages/baekryang/npm/"
|
||||
},
|
||||
|
||||
@ -19,6 +19,8 @@ export interface EditorCanvasProps {
|
||||
style?: EditorCanvasStyle;
|
||||
/** 에디터 UI 표시 여부 (기본값: true) */
|
||||
showEditor?: boolean;
|
||||
/** 영역 선택 콜백 (비선택 영역 클릭 시) */
|
||||
onSelectArea?: (areaId: string) => void;
|
||||
}
|
||||
|
||||
export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
||||
@ -34,6 +36,7 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
||||
onStopDragging,
|
||||
style: customStyle,
|
||||
showEditor = true,
|
||||
onSelectArea,
|
||||
}) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [canvasSize, setCanvasSize] = useState({width: 0, height: 0});
|
||||
@ -97,7 +100,7 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
||||
const handleCanvasDown = useCallback(
|
||||
(e: React.MouseEvent | React.TouchEvent) => {
|
||||
// 에디터가 숨겨진 상태면 동작하지 않음
|
||||
if (!showEditor || !selectedArea || !containerRef.current) return;
|
||||
if (!showEditor || !containerRef.current) return;
|
||||
|
||||
const rect = containerRef.current.getBoundingClientRect();
|
||||
|
||||
@ -116,14 +119,28 @@ export const EditorCanvas: React.FC<EditorCanvasProps> = ({
|
||||
const y = (clientY - rect.top) / rect.height;
|
||||
const clickPoint = { x, y };
|
||||
|
||||
// 사각형 내부를 클릭했는지 확인
|
||||
if (isPointInPolygon(clickPoint, selectedArea.basePoints)) {
|
||||
// 선택된 영역 내부를 클릭했는지 확인 (드래그 시작)
|
||||
if (selectedArea && isPointInPolygon(clickPoint, selectedArea.basePoints)) {
|
||||
setIsDraggingArea(true);
|
||||
setDragStartPos(clickPoint);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// 비선택 영역 클릭 시 해당 영역 선택
|
||||
if (onSelectArea) {
|
||||
// 역순으로 검사 (위에 그려진 영역 우선)
|
||||
for (let i = areas.length - 1; i >= 0; i--) {
|
||||
const area = areas[i];
|
||||
if (area.id !== selectedAreaId && isPointInPolygon(clickPoint, area.basePoints)) {
|
||||
onSelectArea(area.id);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[showEditor, selectedArea, isPointInPolygon]
|
||||
[showEditor, selectedArea, selectedAreaId, areas, isPointInPolygon, onSelectArea]
|
||||
);
|
||||
|
||||
// 이동 (마우스/터치 공통)
|
||||
|
||||
@ -51,22 +51,13 @@ export class AnimationLoop {
|
||||
y: Math.sin(angle * direction) * radius,
|
||||
};
|
||||
} else {
|
||||
// 일반 왕복 모션
|
||||
if (easedProgress < 0.5) {
|
||||
// 0.0 -> 0.5: 0에서 baseVector로 보간
|
||||
const t = easedProgress * 2;
|
||||
// 일반 왕복 모션 (sin 기반으로 진짜 좌↔우/상↔하 왕복)
|
||||
// sin(0)=0 → sin(π/2)=1 → sin(π)=0 → sin(3π/2)=-1 → sin(2π)=0
|
||||
const oscillation = Math.sin(easedProgress * Math.PI * 2);
|
||||
dragVector = {
|
||||
x: baseVector.x * t,
|
||||
y: baseVector.y * t,
|
||||
x: baseVector.x * oscillation,
|
||||
y: baseVector.y * oscillation,
|
||||
};
|
||||
} else {
|
||||
// 0.5 -> 1.0: baseVector에서 0으로 보간
|
||||
const t = (easedProgress - 0.5) * 2;
|
||||
dragVector = {
|
||||
x: baseVector.x * (1 - t),
|
||||
y: baseVector.y * (1 - t),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
14
src/index.ts
14
src/index.ts
@ -31,7 +31,9 @@ export { DEFAULT_EDITOR_CANVAS_STYLE } from './editor/constants';
|
||||
export type {
|
||||
Point,
|
||||
EasingFunction,
|
||||
BuiltInMotionPreset,
|
||||
MotionPreset,
|
||||
MotionPresetDefinition,
|
||||
DistortionMovement,
|
||||
DistortionArea,
|
||||
AreaBounds,
|
||||
@ -52,7 +54,17 @@ export type {
|
||||
// 유틸리티 함수
|
||||
export { applyEasing } from './utils/easing';
|
||||
export { SHADER_CONFIG, ANIMATION_CONFIG, DEFAULT_AREA } from './utils/constants';
|
||||
export { presetToVector, isRotationPreset } from './utils/motionPresets';
|
||||
export {
|
||||
presetToVector,
|
||||
isRotationPreset,
|
||||
// 프리셋 레지스트리 API
|
||||
registerMotionPreset,
|
||||
registerMotionPresets,
|
||||
unregisterMotionPreset,
|
||||
getRegisteredPresets,
|
||||
hasPreset,
|
||||
resetToBuiltInPresets,
|
||||
} from './utils/motionPresets';
|
||||
|
||||
// 엔진 클래스 (고급 사용자용)
|
||||
export { ThreeScene } from './engine/ThreeScene';
|
||||
|
||||
@ -15,12 +15,14 @@ export type EasingFunction =
|
||||
| 'easeOut'
|
||||
| 'easeInOut'
|
||||
| 'easeInQuad'
|
||||
| 'easeOutQuad';
|
||||
| 'easeOutQuad'
|
||||
| 'easeInCubic'
|
||||
| 'easeOutCubic';
|
||||
|
||||
/**
|
||||
* 모션 프리셋 타입
|
||||
* 내장 모션 프리셋 타입
|
||||
*/
|
||||
export type MotionPreset =
|
||||
export type BuiltInMotionPreset =
|
||||
| 'none' // 없음 (애니메이션 없음)
|
||||
| 'horizontal' // 좌우 왕복
|
||||
| 'vertical' // 상하 왕복
|
||||
@ -30,6 +32,24 @@ export type MotionPreset =
|
||||
| 'diagonal-1' // 대각선 (좌상→우하)
|
||||
| 'diagonal-2'; // 대각선 (우상→좌하)
|
||||
|
||||
/**
|
||||
* 모션 프리셋 타입 (내장 + 커스텀)
|
||||
* 커스텀 프리셋은 registerMotionPreset()으로 등록 후 사용
|
||||
*/
|
||||
export type MotionPreset = BuiltInMotionPreset | (string & {});
|
||||
|
||||
/**
|
||||
* 모션 프리셋 정의
|
||||
* @param strength 모션 강도 (기본값: 0.1)
|
||||
* @returns x, y 벡터값
|
||||
*/
|
||||
export type MotionPresetDefinition = (strength: number) => Point;
|
||||
|
||||
/**
|
||||
* 회전 프리셋 판별 함수
|
||||
*/
|
||||
export type RotationPresetChecker = (preset: MotionPreset) => boolean;
|
||||
|
||||
/**
|
||||
* 왜곡 애니메이션 움직임 설정
|
||||
*/
|
||||
|
||||
@ -14,6 +14,9 @@ const easingFunctions: Record<EasingFunction, EasingFunc> = {
|
||||
|
||||
easeInQuad: (t) => t * t,
|
||||
easeOutQuad: (t) => t * (2 - t),
|
||||
|
||||
easeInCubic: (t) => t * t * t,
|
||||
easeOutCubic: (t) => 1 - Math.pow(1 - t, 3),
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -1,4 +1,130 @@
|
||||
import type {MotionPreset, Point} from '../types';
|
||||
import type {MotionPreset, MotionPresetDefinition, Point, RotationPresetChecker} from '../types';
|
||||
|
||||
/**
|
||||
* 프리셋 레지스트리 (내장 + 커스텀)
|
||||
*/
|
||||
const presetRegistry = new Map<string, MotionPresetDefinition>();
|
||||
|
||||
/**
|
||||
* 회전 프리셋 목록
|
||||
*/
|
||||
const rotationPresets = new Set<string>(['rotate-cw', 'rotate-ccw']);
|
||||
|
||||
/**
|
||||
* 내장 프리셋 정의
|
||||
*/
|
||||
const BUILT_IN_PRESETS: Record<string, MotionPresetDefinition> = {
|
||||
'none': () => ({x: 0, y: 0}),
|
||||
'horizontal': (strength) => ({x: strength, y: 0}),
|
||||
'vertical': (strength) => ({x: 0, y: strength}),
|
||||
'rotate-cw': (strength) => ({x: strength, y: 0}),
|
||||
'rotate-ccw': (strength) => ({x: -strength, y: 0}),
|
||||
'pulse': (strength) => ({x: strength, y: strength}),
|
||||
'diagonal-1': (strength) => ({x: strength * 0.707, y: strength * 0.707}),
|
||||
'diagonal-2': (strength) => ({x: strength * 0.707, y: -strength * 0.707}),
|
||||
};
|
||||
|
||||
// 내장 프리셋 등록
|
||||
Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => {
|
||||
presetRegistry.set(name, definition);
|
||||
});
|
||||
|
||||
/**
|
||||
* 커스텀 모션 프리셋 등록
|
||||
* @param name 프리셋 이름
|
||||
* @param definition 프리셋 정의 함수 (strength를 받아 Point 반환)
|
||||
* @param options 추가 옵션
|
||||
* @param options.isRotation 회전 애니메이션 여부 (true면 원운동)
|
||||
*
|
||||
* @example
|
||||
* // 좌우 진짜 왕복 (좌↔우)
|
||||
* registerMotionPreset('horizontal-full', (strength) => ({
|
||||
* x: strength * 2, // 진폭 2배
|
||||
* y: 0
|
||||
* }));
|
||||
*
|
||||
* // 8자 모양 운동 (회전)
|
||||
* registerMotionPreset('figure-8', (strength) => ({
|
||||
* x: strength,
|
||||
* y: strength * 0.5
|
||||
* }), { isRotation: true });
|
||||
*/
|
||||
export function registerMotionPreset(
|
||||
name: string,
|
||||
definition: MotionPresetDefinition,
|
||||
options?: { isRotation?: boolean }
|
||||
): void {
|
||||
presetRegistry.set(name, definition);
|
||||
|
||||
if (options?.isRotation) {
|
||||
rotationPresets.add(name);
|
||||
} else {
|
||||
rotationPresets.delete(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 여러 프리셋을 한번에 등록
|
||||
* @param presets 프리셋 맵 (이름 → 정의)
|
||||
* @param rotationPresetNames 회전 프리셋 이름 목록
|
||||
*
|
||||
* @example
|
||||
* registerMotionPresets({
|
||||
* 'horizontal-full': (s) => ({x: s * 2, y: 0}),
|
||||
* 'wave': (s) => ({x: s, y: s * 0.3}),
|
||||
* }, ['wave']); // wave는 회전 애니메이션
|
||||
*/
|
||||
export function registerMotionPresets(
|
||||
presets: Record<string, MotionPresetDefinition>,
|
||||
rotationPresetNames?: string[]
|
||||
): void {
|
||||
Object.entries(presets).forEach(([name, definition]) => {
|
||||
presetRegistry.set(name, definition);
|
||||
});
|
||||
|
||||
rotationPresetNames?.forEach(name => rotationPresets.add(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* 프리셋 등록 해제
|
||||
* @param name 프리셋 이름
|
||||
* @returns 해제 성공 여부
|
||||
*/
|
||||
export function unregisterMotionPreset(name: string): boolean {
|
||||
rotationPresets.delete(name);
|
||||
return presetRegistry.delete(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 등록된 모든 프리셋 이름 조회
|
||||
* @returns 프리셋 이름 배열
|
||||
*/
|
||||
export function getRegisteredPresets(): string[] {
|
||||
return Array.from(presetRegistry.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* 프리셋 존재 여부 확인
|
||||
* @param name 프리셋 이름
|
||||
* @returns 존재 여부
|
||||
*/
|
||||
export function hasPreset(name: string): boolean {
|
||||
return presetRegistry.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 내장 프리셋으로 초기화 (커스텀 프리셋 모두 제거)
|
||||
*/
|
||||
export function resetToBuiltInPresets(): void {
|
||||
presetRegistry.clear();
|
||||
rotationPresets.clear();
|
||||
|
||||
Object.entries(BUILT_IN_PRESETS).forEach(([name, definition]) => {
|
||||
presetRegistry.set(name, definition);
|
||||
});
|
||||
rotationPresets.add('rotate-cw');
|
||||
rotationPresets.add('rotate-ccw');
|
||||
}
|
||||
|
||||
/**
|
||||
* 모션 프리셋을 벡터로 변환
|
||||
@ -7,47 +133,31 @@ import type {MotionPreset, Point} from '../types';
|
||||
* @returns 계산된 벡터 (vectorA)
|
||||
*/
|
||||
export function presetToVector(preset: MotionPreset, strength: number = 0.1): Point {
|
||||
switch (preset) {
|
||||
case 'none':
|
||||
// 애니메이션 없음
|
||||
return {x: 0, y: 0};
|
||||
const definition = presetRegistry.get(preset);
|
||||
|
||||
case 'horizontal':
|
||||
// 좌우 왕복
|
||||
return {x: strength, y: 0};
|
||||
|
||||
case 'vertical':
|
||||
// 상하 왕복
|
||||
return {x: 0, y: strength};
|
||||
|
||||
case 'rotate-cw':
|
||||
// 시계방향 회전 (원운동의 시작점)
|
||||
return {x: strength, y: 0};
|
||||
|
||||
case 'rotate-ccw':
|
||||
// 반시계방향 회전 (원운동의 시작점)
|
||||
return {x: -strength, y: 0};
|
||||
|
||||
case 'pulse':
|
||||
// 펄스 (중심에서 바깥으로)
|
||||
return {x: strength, y: strength};
|
||||
|
||||
case 'diagonal-1':
|
||||
// 대각선 (좌상→우하)
|
||||
return {x: strength * 0.707, y: strength * 0.707}; // √2/2 ≈ 0.707
|
||||
|
||||
case 'diagonal-2':
|
||||
// 대각선 (우상→좌하)
|
||||
return {x: strength * 0.707, y: -strength * 0.707};
|
||||
|
||||
default:
|
||||
return {x: 0, y: 0};
|
||||
if (definition) {
|
||||
return definition(strength);
|
||||
}
|
||||
|
||||
// 등록되지 않은 프리셋은 none으로 처리
|
||||
console.warn(`Unknown motion preset: "${preset}". Falling back to "none".`);
|
||||
return {x: 0, y: 0};
|
||||
}
|
||||
|
||||
/**
|
||||
* 프리셋이 회전 타입인지 확인
|
||||
*/
|
||||
export function isRotationPreset(preset?: MotionPreset): boolean {
|
||||
return preset === 'rotate-cw' || preset === 'rotate-ccw';
|
||||
if (!preset) return false;
|
||||
return rotationPresets.has(preset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 커스텀 회전 프리셋 판별 함수 등록
|
||||
* @param checker 판별 함수
|
||||
* @deprecated isRotation 옵션을 registerMotionPreset에 전달하세요
|
||||
*/
|
||||
export function setRotationChecker(checker: RotationPresetChecker): void {
|
||||
// Legacy support - 기존 코드 호환성을 위해 유지
|
||||
console.warn('setRotationChecker is deprecated. Use registerMotionPreset with { isRotation: true } option instead.');
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user