Skip to content

Commit

Permalink
Springy action with an RK4 integrator!
Browse files Browse the repository at this point in the history
  • Loading branch information
mcmire committed Feb 12, 2013
1 parent f8879db commit 35ccb4a
Show file tree
Hide file tree
Showing 6 changed files with 306 additions and 121 deletions.
1 change: 1 addition & 0 deletions html/repulsion.html
Expand Up @@ -16,6 +16,7 @@
<script src="../js/lib/request_anim_shims.js"></script>

<script src="../js/lib/vec2.js"></script>
<script src="../js/lib/integrator.js"></script>
<script src="../js/lib/drawable.js"></script>
<script src="../js/lib/canvas_object.js"></script>
<script src="../js/lib/canvas_object_collection.js"></script>
Expand Down
32 changes: 18 additions & 14 deletions js/demos/repulsion.js
Expand Up @@ -73,19 +73,18 @@
this.width = this.height = options.radius * 2
},

/*
defaultPosition: function() {
return Vec2(700, 50)
},
*/

defaultVelocity: function() {
return Vec2(8, 4)
},

setVel: function() {
uber.setVel.call(this)
collision.applyTo(this.canvas, this)
accelerationAt: function(obj, t) {
var k, b, acc, spring, diff
k = 32
b = 1
acc = Vec2(0,0)
spring = Vec2(200,170)
diff = Vec2(0,0)
Vec2.sub(spring, obj.pos, diff)
acc[0] = k * (diff[0] / this.mass) - b * obj.vel[0]
acc[1] = k * (diff[1] / this.mass) - b * obj.vel[1]
//console.log(JSON.stringify({acc: acc}))
return acc
},

render: function() {
Expand All @@ -99,7 +98,12 @@

canvas = Canvas('#wrapper')
objects = canvas.buildObjectCollection(CanvasObjectCollection)
objects.addObject(Ball, {radius: 10})
objects.addObject(Ball, {
radius: 10,
pos: Vec2(200,250),
//vel: Vec2(0,0),
mass: 1
})

window.canvas = canvas
})()
Expand Down
54 changes: 25 additions & 29 deletions js/lib/canvas.js
Expand Up @@ -6,7 +6,7 @@ window.Canvas = P(function(proto, uber, klass, uberklass) {
init: function(id, options) {
this.$wrapperElement = $(id)
this.options = options || {}
if (!this.options.ticksPerSecond) this.options.ticksPerSecond = 1
if (!this.options.ticksPerSecond) this.options.ticksPerSecond = 15
if (!this.options.width) this.options.width = 800
if (!this.options.height) this.options.height = 500

Expand All @@ -17,28 +17,29 @@ window.Canvas = P(function(proto, uber, klass, uberklass) {
this._addCanvas()

this.ctx = this.$canvasElement[0].getContext('2d')
this.msPerTick = 1000 / this.options.ticksPerSecond
console.log({msPerTick: this.msPerTick})
this.secondsPerTick = 1 / this.options.ticksPerSecond

this.tick = (function (_this) {
var lastRenderTime = (new Date()).getTime()
return function () {
var msPerTick
;(function (_this) {
var dt, loop, tick

// This is copied from:
// <http://gafferongames.com/game-physics/fix-your-timestep/>

msPerTick = _this.msPerTick
now = (new Date()).getTime()
realTime = now - _this.startTime

_this.objects.update(realTime, msPerTick)
_this.objects.render()
dt = _this.secondsPerTick

loop = function () {
tick()
if (_this.isRunning) {
_this.timer = requestAnimFrame(_this.tick)
_this.timer = requestAnimFrame(loop)
}
}

tick = function () {
_this.objects.clear()
_this.objects.update(_this.gameTime, dt)
_this.objects.render()
_this.gameTime += dt
}

_this.loop = loop
_this.tick = tick
})(this)
},

Expand Down Expand Up @@ -82,11 +83,6 @@ window.Canvas = P(function(proto, uber, klass, uberklass) {
this.$starterBtn.text('Stop')
},

tickOnce: function () {
this.objects.update()
this.objects.render(1)
},

reset: function () {
this.stop()
this._clear()
Expand All @@ -107,7 +103,7 @@ window.Canvas = P(function(proto, uber, klass, uberklass) {

randomPos: function(bx, by) {
var bx, by
switch(arguments.length) {
switch (arguments.length) {
case 2:
bx = arguments[0]
by = arguments[1]
Expand All @@ -126,10 +122,10 @@ window.Canvas = P(function(proto, uber, klass, uberklass) {
},

center: function() {
return [
this.options.width / 2,
this.options.height / 2
]
return Vec2(
Math.floor(this.options.width / 2),
Math.floor(this.options.height / 2)
)
},

//---
Expand All @@ -139,7 +135,7 @@ window.Canvas = P(function(proto, uber, klass, uberklass) {
// TODO: This should subtract time paused or something...
this.startTime = (new Date()).getTime()
this.gameTime = 0
this.tick()
this.loop()
},

_stopTimer: function () {
Expand Down Expand Up @@ -176,7 +172,7 @@ window.Canvas = P(function(proto, uber, klass, uberklass) {
})

$drawBtn.on('click', function() {
canvas.tickOnce()
canvas.tick()
return false
})

Expand Down
96 changes: 20 additions & 76 deletions js/lib/canvas_object.js
Expand Up @@ -2,29 +2,23 @@
'use strict';

window.CanvasObject = P(Drawable, function(proto, uber) {
var State, Derivative

Props = function () {
return { pos: Vec2(), vel: Vec2(), acc: Vec2() }
}

return {
init: function(parent, opts) {
uber.init.call(this, parent)
this.setOptions(opts)
if (!this.pos) this.pos = this.defaultPosition()
if (!this.vel) this.vel = this.defaultVelocity()
if (!this.acc) this.acc = this.defaultAcceleration()
if (!this.color) this.color = this.defaultColor()
if (this.mass == null) this.mass = this.defaultMass()
if (this.pos == null) this.pos = this.defaultPosition()
if (this.vel == null) this.vel = this.defaultVelocity()
if (this.color == null) this.color = this.defaultColor()
},

setOptions: function(opts) {
this.options = opts
this.mass = opts.mass
this.width = opts.width
this.height = opts.height
this.pos = opts.pos
this.vel = opts.vel
this.acc = opts.acc
this.color = opts.color
},

Expand All @@ -36,56 +30,27 @@ window.CanvasObject = P(Drawable, function(proto, uber) {
this.parent.resumeDrawingObject(this)
},

update: function (t) {
// Here we are using the Runge-Kutta algorithm in order to accurately
// calculate the integrals for velocity and position based on acceleration.
// There is a very very good article about this and why we prefer this
// method instead of the usual Euler integration here:
// <http://gafferongames.com/game-physics/integration-basics/>

// TODO: So if the object collides into the bounding box... how do we fix
// this?

var dt, a, b, c, d, deltapos, deltavel

dt = this.canvas.msPerTick

a = this._calcRKComponent(t, 0, Props())
b = this._calcRKComponent(t, dt*0.5, a)
c = this._calcRKComponent(t, dt*0.5, b)
d = this._calcRKComponent(t, dt, c)

// newvel = (a.vel + 2*(b.vel + c.vel) + d.vel) / 6
// deltapos = newvel * dt
deltapos = Vec2()
deltapos[0] = ((a.vel[0] + 2*(b.vel[0] + c.vel[0]) + d.vel[0]) / 6) * dt
deltapos[1] = ((a.vel[1] + 2*(b.vel[1] + c.vel[1]) + d.vel[1]) / 6) * dt

// newacc = 1/6 * (a.acc + 2*(b.acc + c.acc) + d.acc)
// deltavel = newacc * dt
deltavel = Vec2()
deltavel[0] = (1/6) * (a.acc[0] + 2*(b.acc[0] + c.acc[0]) + d.acc[0]) * dt
deltavel[1] = (1/6) * (a.acc[1] + 2*(b.acc[1] + c.acc[1]) + d.acc[1]) * dt

Vec2.add(this.pos, deltapos)
Vec2.add(this.vel, deltavel)
update: function (t, dt) {
integrator.advance(this, t, dt)
// TODO: check for collisions
},

render: function() {
this.clear()
//this.clear()
// do more here in a subclass
},

calcAcc: function (newstate, t) {
// This is called by the integrator
accelerationAt: function (state, t) {
return Vec2(0,0)
},

clear: function () {
this.ctx.clearRect(
this.pos[0]-(this.width/2),
this.pos[1]-(this.height/2),
this.width,
this.height
Math.ceil(this.pos[0]-(this.width/2))-1,
Math.ceil(this.pos[1]-(this.height/2))-1,
this.width+1,
this.height+1
)
},

Expand All @@ -103,16 +68,15 @@ window.CanvasObject = P(Drawable, function(proto, uber) {
},

defaultPosition: function() {
return this.canvas.randomPos(this.width, this.height)
return Vec2(0,0)
},

defaultVelocity: function() {
//return this.randomVector(10)
return Vec2(2,2)
return Vec2(0,0)
},

defaultAcceleration: function() {
return Vec2(0,0)
defaultMass: function () {
return 1
},

defaultColor: function() {
Expand All @@ -124,27 +88,7 @@ window.CanvasObject = P(Drawable, function(proto, uber) {
util.rand.int(def, def),
util.rand.int(def, def)
)
},

_calcRKComponent: function (t, dt, d) {
var state, d2

// First, advance the current position and velocity using Euler
// integration
state = Props()
state.pos[0] = this.pos[0] + d.vel[0] * dt
state.pos[1] = this.pos[1] + d.vel[1] * dt
state.vel[0] = this.vel[0] + d.acc[0] * dt
state.vel[1] = this.vel[1] + d.acc[1] * dt

// Then, recalculate acceleration using the newly calculated velocity
d2 = Props()
d2.vel = state.vel
// Q: So if we are re-calculating acceleration... what if we want to set
// acceleration based on input?
d2.acc = this.calcAcc(state, t + dt)
return d2
},
}
}
})

11 changes: 9 additions & 2 deletions js/lib/canvas_object_collection.js
Expand Up @@ -9,10 +9,10 @@ window.CanvasObjectCollection = P(Drawable, function (proto, uber) {
this.hiddenObjects = []
},

update: function () {
update: function (t, dt) {
var i, len
for (i = 0, len = this.objects.length; i < len; i++) {
this.objects[i].update()
this.objects[i].update(t, dt)
}
},

Expand All @@ -23,6 +23,13 @@ window.CanvasObjectCollection = P(Drawable, function (proto, uber) {
}
},

clear: function() {
var i, len
for (i = 0, len = this.objects.length; i < len; i++) {
this.objects[i].clear()
}
},

addObject: function(klass/*, rest... */) {
var rest, args, object
rest = Array.prototype.slice.call(arguments, 1)
Expand Down

0 comments on commit 35ccb4a

Please sign in to comment.