Jump To …

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).
This function must be used when neither endpoint of the spring is fixed in place. @params: {Object} opts options @return {Vector} force vector

  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