- EasingFunction에서 step 옵션을 제거하고 독립적인 snapSteps 속성으로 분리 - AnimationLoop에 snapSteps 기반의 움직임 양자화 로직 구현 - 에디터 파라미터 패널에 움직임 단계 조절 슬라이더 추가 - 기본 설정값에 SNAP_STEPS 추가 및 패키지 버전 업데이트 (1.2.10)
122 lines
4.0 KiB
TypeScript
122 lines
4.0 KiB
TypeScript
import { applyEasing } from '../utils/easing';
|
|
import { presetToVector, isRotationPreset } from '../utils/motionPresets';
|
|
import type {DistortionArea, Point} from "../types";
|
|
|
|
/**
|
|
* 애니메이션 루프 관리 클래스
|
|
*/
|
|
export class AnimationLoop {
|
|
/**
|
|
* 영역들의 드래그 벡터를 현재 진행도에 따라 업데이트
|
|
* @param areas 왜곡 영역 배열
|
|
* @returns 업데이트된 영역 배열
|
|
*/
|
|
public static updateAreaDragVectors(
|
|
areas: DistortionArea[]
|
|
): DistortionArea[] {
|
|
return areas.map((area) => {
|
|
const { progress, movement } = area;
|
|
|
|
// duration이 0이거나 프리셋이 'none'이면 애니메이션 없음
|
|
if (movement.duration <= 0 || movement.preset === 'none') {
|
|
return {
|
|
...area,
|
|
dragVector: { x: 0, y: 0 },
|
|
};
|
|
}
|
|
|
|
// 프리셋이 설정되어 있으면 프리셋 기반 벡터 사용
|
|
let baseVector: Point;
|
|
if (movement.preset) {
|
|
const strength = movement.strength ?? 0.1;
|
|
baseVector = presetToVector(movement.preset, strength);
|
|
} else {
|
|
// 프리셋 없으면 기존 vectorA 사용 (하위 호환성)
|
|
baseVector = movement.vectorA;
|
|
}
|
|
|
|
// 이징 적용
|
|
const easedProgress = applyEasing(progress, movement.easing);
|
|
|
|
// 벡터 계산
|
|
let dragVector: Point;
|
|
|
|
// 스텝 양자화 (영역 속성에서 가져옴)
|
|
const snapSteps = area.snapSteps ?? 0;
|
|
|
|
// 회전 프리셋인 경우 원운동
|
|
if (movement.preset && isRotationPreset(movement.preset)) {
|
|
const radius = Math.sqrt(baseVector.x * baseVector.x + baseVector.y * baseVector.y);
|
|
const direction = movement.preset === 'rotate-cw' ? 1 : -1;
|
|
|
|
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
|
|
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 {
|
|
...area,
|
|
dragVector,
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 모든 영역의 진행도를 델타 타임만큼 업데이트
|
|
* @param areas 왜곡 영역 배열
|
|
* @param deltaTime 델타 타임 (초)
|
|
* @returns 업데이트된 영역 배열
|
|
*/
|
|
public static updateProgress(
|
|
areas: DistortionArea[],
|
|
deltaTime: number
|
|
): DistortionArea[] {
|
|
return areas.map((area) => {
|
|
// duration이 0이면 progress 업데이트 안 함
|
|
if (area.movement.duration <= 0) {
|
|
return area;
|
|
}
|
|
|
|
let newProgress = area.progress + deltaTime / area.movement.duration;
|
|
newProgress %= 1.0; // 루프
|
|
|
|
return {
|
|
...area,
|
|
progress: newProgress,
|
|
};
|
|
});
|
|
}
|
|
}
|