/** 시각 탄성만 조절한다. 원본 입체·절단·부피 데이터와 독립이다. */ export const ENDLESS_SOFT={compression:.18,frequency:15,damping:4.8,duration:1.45,initialAmplitude:.045,shearRatio:.65} as const; export interface SoftPose {squash:number;shear:number} const REST:SoftPose=Object.freeze({squash:0,shear:0}); export function softPress(progress:number,reducedMotion=false):SoftPose { if(reducedMotion)return REST; const t=Math.max(0,Math.min(1,progress));if(t===0)return REST; return {squash:-ENDLESS_SOFT.compression*t*t*(3-2*t),shear:0}; } /** 변위와 초기 속도를 맞춘 감쇠 진동. 경과 시간으로 계산해 프레임률에 독립적이다. */ export function softSpring(elapsed:number,amplitude:number=ENDLESS_SOFT.compression,reducedMotion=false):SoftPose { if(reducedMotion||elapsed>=ENDLESS_SOFT.duration||amplitude===0)return REST; const t=Math.max(0,elapsed),w=ENDLESS_SOFT.frequency,d=ENDLESS_SOFT.damping,a=Math.max(-.25,Math.min(.25,amplitude)),envelope=Math.exp(-d*t); return {squash:-a*envelope*(Math.cos(w*t)+d/w*Math.sin(w*t)),shear:a*ENDLESS_SOFT.shearRatio*envelope*Math.sin(w*t)}; }