-
-
Save BonsaiDen/31224d625a9c5d9f46ef to your computer and use it in GitHub Desktop.
This file contains 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
/*global graphics*/ | |
var Class = require('Class').Class, | |
StaticBox = require('StaticBox').StaticBox; | |
// Dynamic AABB Box ----------------------------------------------------------- | |
// ---------------------------------------------------------------------------- | |
exports.DynamicBox = Class(function(x, y, w, h) { | |
StaticBox(this, x, y, w, h); | |
// Collision information | |
this.contact = { | |
surface: { | |
up: null, | |
right: null, | |
down: null, | |
left: null, | |
area: { | |
up: 0, | |
right: 0, | |
down: 0, | |
left: 0 | |
}, | |
previous: { | |
up: null, | |
right: null, | |
down: null, | |
left: null | |
} | |
}, | |
area: { | |
up: 0, | |
right: 0, | |
down: 0, | |
left: 0 | |
} | |
}; | |
this.impact = { | |
surface: { | |
up: null, | |
right: null, | |
down: null, | |
left: null | |
} | |
}; | |
this.inside = { | |
surface: null, | |
area: null | |
}; | |
}, StaticBox, { | |
getType: function(type) { | |
return 'DynamicBox'; | |
}, | |
// Public API ------------------------------------------------------------- | |
update: function(dt) { | |
this.updatePosition(dt); | |
this.updateBounds(); | |
}, | |
render: function(debug) { | |
var x = this.pos.x, | |
y = this.pos.y, | |
sx = this.size.x, | |
sy = this.size.y, | |
cx = x + sx / 2, | |
cy = y + sy / 2, | |
vx = this.vel.x, | |
vy = this.vel.y, | |
vl = Math.sqrt(vx * vx + vy * vy) || 1; | |
graphics.setColor(255, 255, 0); | |
graphics.rect(x, y, sx, sy); | |
graphics.setColor(0, 0, 255); | |
graphics.line(cx, cy, cx + vx / vl * 10, cy + vy / vl * 10); | |
graphics.setColor(0, 0, 255); | |
if (this.contact.surface.down) { | |
graphics.line(x, y + sy - 1, x + sx, y + sy - 1); | |
} | |
if (this.contact.surface.up) { | |
graphics.line(x, y, x + sx, y); | |
} | |
if (this.contact.surface.left) { | |
graphics.line(x, y, x, y + sy); | |
} | |
if (this.contact.surface.right) { | |
graphics.line(x + sx - 1, y, x + sx - 1, y + sy); | |
} | |
}, | |
// Default behavior ------------------------------------------------------- | |
updatePosition: function(dt) { | |
this.pos.x = this.pos.x + this.vel.x; | |
this.pos.y = this.pos.y + this.vel.y; | |
}, | |
setPosition: function(x, y) { | |
this.pos.x = Math.round(x); | |
this.pos.y = Math.round(y); | |
this.vel.x = 0; | |
this.vel.y = 0; | |
this.updateBounds(); | |
}, | |
onCollision: function(other, vel, normal) { | |
this.vel.x = this.vel.x + vel.x; | |
this.vel.y = this.vel.y + vel.y; | |
if (this.contact.surface.down && !this.contact.surface.previous.down) { | |
this.impact.surface.down = other; | |
} | |
if (this.contact.surface.up && !this.contact.surface.previous.up) { | |
this.impact.surface.up = other; | |
} | |
if (this.contact.surface.left && !this.contact.surface.previous.left) { | |
this.impact.surface.left = other; | |
} | |
if (this.contact.surface.right && !this.contact.surface.previous.right) { | |
this.impact.surface.right = other; | |
} | |
}, | |
// Reset the collision state | |
reset: function(dt) { | |
this.contact.surface.previous.up = this.contact.surface.up; | |
this.contact.surface.previous.down = this.contact.surface.down; | |
this.contact.surface.previous.right = this.contact.surface.right; | |
this.contact.surface.previous.left = this.contact.surface.left; | |
this.contact.surface.area.up = 0; | |
this.contact.surface.area.down = 0; | |
this.contact.surface.area.right = 0; | |
this.contact.surface.area.left = 0; | |
this.contact.surface.up = null; | |
this.contact.surface.down = null; | |
this.contact.surface.right = null; | |
this.contact.surface.left = null; | |
this.contact.area.up = 0; | |
this.contact.area.down = 0; | |
this.contact.area.right = 0; | |
this.contact.area.left = 0; | |
this.impact.surface.up = null; | |
this.impact.surface.down = null; | |
this.impact.surface.right = null; | |
this.impact.surface.left = null; | |
this.inside.surface = null; | |
this.inside.area = 0; | |
}, | |
// Highly complicated sweeping logic -------------------------------------- | |
// ------------------------------------------------------------------------ | |
sweep: function(other, otherVel) { | |
var a = this, | |
b = other, | |
v = { | |
x: a.vel.x - b.vel.x, | |
y: a.vel.y - b.vel.y | |
}; | |
if (otherVel) { | |
v.x = v.x + otherVel.x; | |
v.y = v.y + otherVel.y; | |
} | |
var hitTime = 0, | |
outTime = 1, | |
outVel = { | |
x: 0, | |
y: 0 | |
}, | |
overlapsTime = { | |
x: 0, | |
y: 0 | |
}; | |
// invert v, since we're treating b as stationary here | |
v.x = -v.x; | |
v.y = -v.y; | |
// X axis overlap | |
if (v.x < 0) { | |
if (b.max.x < a.min.x) { | |
return false; | |
} | |
if (b.max.x > a.min.x) { | |
outTime = Math.min((a.min.x - b.max.x) / v.x, outTime); | |
} | |
if (a.max.x < b.min.x) { | |
overlapsTime.x = (a.max.x - b.min.x) / v.x; | |
hitTime = Math.max(overlapsTime.x, hitTime); | |
} | |
} else if (v.x > 0) { | |
if (b.min.x > a.max.x) { | |
return false; | |
} | |
if (a.max.x > b.min.x) { | |
outTime = Math.min((a.max.x - b.min.x) / v.x, outTime); | |
} | |
if (b.max.x < a.min.x) { | |
overlapsTime.x = (a.min.x - b.max.x) / v.x; | |
hitTime = Math.max(overlapsTime.x, hitTime); | |
} | |
} | |
if (hitTime > outTime) { | |
return false; | |
} | |
// Y axis overlap | |
if (v.y < 0) { | |
if (b.max.y < a.min.y) { | |
return false; | |
} | |
if (b.max.y > a.min.y) { | |
outTime = Math.min((a.min.y - b.max.y) / v.y, outTime); | |
} | |
if (a.max.y < b.min.y) { | |
overlapsTime.y = (a.max.y - b.min.y) / v.y; | |
hitTime = Math.max(overlapsTime.y, hitTime); | |
} | |
} else if (v.y > 0) { | |
if (b.min.y > a.max.y) { | |
return false; | |
} | |
if (a.max.y > b.min.y) { | |
outTime = Math.min((a.max.y - b.min.y) / v.y, outTime); | |
} | |
if (b.max.y < a.min.y) { | |
overlapsTime.y = (a.min.y - b.max.y) / v.y; | |
hitTime = Math.max(overlapsTime.y, hitTime); | |
} | |
} | |
if (hitTime > outTime) { | |
return false; | |
} | |
// the correction to the current velocity | |
outVel.x = (v.x - (v.x * hitTime)); | |
outVel.y = (v.y - (v.y * hitTime)); | |
// IMPORTANT | |
// since hitTime defaults to 0, everything that's | |
// somehwere along an axis will eventually collide at "0" | |
// thus, we bail out in case of <= 0 to prevent "ghosting" collisions | |
// BUT: In order to stop at objects, we need to NOT bail out in the case that we actually overlap | |
if ((hitTime <= 0 && !a.overlaps(b)) || hitTime > 1 || hitTime > outTime) { | |
return false; | |
} | |
// allow for sliding on surfaces | |
if (a.max.y - b.min.y === 0 || a.min.y - b.max.y === 0) { | |
outVel.x = 0; | |
} | |
if (a.max.x - b.min.x === 0 || a.min.x - b.max.x === 0) { | |
outVel.y = 0; | |
} | |
// TODO cleanup below with some teneries | |
var hitNormal = { | |
x: (outVel.x < 0) && 1 || ((outVel.x > 0) && -1 || 0), | |
y: (outVel.y < 0) && 1 || ((outVel.y > 0) && -1 || 0) | |
}; | |
// allow us to get away if (we're in contact but are moving into the opposite direction) | |
if (hitTime === 0) { | |
// return false in case we're trying to move away | |
if (v.x > 0 && a.max.x <= b.min.x) { | |
return false; | |
} else if (v.x < 0 && a.min.x >= b.max.x) { | |
return false; | |
} | |
if (v.y > 0 && a.max.y <= b.min.y) { | |
return false; | |
} else if (v.y < 0 && a.min.y >= b.max.y) { | |
return false; | |
} | |
// otherwise set the contact surface | |
var area = 0; | |
if (a.min.x < b.max.x && a.max.x > b.min.x) { | |
area = 0; | |
if (a.min.x >= b.min.x && a.max.x <= b.max.x) { | |
area = a.size.x; | |
} else if (a.min.x < b.min.x) { | |
area = a.max.x - b.min.x; | |
} else if (a.max.x > b.max.x) { | |
area = b.max.x - a.min.x; | |
} | |
area = Math.min(Math.min(a.size.x, b.size.x), area); | |
if (a.max.y === b.min.y && b._col.up) { | |
if (area > this.contact.surface.area.down) { | |
this.contact.surface.down = b; | |
this.contact.surface.area.down = area; | |
} | |
this.contact.area.down = this.contact.area.down + area; | |
} else if (a.min.y === b.max.y && b._col.down) { | |
if (area > this.contact.surface.area.up) { | |
this.contact.surface.up = b; | |
this.contact.surface.area.up = area; | |
} | |
this.contact.area.up = this.contact.area.up + area; | |
} | |
} | |
if (a.min.y < b.max.y && a.max.y > b.min.y) { | |
area = 0; | |
if (a.min.y >= b.min.y && a.max.y <= b.max.y) { | |
area = a.size.y; | |
} else if (a.min.y < b.min.y) { | |
area = a.max.y - b.min.y; | |
} else if (a.max.y > b.max.y) { | |
area = b.max.y - a.min.y; | |
} | |
area = Math.min(Math.min(a.size.y, b.size.y), area); | |
if (a.max.x === b.min.x && b._col.right) { | |
if (area > this.contact.surface.area.right) { | |
this.contact.surface.right = b; | |
this.contact.surface.area.right = area; | |
} | |
this.contact.area.right = this.contact.area.right + area; | |
} else if (a.min.x === b.max.x && b._col.left) { | |
if (area > this.contact.surface.area.left) { | |
this.contact.surface.left = b; | |
this.contact.surface.area.left = area; | |
} | |
this.contact.area.left = this.contact.area.left + area; | |
} | |
} | |
// in case we're stuck make sure push us out | |
var pushVel = { | |
x: 0, | |
y: 0 | |
}; | |
var acy = a.min.y + a.size.y / 2, | |
bcy = b.min.y + b.size.y / 2, | |
acx = a.min.x + a.size.x / 2, | |
bcx = b.min.x + b.size.x / 2; | |
if (Math.abs(acx - bcx) < (a.size.x + b.size.x) / 2 && Math.abs(acy - bcy) < (a.size.y + b.size.y) / 2) { | |
if (acy < bcy) { | |
pushVel.y = b.min.y - a.max.y; | |
} else { | |
pushVel.y = b.max.y - a.min.y; | |
} | |
if (acx < bcx) { | |
pushVel.x = b.min.x - a.max.x; | |
} else { | |
pushVel.x = b.max.x - a.min.x; | |
} | |
// handle non-blocking edges | |
if (pushVel.x < 0 && !b._col.right && v.x >= 0) { | |
pushVel.x = 0; | |
} else if (pushVel.x > 0 && !b._col.left && v.x <= 0) { | |
pushVel.x = 0; | |
} | |
if (pushVel.y < 0 && !b._col.down && v.y >= 0) { | |
pushVel.y = 0; | |
} else if (pushVel.y > 0 && !b._col.up && v.y <= 0) { | |
pushVel.y = 0; | |
} | |
// Correct possible floating point issues | |
if (pushVel.y > 0) { | |
pushVel.y = Math.max(1, pushVel.y); | |
} else if (pushVel.y < 0) { | |
pushVel.y = Math.min(-1, pushVel.y); | |
} | |
if (pushVel.x > 0) { | |
pushVel.x = Math.max(1, pushVel.x); | |
} else if (pushVel.x < 0) { | |
pushVel.x = Math.min(-1, pushVel.x); | |
} | |
// now choose the minimum / maximum of the push / out values | |
// to resolve the stuck state in the "best" possible way | |
if (pushVel.y !== 0 && Math.abs(pushVel.y) < Math.abs(pushVel.x)) { | |
outVel.y = outVel.y + pushVel.y; | |
} else if (pushVel.x !== 0 && Math.abs(pushVel.x) < Math.abs(pushVel.y)) { | |
outVel.x = outVel.x + pushVel.x; | |
} else { | |
outVel.y = outVel.y + pushVel.y; | |
outVel.x = outVel.x + pushVel.x; | |
} | |
} | |
} | |
// in case the block isn't blocking in specific directions+ | |
// reset the out velocity | |
// up / down | |
if (outVel.y > 0 && !b._col.down && v.y >= 0) { | |
outVel.y = 0; | |
} else if (!b._col.down && v.y >= 0) { | |
outVel.y = 0; | |
} else if (outVel.y < 0 && !b._col.up && v.y <= 0) { | |
outVel.y = 0; | |
} else if (!b._col.up && v.y <= 0) { | |
outVel.y = 0; | |
} | |
// left / right | |
if (outVel.x > 0 && !b._col.right && v.x >= 0) { | |
outVel.x = 0; | |
} else if (!b._col.right && v.x >= 0) { | |
outVel.x = 0; | |
} else if (outVel.x < 0 && !b._col.left && v.x >= 0) { | |
outVel.x = 0; | |
} else if (!b._col.left && v.x <= 0) { | |
outVel.x = 0; | |
} | |
if (a.max.y > b.min.y && !b._col.down) { | |
outVel.y = 0; | |
} | |
// final correction for ghosting artifacts | |
if (outVel.y !== 0 && !(a.min.x < b.max.x && a.max.x > b.min.x)) { | |
// only return in case x is also 0, otherwise we'll clip into walls | |
// in case of diagonal movement | |
if (outVel.x === 0) { | |
return false; | |
} else { | |
hitNormal.y = 0; | |
outVel.y = 0; | |
} | |
} | |
if (outVel.x !== 0 && !(a.min.y < b.max.y && a.max.y > b.min.y)) { | |
// only return in case x is also 0, otherwise we'll clip into floors | |
// in case of diagonal movement | |
if (outVel.y === 0) { | |
return false; | |
} else { | |
hitNormal.x = 0; | |
outVel.x = 0; | |
} | |
} | |
// keep track of the biggest overlapping rectangle | |
area = a.overlapArea(b); | |
if (area > a.insideArea) { | |
a.inside.area = area; | |
a.inside.surface = b; | |
} | |
return { | |
vel: outVel, | |
normal: hitNormal | |
}; | |
} | |
}); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment