80 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			80 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { warning } from 'hey-listen';
 | |
| import { clamp } from '../../utils/clamp.mjs';
 | |
| 
 | |
| const safeMin = 0.001;
 | |
| const minDuration = 0.01;
 | |
| const maxDuration = 10.0;
 | |
| const minDamping = 0.05;
 | |
| const maxDamping = 1;
 | |
| function findSpring({ duration = 800, bounce = 0.25, velocity = 0, mass = 1, }) {
 | |
|     let envelope;
 | |
|     let derivative;
 | |
|     warning(duration <= maxDuration * 1000, "Spring duration must be 10 seconds or less");
 | |
|     let dampingRatio = 1 - bounce;
 | |
|     dampingRatio = clamp(minDamping, maxDamping, dampingRatio);
 | |
|     duration = clamp(minDuration, maxDuration, duration / 1000);
 | |
|     if (dampingRatio < 1) {
 | |
|         envelope = (undampedFreq) => {
 | |
|             const exponentialDecay = undampedFreq * dampingRatio;
 | |
|             const delta = exponentialDecay * duration;
 | |
|             const a = exponentialDecay - velocity;
 | |
|             const b = calcAngularFreq(undampedFreq, dampingRatio);
 | |
|             const c = Math.exp(-delta);
 | |
|             return safeMin - (a / b) * c;
 | |
|         };
 | |
|         derivative = (undampedFreq) => {
 | |
|             const exponentialDecay = undampedFreq * dampingRatio;
 | |
|             const delta = exponentialDecay * duration;
 | |
|             const d = delta * velocity + velocity;
 | |
|             const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration;
 | |
|             const f = Math.exp(-delta);
 | |
|             const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio);
 | |
|             const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
 | |
|             return (factor * ((d - e) * f)) / g;
 | |
|         };
 | |
|     }
 | |
|     else {
 | |
|         envelope = (undampedFreq) => {
 | |
|             const a = Math.exp(-undampedFreq * duration);
 | |
|             const b = (undampedFreq - velocity) * duration + 1;
 | |
|             return -safeMin + a * b;
 | |
|         };
 | |
|         derivative = (undampedFreq) => {
 | |
|             const a = Math.exp(-undampedFreq * duration);
 | |
|             const b = (velocity - undampedFreq) * (duration * duration);
 | |
|             return a * b;
 | |
|         };
 | |
|     }
 | |
|     const initialGuess = 5 / duration;
 | |
|     const undampedFreq = approximateRoot(envelope, derivative, initialGuess);
 | |
|     duration = duration * 1000;
 | |
|     if (isNaN(undampedFreq)) {
 | |
|         return {
 | |
|             stiffness: 100,
 | |
|             damping: 10,
 | |
|             duration,
 | |
|         };
 | |
|     }
 | |
|     else {
 | |
|         const stiffness = Math.pow(undampedFreq, 2) * mass;
 | |
|         return {
 | |
|             stiffness,
 | |
|             damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
 | |
|             duration,
 | |
|         };
 | |
|     }
 | |
| }
 | |
| const rootIterations = 12;
 | |
| function approximateRoot(envelope, derivative, initialGuess) {
 | |
|     let result = initialGuess;
 | |
|     for (let i = 1; i < rootIterations; i++) {
 | |
|         result = result - envelope(result) / derivative(result);
 | |
|     }
 | |
|     return result;
 | |
| }
 | |
| function calcAngularFreq(undampedFreq, dampingRatio) {
 | |
|     return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
 | |
| }
 | |
| 
 | |
| export { calcAngularFreq, findSpring, maxDamping, maxDuration, minDamping, minDuration };
 | 
