Refactor step easing into independent snapSteps property
- EasingFunction에서 step 옵션을 제거하고 독립적인 snapSteps 속성으로 분리 - AnimationLoop에 snapSteps 기반의 움직임 양자화 로직 구현 - 에디터 파라미터 패널에 움직임 단계 조절 슬라이더 추가 - 기본 설정값에 SNAP_STEPS 추가 및 패키지 버전 업데이트 (1.2.10)
This commit is contained in:
parent
6d9dd082c1
commit
ecf3e81101
@ -13,7 +13,8 @@
|
||||
"Bash(findstr:*)",
|
||||
"Bash(npm link:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(nul)"
|
||||
"Bash(nul)",
|
||||
"Bash(cd:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
6
dist/index.d.mts
vendored
6
dist/index.d.mts
vendored
@ -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';
|
||||
/**
|
||||
* 내장 모션 프리셋 타입
|
||||
*/
|
||||
@ -73,6 +73,8 @@ interface DistortionArea {
|
||||
/** 렌즈 강도 (양수: 볼록, 음수: 오목, 0: 없음, 범위: -1.0 ~ 1.0) */
|
||||
strength: number;
|
||||
};
|
||||
/** 스텝 양자화 단계 수 (0=없음, 1~5단계, 이징과 독립적으로 적용) */
|
||||
snapSteps?: number;
|
||||
}
|
||||
/**
|
||||
* 영역 충돌 감지를 위한 경계 상자
|
||||
@ -429,6 +431,8 @@ declare const DEFAULT_AREA: {
|
||||
};
|
||||
/** 기본 렌즈 효과 강도 */
|
||||
readonly LENS_STRENGTH: 0;
|
||||
/** 기본 스텝 양자화 단계 수 (0=없음) */
|
||||
readonly SNAP_STEPS: 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
6
dist/index.d.ts
vendored
6
dist/index.d.ts
vendored
@ -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';
|
||||
/**
|
||||
* 내장 모션 프리셋 타입
|
||||
*/
|
||||
@ -73,6 +73,8 @@ interface DistortionArea {
|
||||
/** 렌즈 강도 (양수: 볼록, 음수: 오목, 0: 없음, 범위: -1.0 ~ 1.0) */
|
||||
strength: number;
|
||||
};
|
||||
/** 스텝 양자화 단계 수 (0=없음, 1~5단계, 이징과 독립적으로 적용) */
|
||||
snapSteps?: number;
|
||||
}
|
||||
/**
|
||||
* 영역 충돌 감지를 위한 경계 상자
|
||||
@ -429,6 +431,8 @@ declare const DEFAULT_AREA: {
|
||||
};
|
||||
/** 기본 렌즈 효과 강도 */
|
||||
readonly LENS_STRENGTH: 0;
|
||||
/** 기본 스텝 양자화 단계 수 (0=없음) */
|
||||
readonly SNAP_STEPS: 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
81
dist/index.js
vendored
81
dist/index.js
vendored
@ -243,7 +243,6 @@ var ShaderManager = class {
|
||||
};
|
||||
|
||||
// src/utils/easing.ts
|
||||
var createStepEasing = (steps) => (t) => Math.floor(t * steps) / steps;
|
||||
var easingFunctions = {
|
||||
linear: (t) => t,
|
||||
easeIn: (t) => t * t,
|
||||
@ -252,14 +251,7 @@ var easingFunctions = {
|
||||
easeInQuad: (t) => t * t,
|
||||
easeOutQuad: (t) => t * (2 - t),
|
||||
easeInCubic: (t) => t * t * t,
|
||||
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)
|
||||
easeOutCubic: (t) => 1 - Math.pow(1 - t, 3)
|
||||
};
|
||||
var applyEasing = (progress, easingType) => {
|
||||
const clampedProgress = Math.max(0, Math.min(1, progress));
|
||||
@ -353,20 +345,40 @@ var AnimationLoop = class {
|
||||
}
|
||||
const easedProgress = applyEasing(progress, movement.easing);
|
||||
let dragVector;
|
||||
const snapSteps = area.snapSteps ?? 0;
|
||||
if (movement.preset && isRotationPreset(movement.preset)) {
|
||||
const angle = easedProgress * Math.PI * 2;
|
||||
const radius = Math.sqrt(baseVector.x * baseVector.x + baseVector.y * baseVector.y);
|
||||
const direction = movement.preset === "rotate-cw" ? 1 : -1;
|
||||
dragVector = {
|
||||
x: Math.cos(angle * direction) * radius,
|
||||
y: Math.sin(angle * direction) * radius
|
||||
};
|
||||
if (snapSteps > 0) {
|
||||
const totalAngleSteps = snapSteps * 4;
|
||||
const rawAngle = progress * Math.PI * 2;
|
||||
const quantizedAngle = Math.round(rawAngle / (Math.PI * 2) * totalAngleSteps) / totalAngleSteps * Math.PI * 2;
|
||||
dragVector = {
|
||||
x: Math.cos(quantizedAngle * direction) * radius,
|
||||
y: Math.sin(quantizedAngle * direction) * radius
|
||||
};
|
||||
} else {
|
||||
const angle = easedProgress * Math.PI * 2;
|
||||
dragVector = {
|
||||
x: Math.cos(angle * direction) * radius,
|
||||
y: Math.sin(angle * direction) * radius
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const oscillation = Math.sin(easedProgress * Math.PI * 2);
|
||||
dragVector = {
|
||||
x: baseVector.x * oscillation,
|
||||
y: baseVector.y * oscillation
|
||||
};
|
||||
if (snapSteps > 0) {
|
||||
const oscillation = Math.sin(progress * Math.PI * 2);
|
||||
const quantized = Math.round(oscillation * snapSteps) / snapSteps;
|
||||
dragVector = {
|
||||
x: baseVector.x * quantized,
|
||||
y: baseVector.y * quantized
|
||||
};
|
||||
} else {
|
||||
const oscillation = Math.sin(easedProgress * Math.PI * 2);
|
||||
dragVector = {
|
||||
x: baseVector.x * oscillation,
|
||||
y: baseVector.y * oscillation
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
...area,
|
||||
@ -839,7 +851,9 @@ var DEFAULT_AREA = {
|
||||
/** 기본 벡터 B */
|
||||
VECTOR_B: { x: -0.1, y: -0.1 },
|
||||
/** 기본 렌즈 효과 강도 */
|
||||
LENS_STRENGTH: 0
|
||||
LENS_STRENGTH: 0,
|
||||
/** 기본 스텝 양자화 단계 수 (0=없음) */
|
||||
SNAP_STEPS: 0
|
||||
};
|
||||
|
||||
// src/components/ImageDistortion.tsx
|
||||
@ -1110,14 +1124,7 @@ var EASING_OPTIONS = [
|
||||
{ value: "easeOut", label: "\uAC10\uC18D (Ease Out)" },
|
||||
{ value: "easeInOut", label: "\uAC00\uAC10\uC18D (Ease In Out)" },
|
||||
{ value: "easeInQuad", label: "\uAC00\uC18D\xB2 (Ease In Quad)" },
|
||||
{ value: "easeOutQuad", label: "\uAC10\uC18D\xB2 (Ease Out Quad)" },
|
||||
{ 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" }
|
||||
{ value: "easeOutQuad", label: "\uAC10\uC18D\xB2 (Ease Out Quad)" }
|
||||
];
|
||||
var ParameterPanel = ({ area, onUpdateArea }) => {
|
||||
if (!area) {
|
||||
@ -1199,6 +1206,24 @@ var ParameterPanel = ({ area, onUpdateArea }) => {
|
||||
}
|
||||
)
|
||||
] }),
|
||||
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "parameter-group", children: [
|
||||
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { children: [
|
||||
"\uC6C0\uC9C1\uC784 \uB2E8\uACC4: ",
|
||||
(area.snapSteps ?? 0) === 0 ? "\uC5C6\uC74C" : `${area.snapSteps}\uB2E8\uACC4`
|
||||
] }),
|
||||
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
||||
"input",
|
||||
{
|
||||
type: "range",
|
||||
min: "0",
|
||||
max: "5",
|
||||
step: "1",
|
||||
value: area.snapSteps ?? 0,
|
||||
onChange: (e) => onUpdateArea({ snapSteps: parseInt(e.target.value) }),
|
||||
className: "slider"
|
||||
}
|
||||
)
|
||||
] }),
|
||||
/* @__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)("div", { className: "points-display", children: area.basePoints.map((point, idx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "point-coord", children: [
|
||||
|
||||
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
81
dist/index.mjs
vendored
81
dist/index.mjs
vendored
@ -183,7 +183,6 @@ var ShaderManager = class {
|
||||
};
|
||||
|
||||
// src/utils/easing.ts
|
||||
var createStepEasing = (steps) => (t) => Math.floor(t * steps) / steps;
|
||||
var easingFunctions = {
|
||||
linear: (t) => t,
|
||||
easeIn: (t) => t * t,
|
||||
@ -192,14 +191,7 @@ var easingFunctions = {
|
||||
easeInQuad: (t) => t * t,
|
||||
easeOutQuad: (t) => t * (2 - t),
|
||||
easeInCubic: (t) => t * t * t,
|
||||
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)
|
||||
easeOutCubic: (t) => 1 - Math.pow(1 - t, 3)
|
||||
};
|
||||
var applyEasing = (progress, easingType) => {
|
||||
const clampedProgress = Math.max(0, Math.min(1, progress));
|
||||
@ -293,20 +285,40 @@ var AnimationLoop = class {
|
||||
}
|
||||
const easedProgress = applyEasing(progress, movement.easing);
|
||||
let dragVector;
|
||||
const snapSteps = area.snapSteps ?? 0;
|
||||
if (movement.preset && isRotationPreset(movement.preset)) {
|
||||
const angle = easedProgress * Math.PI * 2;
|
||||
const radius = Math.sqrt(baseVector.x * baseVector.x + baseVector.y * baseVector.y);
|
||||
const direction = movement.preset === "rotate-cw" ? 1 : -1;
|
||||
dragVector = {
|
||||
x: Math.cos(angle * direction) * radius,
|
||||
y: Math.sin(angle * direction) * radius
|
||||
};
|
||||
if (snapSteps > 0) {
|
||||
const totalAngleSteps = snapSteps * 4;
|
||||
const rawAngle = progress * Math.PI * 2;
|
||||
const quantizedAngle = Math.round(rawAngle / (Math.PI * 2) * totalAngleSteps) / totalAngleSteps * Math.PI * 2;
|
||||
dragVector = {
|
||||
x: Math.cos(quantizedAngle * direction) * radius,
|
||||
y: Math.sin(quantizedAngle * direction) * radius
|
||||
};
|
||||
} else {
|
||||
const angle = easedProgress * Math.PI * 2;
|
||||
dragVector = {
|
||||
x: Math.cos(angle * direction) * radius,
|
||||
y: Math.sin(angle * direction) * radius
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const oscillation = Math.sin(easedProgress * Math.PI * 2);
|
||||
dragVector = {
|
||||
x: baseVector.x * oscillation,
|
||||
y: baseVector.y * oscillation
|
||||
};
|
||||
if (snapSteps > 0) {
|
||||
const oscillation = Math.sin(progress * Math.PI * 2);
|
||||
const quantized = Math.round(oscillation * snapSteps) / snapSteps;
|
||||
dragVector = {
|
||||
x: baseVector.x * quantized,
|
||||
y: baseVector.y * quantized
|
||||
};
|
||||
} else {
|
||||
const oscillation = Math.sin(easedProgress * Math.PI * 2);
|
||||
dragVector = {
|
||||
x: baseVector.x * oscillation,
|
||||
y: baseVector.y * oscillation
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
...area,
|
||||
@ -779,7 +791,9 @@ var DEFAULT_AREA = {
|
||||
/** 기본 벡터 B */
|
||||
VECTOR_B: { x: -0.1, y: -0.1 },
|
||||
/** 기본 렌즈 효과 강도 */
|
||||
LENS_STRENGTH: 0
|
||||
LENS_STRENGTH: 0,
|
||||
/** 기본 스텝 양자화 단계 수 (0=없음) */
|
||||
SNAP_STEPS: 0
|
||||
};
|
||||
|
||||
// src/components/ImageDistortion.tsx
|
||||
@ -1050,14 +1064,7 @@ var EASING_OPTIONS = [
|
||||
{ value: "easeOut", label: "\uAC10\uC18D (Ease Out)" },
|
||||
{ value: "easeInOut", label: "\uAC00\uAC10\uC18D (Ease In Out)" },
|
||||
{ value: "easeInQuad", label: "\uAC00\uC18D\xB2 (Ease In Quad)" },
|
||||
{ value: "easeOutQuad", label: "\uAC10\uC18D\xB2 (Ease Out Quad)" },
|
||||
{ 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" }
|
||||
{ value: "easeOutQuad", label: "\uAC10\uC18D\xB2 (Ease Out Quad)" }
|
||||
];
|
||||
var ParameterPanel = ({ area, onUpdateArea }) => {
|
||||
if (!area) {
|
||||
@ -1139,6 +1146,24 @@ var ParameterPanel = ({ area, onUpdateArea }) => {
|
||||
}
|
||||
)
|
||||
] }),
|
||||
/* @__PURE__ */ jsxs2("div", { className: "parameter-group", children: [
|
||||
/* @__PURE__ */ jsxs2("label", { children: [
|
||||
"\uC6C0\uC9C1\uC784 \uB2E8\uACC4: ",
|
||||
(area.snapSteps ?? 0) === 0 ? "\uC5C6\uC74C" : `${area.snapSteps}\uB2E8\uACC4`
|
||||
] }),
|
||||
/* @__PURE__ */ jsx3(
|
||||
"input",
|
||||
{
|
||||
type: "range",
|
||||
min: "0",
|
||||
max: "5",
|
||||
step: "1",
|
||||
value: area.snapSteps ?? 0,
|
||||
onChange: (e) => onUpdateArea({ snapSteps: parseInt(e.target.value) }),
|
||||
className: "slider"
|
||||
}
|
||||
)
|
||||
] }),
|
||||
/* @__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("div", { className: "points-display", children: area.basePoints.map((point, idx) => /* @__PURE__ */ jsxs2("div", { className: "point-coord", children: [
|
||||
|
||||
2
dist/index.mjs.map
vendored
2
dist/index.mjs.map
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@baekryang/responsive-image-canvas",
|
||||
"version": "1.2.7",
|
||||
"version": "1.2.10",
|
||||
"publishConfig": {
|
||||
"registry": "https://git.bnovalab.com/api/packages/baekryang/npm/"
|
||||
},
|
||||
|
||||
@ -13,13 +13,6 @@ const EASING_OPTIONS: { value: EasingFunction; label: string }[] = [
|
||||
{ value: 'easeInOut', label: '가감속 (Ease In Out)' },
|
||||
{ value: 'easeInQuad', label: '가속² (Ease In 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 }) => {
|
||||
@ -107,6 +100,22 @@ export const ParameterPanel: React.FC<ParameterPanelProps> = ({ area, onUpdateAr
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 스텝 양자화 */}
|
||||
<div className="parameter-group">
|
||||
<label>
|
||||
움직임 단계: {(area.snapSteps ?? 0) === 0 ? '없음' : `${area.snapSteps}단계`}
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="5"
|
||||
step="1"
|
||||
value={area.snapSteps ?? 0}
|
||||
onChange={(e) => onUpdateArea({ snapSteps: parseInt(e.target.value) })}
|
||||
className="slider"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 포인트 좌표 (읽기 전용 표시) */}
|
||||
<div className="parameter-group">
|
||||
<label>포인트 좌표 (캔버스에서 드래그)</label>
|
||||
|
||||
@ -41,23 +41,49 @@ export class AnimationLoop {
|
||||
// 벡터 계산
|
||||
let dragVector: Point;
|
||||
|
||||
// 스텝 양자화 (영역 속성에서 가져옴)
|
||||
const snapSteps = area.snapSteps ?? 0;
|
||||
|
||||
// 회전 프리셋인 경우 원운동
|
||||
if (movement.preset && isRotationPreset(movement.preset)) {
|
||||
const angle = easedProgress * Math.PI * 2;
|
||||
const radius = Math.sqrt(baseVector.x * baseVector.x + baseVector.y * baseVector.y);
|
||||
const direction = movement.preset === 'rotate-cw' ? 1 : -1;
|
||||
dragVector = {
|
||||
x: Math.cos(angle * direction) * radius,
|
||||
y: Math.sin(angle * direction) * radius,
|
||||
};
|
||||
|
||||
if (snapSteps > 0) {
|
||||
// 스텝 양자화: 각도를 이산적 단계로 양자화
|
||||
const totalAngleSteps = snapSteps * 4;
|
||||
const rawAngle = progress * Math.PI * 2;
|
||||
const quantizedAngle = Math.round(rawAngle / (Math.PI * 2) * totalAngleSteps) / totalAngleSteps * Math.PI * 2;
|
||||
dragVector = {
|
||||
x: Math.cos(quantizedAngle * direction) * radius,
|
||||
y: Math.sin(quantizedAngle * direction) * radius,
|
||||
};
|
||||
} else {
|
||||
const angle = easedProgress * Math.PI * 2;
|
||||
dragVector = {
|
||||
x: Math.cos(angle * direction) * radius,
|
||||
y: Math.sin(angle * direction) * radius,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// 일반 왕복 모션 (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 * oscillation,
|
||||
y: baseVector.y * oscillation,
|
||||
};
|
||||
if (snapSteps > 0) {
|
||||
// 스텝 양자화: oscillation 출력을 양자화
|
||||
// Math.round(sin * N) / N → 한 방향 N단계, 전체 2N+1개 위치
|
||||
const oscillation = Math.sin(progress * Math.PI * 2);
|
||||
const quantized = Math.round(oscillation * snapSteps) / snapSteps;
|
||||
dragVector = {
|
||||
x: baseVector.x * quantized,
|
||||
y: baseVector.y * quantized,
|
||||
};
|
||||
} else {
|
||||
const oscillation = Math.sin(easedProgress * Math.PI * 2);
|
||||
dragVector = {
|
||||
x: baseVector.x * oscillation,
|
||||
y: baseVector.y * oscillation,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@ -92,4 +118,4 @@ export class AnimationLoop {
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,14 +17,7 @@ export type EasingFunction =
|
||||
| 'easeInQuad'
|
||||
| 'easeOutQuad'
|
||||
| 'easeInCubic'
|
||||
| 'easeOutCubic'
|
||||
| 'steps2'
|
||||
| 'steps3'
|
||||
| 'steps4'
|
||||
| 'steps5'
|
||||
| 'steps6'
|
||||
| 'steps8'
|
||||
| 'steps10';
|
||||
| 'easeOutCubic';
|
||||
|
||||
/**
|
||||
* 내장 모션 프리셋 타입
|
||||
@ -104,6 +97,8 @@ export interface DistortionArea {
|
||||
/** 렌즈 강도 (양수: 볼록, 음수: 오목, 0: 없음, 범위: -1.0 ~ 1.0) */
|
||||
strength: number;
|
||||
};
|
||||
/** 스텝 양자화 단계 수 (0=없음, 1~5단계, 이징과 독립적으로 적용) */
|
||||
snapSteps?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -40,4 +40,6 @@ export const DEFAULT_AREA = {
|
||||
VECTOR_B: { x: -0.1, y: -0.1 },
|
||||
/** 기본 렌즈 효과 강도 */
|
||||
LENS_STRENGTH: 0,
|
||||
/** 기본 스텝 양자화 단계 수 (0=없음) */
|
||||
SNAP_STEPS: 0,
|
||||
} as const;
|
||||
@ -2,10 +2,6 @@ import { type EasingFunction } from '../types';
|
||||
|
||||
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,
|
||||
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),
|
||||
};
|
||||
|
||||
/**
|
||||
@ -43,4 +31,4 @@ export const applyEasing = (
|
||||
): number => {
|
||||
const clampedProgress = Math.max(0, Math.min(1, progress));
|
||||
return easingFunctions[easingType](clampedProgress);
|
||||
};
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user