springs.js.coffee | |
---|---|
springs.js main() for the springs page demos | |
= require ./plugins = require mylibs/vec = require mylibs/Math = require mylibs/Spring = require mylibs/Circle = require mylibs/Canvas = require mylibs/particles = require mylibs/canvasEvents = require mylibs/collisions | $(document).ready ->
$canvas = $("#maincanvas")
canvasEl = $canvas.get(0)
canvas = new Canvas(canvasEl)
canvas.setOrigin('bottomleft')
events = canvasEvents canvasEl
elapsed = lastTime = 0
sim = 'force'
paused = true
updateObjectsFn = null |
spring properties | springLen = 0
springMinLen = 0
springElasticity = 0
springDamping = 0
elasticLimit = 0
compressiveness = 0 |
particle properties | particleMass = 0 |
world properties | gravity = 10
energy = null
forceOnEnd = Vector.Zero()
dhmParams = null |
objects | objects = []
drawableText = []
spring = null
particle = null
walls = [] |
mouse throwing variables | mouseOnBall = false
lastDragPoint = null
lastDragTime = 0
throwTime = 0
posOffset = 0
dragPoints = []
|
Collect control values | updateControls = ->
springLen = parseInt $('input[name=springLen]').val()
springMinLen = parseInt $('input[name=springMinLen]').val()
springElasticity = parseInt $('input[name=springElasticity]').val()
springDamping = parseFloat $('input[name=springDamping]').val()
elasticLimit = parseInt Math.max($('input[name=elasticLimit]').val(), springLen)
compressiveness = $('input[name=compressiveness]:checked').val()
particleMass = parseInt $('input[name=pMass]').val()
|
Display updated values next to sliders | $('#springLen').html(springLen)
$('#springMinLen').html(springMinLen)
$('#springElasticity').html(springElasticity)
$('#springDamping').html(springDamping)
$('#elasticLimit').html(elasticLimit)
$('#pMass').html(particleMass)
spring? && updateSpring(spring)
particle? && updateParticle(particle)
walls = [new Line(0, 0, canvas.width, 0),
new Line(canvas.width, 0, 0, canvas.height),
new Line(0, canvas.height, 0, -canvas.height),
new Line(canvas.width, canvas.height, -canvas.width, 0)
]
|
update spring properties (from controls) | updateSpring = (spring) ->
spring.elasticity = springElasticity
spring.damping = springDamping
spring.minLength = springMinLen
spring.elasticLimit = elasticLimit
if compressiveness == 'loose'
spring.compressiveness = Spring.LOOSE
else if compressiveness == 'rigid'
spring.compressiveness = Spring.RIGID
else
spring.compressive = true
updateParticle = (p) ->
p.mass = particleMass
p.radius = Math.max(particleMass / 2, 1)
p.speed = 0
p.direction = $V([0, 0, 0])
energy = null
dhmParams = Spring.initialDHMParams()
|
move spring endpoint up or down | moveSpringEnd = (dir) ->
if dir == 'u'
spring.pnt2 = spring.pnt2.add($V([0, 5, 0]))
else
spring.pnt2 = spring.pnt2.subtract($V([0, 5, 0]))
|
Initiate user 'throw' of a particle on spring @param {Vector} p current mouse position | throwBall = (p) ->
ts = ((new Date).getTime() - dragPoints[0][0]) / 1000
return unless ts > 0
dv = p.subtract(dragPoints[0][1])
particle.velocity = dv.divide(ts)
particle.speed = Math.min(particle.velocity.mag(), 150)
if (particle.speed > 0)
particle.direction = particle.velocity.toUnitVector()
console.log "ball speed: #{particle.speed}"
console.log "ball dir: #{particle.direction.inspect()}"
paused = false
|
Calculate DHM params now too | initPos = p.e(2) - posOffset - canvas.height/2
initVel = dv.e(2)
dhmParams = Spring.calculateDHMParams(initPos, initVel, springElasticity, springDamping)
throwTime = (new Date).getTime()
|
Force on particle due to spring simulation | forceFromStringSim = ->
console.log "init force from spring simulation"
sim = 'force'
$('#instructions').show().html($('#ffsInstructions').html()) |
Create spring & particle to attach to an endpoint Fix spring end to bottom center | spring = new Spring($V([canvas.width/2, 0, 0]), $V([0, springLen, 0]),
$V([0, 0, 0]),
$V([0, 10, 0]),
springLen)
updateSpring spring
objects = [spring]
updateObjectsFn = updateForceSim |
start animation | paused = false
|
Moving particle on a spring simulation | particleOnStringSim = ->
console.log "particle on spring simulation"
sim = 'particle'
$('#instructions').show().html($('#posInstructions').html()) |
Create spring & particle to attach to an endpoint Fix spring end to bottom center | spring = new Spring($V([canvas.width/2, canvas.height-100, 0]), $V([0, -springLen, 0]),
$V([0, 0, 0]),
$V([0, 0, 0]),
springLen)
updateSpring spring
particle = new Circle(canvas.width/2, canvas.height-springLen, 0, {
color: 'black'
})
updateParticle particle
objects = [spring, particle]
updateObjectsFn = updateParticleEnergySim |
start animation | paused = false
|
DHM simulation | dhmSim = ->
console.log "DHM simulation"
sim = 'dhm'
$('#instructions').show().html($('#posInstructions').html())
particle = new Circle(canvas.width/2, canvas.height/2, 0, {
color: 'black'
})
updateParticle particle
objects = [particle]
dhmParams = Spring.initialDHMParams()
updateObjectsFn = updateDHMSim |
start animation | paused = false
|
Multiple connected springs simulation | multiSpringSim = ->
console.log "Multispring simulation"
sim = 'multi'
|
Start w/ 2 springs & 2 particles | s1 = new Spring($V([canvas.width/2, canvas.height-40, 0]), $V([0, -springLen, 0]),
$V([0, 0, 0]),
$V([0, 0, 0]),
springLen)
updateSpring s1
s2 = new Spring($V([s1.endpoint().e(1), s1.endpoint().e(2), 0]), $V([0, -springLen, 0]),
$V([0, 0, 0]),
$V([0, 0, 0]),
springLen)
updateSpring s2
|
particle attached to s1 & s2 | pa = new Circle(s1.x(), s1.y(), 0, {
color: 'black',
id: 'p0'
})
updateParticle pa |
particle attached to s1 & s2 | p1 = new Circle(s2.x(), s2.y(), 0, {
color: 'black',
id: 'p1'
})
updateParticle p1 |
particle attached to s2 endpoint | p2 = new Circle(s2.endpoint().e(1), s2.endpoint().e(2), 0, {
color: 'black',
id: 'p2'
})
updateParticle p2 |
start last particle moving sideways | p2.speed = 100
p2.direction = $V([1, 1, 0])
s2.evel = p2.direction.x(p2.speed)
|
s1 point is fixed set particle/spring pairings for force calculations end: 1 = particle at start of spring, 2 = at end of spring | pa.springs = [{spring: s1, end: 1}]
p1.springs = [{spring: s1, end: 2}, {spring: s2, end: 1}]
p2.springs = [{spring: s2, end: 2}]
objects = [s1, s2, pa, p1, p2]
updateObjectsFn = updateMultiSpringSim |
start animation | paused = false
|
Draw objects on canvas | drawScene = (objects, ts) ->
canvas.clear()
textY = 10
for text in drawableText
canvas.drawText(text, 10, textY)
textY += 25
for obj in objects
switch obj.name
when 'Circle'
canvas.drawCircle obj
when 'Spring'
canvas.drawLineFromPoints obj.pnt1, obj.pnt2
queueOutput = (txt) ->
console.log txt
drawableText.push txt
checkCollisions = (ts) ->
for p in objects when p.name == 'Circle'
changed = false |
Check for particle bounce against edges | for w in walls
res = collisions.circleWallCollision p, w
console.log "p#{p.id} vs wall in #{res[0]}"
if res[0] == collisions.EMBEDDED
console.log "embedded - calculate normal"
if collisions.isImpendingCollision(res[0]) || (res[0] == collisions.EMBEDDED)
collisions.resolveCollisionFixed p, res[1]
p.moveByTime(ts) # move particle away from wall
changed = true
|
Sanity check for collison resolution | if p.x() < p.radius
p.pos.elements[0] = p.radius
p.direction.elements[0] = 0
else if p.x() > canvas.width - p.radius
p.pos.elements[0] = canvas.width - p.radius
p.direction.elements[0] = 0
if p.y() < p.radius
p.pos.elements[0] = p.radius
p.direction.elements[1] = 0
else if p.y() > canvas.height - p.radius
p.pos.elements[1] = canvas.height - p.radius
p.direction.elements[2] = 0
break
if changed
if p.springs? |
spring(s) endpoint property update | updateConnectingSprings(p)
else if spring?
spring.pnt2 = p.pos |
update spring endpoint(s) with particle's new position & velocity | updateConnectingSprings = (p) ->
for sp in p.springs
if sp.end == 1
sp.spring.pnt1 = p.pos
sp.spring.svel = p.velocity
else
sp.spring.pnt2 = p.pos
sp.spring.evel = p.velocity
|
calculate article position/speed from springs force & gravity @param {Circle} particle @param {Number} ts timestep | updateParticleFromSpringForces = (p, ts, count=1) ->
ten = $V([0, p.mass * gravity, 0])
for sp in p.springs
f = sp.spring.forceOnEndpoint({reverse: sp.end == 1})
if f == Spring.BOUNCE |
queueOutput "#{p.id} BOUNCE!" resolve collision | collisions.resolveCollisionFixed p, sp.spring.toVector()
p.pos = p.pos.add(p.direction.x(p.speed*ts))
updateConnectingSprings p |
console.log "new direction (#{p.id}): #{p.direction.inspect()}" | if count < 10 # prevent inf. recursion
updateParticleFromSpringForces(p, ts, count + 1)
else
console.log "BOUNCE MAX!"
return
else
ten = ten.add(f)
|
apply force to particle | queueOutput "tension on p #{p.id}: #{ten.inspect()}"
acc = ten.divide(p.mass) |
Try to dampen serious accelerations - doesn't help :( | acc.elements[0] -= 100 if acc.e(1) >= 500
acc.elements[1] -= 100 if acc.e(2) >= 500
p.pos = p.pos.add(p.direction.x(p.speed*ts)).add(acc.x(ts*ts/2))
p.setVelocity p.velocity.add(acc.x(ts))
updateConnectingSprings p
|
console.log "new direction (#{p.id}): #{p.direction.inspect()}" | |
update function for force simulation | updateForceSim = (ts) -> |
calculate force on endpoint | f = spring.forceOnEndpoint()
if (f == Spring.BOUNCE)
console.log("BOUNCE")
forceOnEnd = "BOUNCE"
else
forceOnEnd = f.inspect()
queueOutput "force at endpoint: #{forceOnEnd}"
queueOutput "spring length: #{spring.currentLength()}"
|
update function for particle simulation | updateParticleEnergySim = (ts) -> |
calculate new particle properties | next = particles.particleOnSpring(spring, particle, energy, ts, gravity)
queueOutput "particle pos: #{next.pos.inspect()}"
queueOutput "particle speed: #{next.speed}"
queueOutput "total energy: #{next.totalEnergy}"
spring.pnt2 = next.pos.dup()
particle.pos = next.pos.dup()
particle.speed = next.speed
if particle.speed > 0
queueOutput "particle direction: #{next.velocity.inspect()}"
particle.direction = next.velocity.dup()
energy = next.totalEnergy
queueOutput "spring length: #{spring.currentLength()}"
updateDHMSim = (ts) ->
if throwTime > 0
t = ((new Date()).getTime() - throwTime) / 1000
pos = Spring.getOscillatorPosition(springElasticity, springDamping, dhmParams, t)
speed = Spring.getOscillatorSpeed(springElasticity, springDamping, dhmParams, t, pos)
queueOutput "DHM pos: #{pos}"
queueOutput "DHM speed: #{speed}"
queueOutput "motion: #{dhmParams.motion}"
particle.moveTo $V([particle.x(), pos + canvas.height/2, 0])
particle.speed = speed
queueOutput "particle pos: #{particle.pos.inspect()}"
queueOutput "particle speed: #{particle.speed}"
updateMultiSpringSim = (ts) ->
for p in objects when p.name == 'Circle'
updateParticleFromSpringForces(p, ts)
|
adjust spring length and endpoint velocity based on force value animate all objects | update = -> |
Pass latest timestep to the collision detection function | timeNow = (new Date()).getTime()
if lastTime != 0
elapsed = (timeNow - lastTime) / 1000
elapsed = 0.1 if elapsed > 0.1
drawableText = []
updateObjectsFn.call(@, elapsed)
checkCollisions(elapsed)
lastTime = timeNow |
tick funtion | tick = ->
requestAnimFrame(tick)
unless paused
update()
drawScene(objects, elapsed)
|
convert mouse coordinates to vector with proper y-orientation | pointToVec = (p) ->
$V([p.x, canvas.height-p.y, 0])
|
prevent arrow keys from scrolling around | keyDown = (evt) ->
console.log "on key down #{evt.keyCode}"
evt.preventDefault()
evt.stopPropagation()
return false if paused
dir = null
switch evt.keyCode
when 38, 87
dir = 'u'
when 40, 83
dir = 'd'
moveSpringEnd(dir) if dir?
mouseDown = (evt) ->
p = events.convertEventToCanvas(evt)
mouseOnBall = particle && collisions.pointInCircle(pointToVec(p), particle)
if mouseOnBall
paused = true
energy = null
posOffset = p.y - particle.y()
dragPoints.push([(new Date()).getTime(), pointToVec(p)])
mouseUp = (evt) ->
p = events.convertEventToCanvas(evt)
if mouseOnBall
throwBall pointToVec(p)
mouseOnBall = false
mouseMove = (evt) ->
if mouseOnBall
lastDragPoint = events.convertEventToCanvas(evt)
particle.pos = pointToVec(lastDragPoint)
spring.pnt2 = particle.pos if spring?
dragPoints.push([(new Date()).getTime(), particle.pos])
dragPoints.shift() if dragPoints.length > 5
$('.forceFromSpring').click(forceFromStringSim)
$('.particleOnSpring').click(particleOnStringSim)
$('.dhm').click(dhmSim)
$('.multiSpring').click(multiSpringSim)
|
Update simulation controls | $('#controls input').change(updateControls);
$('#info span').hide()
$(document).keydown(keyDown)
$canvas.mousedown(mouseDown)
$canvas.mouseup(mouseUp)
$canvas.mousemove(mouseMove)
updateControls()
tick()
|