Spring.js.coffee | |
---|---|
@class Spring Represents a spring object with methods for resolving physics | |
= require ./Line | class Spring extends Line |
Spring compressiveness values | Spring.LOOSE = 1
Spring.RIGID = 2 |
force value constant | Spring.BOUNCE = -1
Spring.Motion = {
SHM: 'shm'
UNDERDAMPED: 'underdamped'
CRITICAL: 'critical'
OVERDAMPED: 'overdamped'
} |
@param {Vector} pnt1 starting position vector @param {Vector} vec direction & length of spring vector @param {Vector} v1 velocity at starting point @param {Vector] v2 velocity at end point @param {Object} opts object containing additional properties | constructor: (@pnt1, @vec, v1, v2, @length, opts={}) ->
super(@pnt1.e(1), @pnt1.e(2), @vec.e(1), @vec.e(2), opts)
@svel = v1.dup()
@evel = v2.dup()
@[x] = val for x, val of opts # Save optional properties
@name = 'Spring'
@pnt2 = @endpoint() |
Set property defaults | @elasticity ?= 1
@damping ?= 0
@elasticLimit ?= 1
@compressiveness ?= Spring.RIGID
@compressive ?= false
@minLength ?= 1
currentLength: ->
@pnt1.subtract(@pnt2).mag()
isCompressive: =>
@compressive || (@compressiveness == Spring.RIGID)
|
General purpose function to determine force on a particle due to the
spring (at spring endpoint). | forceOnEndpoint: (opts={}) -> |
Caller can pass reverse: true to calculate force on the 'start' end of the spring | if opts.reverse
pnt1 = @pnt2
pnt2 = @pnt1
vel1 = @evel
vel2 = @svel
else
pnt1 = @pnt1
pnt2 = @pnt2
vel1 = @svel
vel2 = @evel
elasticity = 0
damping = 0
v = pnt1.subtract(pnt2)
d = v.mag()
return Vector.Zero() if d == 0
|
loose elastics have no force when compressed | if d <= @length
return Vector.Zero() if @compressiveness == Spring.LOOSE
|
apply 2nd elastic limit (inextensible behavior) | if d >= @elasticLimit*1.2 ||
d <= @minLength*0.9 ||
(d <= @length*0.9 and @compressiveness == Spring.RIGID)
return Spring.BOUNCE
|
apply 1st elastic limit (increased force and damping) | if (d >= @elasticLimit) || (d <= @minLength) || (d <= @length and @compressiveness == Spring.RIGID)
elasticity = @elasticity * 20
damping = Math.max(@damping*10, 20)
|
calculate force by Hooke's law | e = d - @length
vec = v.divide(d)
f = if @damping > 0
comp = vel1.subtract(vel2).component(vec)
@damping * comp + @elasticity * e
else
elasticity * e
vec.x(f)
toVector: ->
@pnt2.subtract(@pnt1).toUnitVector()
toString: ->
"Spring at #{@pnt1.inspect()}" |
Static functions | |
Pure Damped Harmonic Motion (DHM) oscillator functions Using intial position and velocity and a time, will calculate position and velocity at that time. | Spring.initialDHMParams = ->
motion: Spring.Motion.SHM
phase: 0
amp: 0 |
Calculates the params and the form of the motion @param {Number} initPos initial position @param {Number} initVel initial velocity @param {Number} elasticity of the spring @param {Number} damping of the spring @return {Object} a list of parameters for an oscillation, to be used in the getOscillatorPosition and getOscillatorSpeed functions: type of motion, phase (p), amplitude (C), and optionally a, r1, r2 | Spring.calculateDHMParams = (initPos, initVel, elasticity, damping)->
omega = Math.sqrt(elasticity)
d = damping / 2
alpha = d * d - elasticity
if d == 0 # no damping: use SHM
p = Math.atan2(omega * initPos, initVel)
c = Math.sqrt(elasticity * initPos * initPos + initVel * initVel) / omega
return {
motion: Spring.Motion.SHM
phase: p
amp: c
}
else if d < omega # underdamped
v = initVel + d * initPos
p = Math.atan2(initPos * omega, v)
s = initPos * initPos * elasticity + v * v
c = Math.sqrt(s) / omega
return {
motion: Spring.Motion.UNDERDAMPED
phase: p
amp: c
a: Math.sqrt(-alpha)
}
else if d == omega # critical
return {
motion: Spring.Motion.CRITICAL
phase: initPos
amp: omega * initPos + initVel
}
else # overdamping
sq = Math.sqrt(alpha)
r1 = -d - sq
r2 = -d + sq
a = (r2 * initPos - initVel) / (2 * sq)
b = -(r1 * initPos - initVel) / (2 * sq)
return {
motion: Spring.Motion.OVERDAMPED
phase: a
amp: b
r1: r1
r2: r2
} |
Used in conjunction with other DHM functions. Parameters passed to this function must be generated by calculateDHMParams(). @param {Number} elasticity @param {Number} damping @param {Object} params values returned by calculateDHMParams @param {Number} time @return {Number} new position value | Spring.getOscillatorPosition = (elasticity, damping, params, time) ->
omega = Math.sqrt(elasticity)
d = damping / 2
switch params.motion
when Spring.Motion.SHM
params.amp * Math.sin(omega * time + params.phase)
when Spring.Motion.UNDERDAMPED
params.amp * Math.sin(params.a * time + params.phase) * Math.exp(-d * time)
when Spring.Motion.CRITICAL
(params.phase + time * params.amp) * Math.exp(-d * time)
when Spring.Motion.OVERDAMPED
params.phase * Math.exp(params.r1 * time) + params.amp * Math.exp(params.r2 * time)
else
throw "getOscillatorPosition: Unknown oscillator motion!" |
Used in conjunction with other DHM functions. Parameters passed to this function must be generated by calculateDHMParams(). @param {Number} elasticity @param {Number} damping @param {Object} params values returned by calculateDHMParams @param {Number} time @param {Number} pos value from getOscillatorPosition() @return {Number} new speed value | Spring.getOscillatorSpeed = (elasticity, damping, params, time, pos) ->
omega = Math.sqrt(elasticity)
d = damping / 2
switch params.motion
when Spring.Motion.SHM
params.amp * omega * Math.cos(omega * time + params.phase)
when Spring.Motion.UNDERDAMPED
params.amp * omega * Math.cos(params.a * time + params.phase) * Math.exp(-d * time) - d * pos
when Spring.Motion.CRITICAL
params.amp * Math.exp(-d * time) - d * pos
when Spring.Motion.OVERDAMPED
params.phase * params.r1 * Math.exp(params.r1 * time) + params.amp * params.r2 * Math.exp(params.r2 * time)
else
throw "getOscillatorSpeed: Unknown oscillator motion!"
root = exports ? window
root.Spring = Spring
|