Repository: motion-canvas/motion-canvas
File: packages/core/src/tweening/spring.ts (TypeScript)

Code before:
    }
    lastUpdate = project.time;

    // Add the timeSteps for the current frame to the stash
    accumulation += stepsPerFrame * timeStep;

    let divider = 1;

    // For the case we have uneven `timeStep` sizes, like when we have a
    // framerate of 25 and a simulation framerate of 60 we end up having
    // 2.4 steps per frame. So we just make substeps until the stash
    // has less then 0.001 steps left.
    while (divider < 10000) {
      // Calculate the substep size based on the divider
      const step = timeStep / divider;

      // Sub step until there are no longer sub steps available
      while (accumulation / step >= 1) {
        update(step);
        // remove the substeps from the stash
        accumulation -= step;
      }

      // Increase the divider
      divider *= 10;
    }

    // The simulation would run forever but is often barely noticeable after
    // some seconds. We fix this by checking if the delta between `to` and
    // `position` is smaller than the given `settleTolerance`. But we also need
    // to check if the velocity is below the tolerance too.
    if (
      Math.abs(to) - Math.abs(position) < settleTolerance &&
      Math.abs(velocity) < settleTolerance
    ) {
      settled = true;
    }

    const value = position;

    onProgress(value, project.time - startTime);
    yield;
  }

  onProgress(to, project.time - startTime);
  onEnd?.(to, project.time - startTime);
}

export interface Spring {
  mass: number;
  stiffness: number;

Review comment (refactor):
I think we can simplify that by calling `update` once, for the missing fraction of a frame.
For example, let's assume that a frame takes 33ms (30FPS) while the physics should be integrated using 10ms intervals (100FPS), then we should perform four updates per frame:
```
update(10ms);
update(10ms);
update(10ms);
update(3ms); // missing fraction
// 10 + 10 + 10 + 3 = 33ms
```
An update loop for something like this could look as follows:
```ts
const startTime = thread.time();
let simulationTime = startTime;

while (!settled) {
  // while our simulation is behind the project time
  while (simulationTime < project.time) {
    // calculate the remaining difference:
    const difference = project.time - simulationTime;

    // if the remaining time is smaller than our simulation step
    if (timeStep > difference) {
      // update using the remaining difference
      update(difference);
      // make the simulation time equal to the project time to make sure the while loop ends.
      simulationTime = project.time;
    } else {
      // otherwise update using the simulation step
      update(timeStep);
      // progress the simulation time by the same amount
      simulationTime += timeStep;
    }
  }
  
  // handle settling and call onProgress
}
```
Because we initialize the `simulationTime` using the thread time, it will also account for the thread offset.

Code after:
        settled = true;
        // Break out when settled
        break;
      }
    }

    // Only yield if we haven't settled yet.
    if (!settled) {
      onProgress(position, project.time - startTime);
      yield;
    }
  }

  onProgress(to, project.time - startTime);
  onEnd?.(to, project.time - startTime);
}

export interface Spring {
  mass: number;
  stiffness: number;
  damping: number;
  initialVelocity?: number;
}

export function makeSpring(
  mass: number,
  stiffness: number,
  damping: number,
  initialVelocity?: number,
): Spring {
  return {
    mass,
    stiffness,
    damping,
    initialVelocity,
  };
}

export const BeatSpring: Spring = makeSpring(0.13, 5.7, 1.2, 10.0);
export const PlopSpring: Spring = makeSpring(0.2, 20.0, 0.68, 0.0);
export const BounceSpring: Spring = makeSpring(0.08, 4.75, 0.05, 0.0);
export const SwingSpring: Spring = makeSpring(0.39, 19.85, 2.82, 0.0);
export const JumpSpring: Spring = makeSpring(0.04, 10.0, 0.7, 8.0);
export const StrikeSpring: Spring = makeSpring(0.03, 20.0, 0.9, 4.8);
export const SmoothSpring: Spring = makeSpring(0.16, 15.35, 1.88, 0.0);