Skip to content

Instantly share code, notes, and snippets.

@lucas-jones
Created December 14, 2022 09:08
Show Gist options
  • Save lucas-jones/f3684d94df2e0ad74428f62e21da1e36 to your computer and use it in GitHub Desktop.
Save lucas-jones/f3684d94df2e0ad74428f62e21da1e36 to your computer and use it in GitHub Desktop.
Spring
import { Vector3 } from "@milk-ecs/core";
import { System } from "typescript";
export type SpringParams = {
posPosCoef: number;
positionVelCoef: number;
velPosCoef: number;
velocityVelCoef: number;
};
export const CalcDampedSpringMotionParams = (deltaTime: number, angularFrequency: number, dampingRatio: number) => {
const epsilon = 0.0001;
var springParams: SpringParams = {
posPosCoef: 0,
positionVelCoef: 0,
velPosCoef: 0,
velocityVelCoef: 0
}
if (dampingRatio < 0.0) dampingRatio = 0.0;
if (angularFrequency < 0.0) angularFrequency = 0.0;
if (angularFrequency < epsilon) {
springParams.posPosCoef = 1.0;
springParams.positionVelCoef = 0.0;
springParams.velPosCoef = 0.0;
springParams.velocityVelCoef = 1.0;
return;
}
if (dampingRatio > 1.0 + epsilon) {
var za = -angularFrequency * dampingRatio;
var zb = angularFrequency * Math.sqrt(dampingRatio * dampingRatio - 1.0);
var z1 = za - zb;
var z2 = za + zb;
var e1 = Math.exp(z1 * deltaTime);
var e2 = Math.exp(z2 * deltaTime);
var invTwoZb = 1.0 / (2.0 * zb);
var e1_Over_TwoZb = e1 * invTwoZb;
var e2_Over_TwoZb = e2 * invTwoZb;
var z1e1_Over_TwoZb = z1 * e1_Over_TwoZb;
var z2e2_Over_TwoZb = z2 * e2_Over_TwoZb;
springParams.posPosCoef = e1_Over_TwoZb * z2 - z2e2_Over_TwoZb + e2;
springParams.positionVelCoef = -e1_Over_TwoZb + e2_Over_TwoZb;
springParams.velPosCoef = (z1e1_Over_TwoZb - z2e2_Over_TwoZb + e2) * z2;
springParams.velocityVelCoef = -z1e1_Over_TwoZb + z2e2_Over_TwoZb;
} else if (dampingRatio < 1.0 - epsilon) {
var omegaZeta = angularFrequency * dampingRatio;
var alpha = angularFrequency * Math.sqrt(1.0 - dampingRatio * dampingRatio);
var expTerm = Math.exp(-omegaZeta * deltaTime);
var cosTerm = Math.cos(alpha * deltaTime);
var sinTerm = Math.sin(alpha * deltaTime);
var invAlpha = 1.0 / alpha;
var expSin = expTerm * sinTerm;
var expCos = expTerm * cosTerm;
var expOmegaZetaSin_Over_Alpha = expTerm * omegaZeta * sinTerm * invAlpha;
springParams.posPosCoef = expCos + expOmegaZetaSin_Over_Alpha;
springParams.positionVelCoef = expSin * invAlpha;
springParams.velPosCoef = -expSin * alpha - omegaZeta * expOmegaZetaSin_Over_Alpha;
springParams.velocityVelCoef = expCos - expOmegaZetaSin_Over_Alpha;
} else {
var expTerm = Math.exp(-angularFrequency * deltaTime);
var timeExp = deltaTime * expTerm;
var timeExpFreq = timeExp * angularFrequency;
springParams.posPosCoef = timeExpFreq + expTerm;
springParams.positionVelCoef = timeExp;
springParams.velPosCoef = -angularFrequency * timeExpFreq;
springParams.velocityVelCoef = -timeExpFreq + expTerm;
}
return springParams;
};
export interface ISpring<T extends unknown> {
target: T;
position: T;
velocity: number;
update(deltaTime: number): void;
}
type SpringSettings = {
frequency: number;
damping: number;
}
export const SpringSettingsPresets = {
VERY_SLOW: { frequency: 0.05, damping: 1 },
SLOW: { frequency: 0.1, damping: 1 },
NORMAL: { frequency: 0.2, damping: 1 },
FAST: { frequency: 0.4, damping: 1 },
VERY_FAST: { frequency: 0.8, damping: 1 },
}
export class Spring implements ISpring<number> {
target = 0;
position = 0;
velocity = 0;
settings: SpringSettings;
constructor(settings: SpringSettings = SpringSettingsPresets.NORMAL) {
this.settings = settings;
}
update(deltaTime: number) {
var parms = CalcDampedSpringMotionParams(deltaTime, this.settings.frequency, this.settings.damping)
UpdateDampedSpringMotion(this, this.target, parms)
}
};
export const useSprings = <Q extends { [index: string]: ISpring<unknown> }>(springsMap: Q) => {
const springs = Object.values(springsMap);
return {
...springsMap,
update: (deltaTime: number) => {
springs.forEach(spring => spring.update(deltaTime));
},
target: (target: Partial<{[P in keyof Q]: Q[P]["target"]}>) => {
Object.entries(target).forEach(([key, value]) => {
springsMap[key].target = value;
});
}
}
};
// useSprings({ x: new Spring(), y: new Spring() }).
export class Vector3Spring implements ISpring<Vector3> {
position = new Vector3();
springs: Spring[] = [];
// settings: SpringSettings;
constructor(settings: SpringSettings = SpringSettingsPresets.NORMAL) {
this.springs.push(new Spring(settings));
this.springs.push(new Spring(settings));
this.springs.push(new Spring(settings));
}
set frequency(frequency: number) {
for (const spring of this.springs) {
spring.settings.frequency = frequency;
}
}
set damping(damping: number) {
for (const spring of this.springs) {
spring.settings.damping = damping;
}
}
set target(target: Vector3) {
this.springs[0].target = target.x;
this.springs[1].target = target.y;
this.springs[2].target = target.z;
}
// This is a little weird...
get velocity() {
return 1;
};
update(deltaTime: number) {
for (const spring of this.springs) {
spring.update(deltaTime);
}
this.position.set(
this.springs[0].position,
this.springs[1].position,
this.springs[2].position,
)
}
}
export const UpdateDampedSpringMotion = (spring: Spring, equilibriumPos: number, params: SpringParams) => {
const oldPos = spring.position - equilibriumPos;
const oldVel = spring.velocity;
spring.position = oldPos * params.posPosCoef + oldVel * params.positionVelCoef + equilibriumPos;
spring.velocity = oldPos * params.velPosCoef + oldVel * params.velocityVelCoef;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment