Created
December 14, 2022 09:08
-
-
Save lucas-jones/f3684d94df2e0ad74428f62e21da1e36 to your computer and use it in GitHub Desktop.
Spring
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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