Created
March 31, 2011 05:11
-
-
Save tyage/895853 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
window.Boku2D = (function() { | |
// RDC | |
var RDC = (function() { | |
var Entity = function (type, pos, obj) { | |
this.type = type; | |
this.pos = pos; | |
this.obj = obj; | |
}; | |
var RDC = function (collide) { | |
this.collide = collide; | |
}; | |
RDC.SUBDIVISION_THRESHOLD = 4; | |
RDC.CONTACT_THRESHOLD = 0.001; | |
RDC.prototype.bruteForce = function (group) { | |
for (var i=0,l=group.length; i<l; i++) { | |
for (var j=i+1; j<l; j++) { | |
this.collide(group[i], group[j]); | |
} | |
} | |
}; | |
RDC.prototype.recursiveClustering = function (group, axis1, axis2) { | |
if (axis1 === -1 || group.length < RDC.SUBDIVISION_THRESHOLD) { | |
this.bruteForce(group); | |
} else { | |
var boundaries = this.getOpenCloseBounds(group, axis1); | |
boundaries.sort(function (a, b) { | |
return a.pos - b.pos; | |
}); | |
var newAxis1 = axis2, | |
newAxis2 = -1, | |
groupSubdivided = false, | |
subgroup = [], | |
count = 0, | |
l = boundaries.length; | |
for (var i=0; i<l; i++) | |
{ | |
var b = boundaries[i]; | |
if (b.type === "open") { | |
count++; | |
subgroup.push(b.obj); | |
} else { | |
count--; | |
if (count === 0) { | |
if (i !== (l - 1)) { | |
groupSubdivided = true; | |
} | |
if (groupSubdivided) { | |
if (axis1 === 0) { | |
newAxis1 = 1; | |
} else if (axis1 === 1) { | |
newAxis1 = 0; | |
} | |
} | |
this.recursiveClustering(subgroup, newAxis1, newAxis2); | |
subgroup = []; | |
} | |
} | |
} | |
} | |
}; | |
RDC.prototype.getOpenCloseBounds = function (group, axis) { | |
var l = group.length, | |
boundaries = []; | |
switch(axis) | |
{ | |
case 0: | |
for (i=0; i<l; i++) { | |
o = group[i]; | |
boundaries.push(new Entity("open", o.center.x - o.size.x + RDC.CONTACT_THRESHOLD, o)); | |
boundaries.push(new Entity("close", o.center.x + o.size.x - RDC.CONTACT_THRESHOLD, o)); | |
} | |
break; | |
case 1: | |
for (i=0; i<l; i++) { | |
o = group[i]; | |
boundaries.push(new Entity("open", o.center.y - o.size.y + RDC.CONTACT_THRESHOLD, o)); | |
boundaries.push(new Entity("close", o.center.y + o.size.y - RDC.CONTACT_THRESHOLD, o)); | |
} | |
} | |
return boundaries; | |
}; | |
return RDC; | |
})(); | |
var IsNumber = function(num) { | |
return typeof num === "number"; | |
}; | |
var Extend = function(option) { | |
if (!option) { | |
return; | |
} | |
var key; | |
for (key in option) { | |
if (option.hasOwnProperty(key)) { | |
this[key] = option[key]; | |
} | |
} | |
}; | |
var Vec = function(x, y) { | |
this.x = (IsNumber(x) ? x : parseInt(x, 10)) || 0; | |
this.y = (IsNumber(y) ? y : parseInt(y, 10)) || 0; | |
}; | |
Vec.prototype = { | |
copy: function() { | |
return new Vec(this.x, this.y); | |
}, | |
add: function(v, y) { | |
if (y !== undefined) { | |
v = new Vec(v, y); | |
} | |
var x = this.x + v.x, | |
y = this.y + v.y; | |
return new Vec(x, y); | |
}, | |
subtract: function(v, y) { | |
if (y !== undefined) { | |
v = new Vec(v, y); | |
} | |
var x = this.x - v.x, | |
y = this.y - v.y; | |
return new Vec(x, y); | |
}, | |
multiply: function(i) { | |
var x = this.x * i, | |
y = this.y * i; | |
return new Vec(x, y); | |
}, | |
divide: function(i) { | |
var x = this.x / i, | |
y = this.y / i; | |
return new Vec(x, y); | |
}, | |
dot: function(v) { | |
return this.x * v.x + this.y * v.y; | |
}, | |
length: function() { | |
return Math.sqrt(this.x*this.x + this.y*this.y); | |
}, | |
normalize: function() { | |
var length = this.length(); | |
return this.divide(length); | |
} | |
}; | |
var SPRING = 10; | |
var DAMP = 1; | |
var Manifold = function(object, opponent) { | |
this.object = object; | |
this.opponent = opponent; | |
this.normal = new Vec(); | |
}; | |
Manifold.prototype = { | |
solve: function() { | |
var move = this.object, | |
fixed = this.opponent; | |
if (move.fixed) { | |
return false; | |
} | |
// 衝突ソルバー | |
var diff, | |
relSpeed = move.speed.subtract(fixed.speed), | |
adj = 0; | |
this.normal = new Vec(); | |
if (this.direction.x < 0) { | |
diff = move.maxPos().x - fixed.minPos().x; | |
adj = SPRING * diff + DAMP * relSpeed.x; | |
this.normal = this.normal.add(-adj, 0); | |
} else if (this.direction.x > 0) { | |
diff = fixed.maxPos().x - move.minPos().x; | |
adj = SPRING * diff - DAMP * relSpeed.x; | |
this.normal = this.normal.add(adj, 0); | |
} | |
if (this.direction.y < 0) { | |
diff = move.maxPos().y - fixed.minPos().y; | |
adj = SPRING * diff + DAMP * relSpeed.y; | |
this.normal = this.normal.add(0, -adj); | |
} else if (this.direction.y > 0) { | |
diff = fixed.maxPos().y - move.minPos().y; | |
adj = SPRING * diff - DAMP * relSpeed.y; | |
this.normal = this.normal.add(0, adj); | |
} | |
move.applyForce(this.normal); | |
// 摩擦 | |
var n = this.normal.length(), | |
friction = Math.sqrt(move.friction*fixed.friction), | |
force = new Vec(n, n).multiply(friction); | |
if (force.x > Math.abs(move.force.x)) { | |
force.x = Math.abs(move.force.x); | |
} | |
if (force.y > Math.abs(move.force.y)) { | |
force.y = Math.abs(move.force.y); | |
} | |
if (this.direction.y !== 0) { | |
if (relSpeed.x > 0) { | |
force.x = -force.x; | |
} | |
} else { | |
force.x = 0; | |
} | |
if (this.direction.x !== 0) { | |
if (relSpeed.y > 0) { | |
force.y = -force.y; | |
} | |
} else { | |
force.y = 0; | |
} | |
move.applyForce(force); | |
!fixed.fixed && fixed.applyForce(force.multiply(-1)); | |
} | |
}; | |
var Contact = function(object1, object2) { | |
var direction1 = new Vec(), | |
direction2 = new Vec(); | |
// 前回の位置から衝突方向を調べる | |
if (object1.maxTmpPos().x <= object2.minTmpPos().x && | |
object1.maxPos().x >= object2.minPos().x) { | |
direction1 = direction1.add(-1, 0); | |
} else if (object1.minTmpPos().x >= object2.maxTmpPos().x && | |
object1.minPos().x <= object2.maxPos().x) { | |
direction1 = direction1.add(1, 0); | |
} | |
if (object1.maxTmpPos().y <= object2.minTmpPos().y && | |
object1.maxPos().y >= object2.minPos().y) { | |
direction1 = direction1.add(0, -1); | |
} else if (object1.minTmpPos().y >= object2.maxTmpPos().y && | |
object1.minPos().y <= object2.maxPos().y) { | |
direction1 = direction1.add(0, 1); | |
} | |
if (direction1.x === 0 && direction1.y === 0) { | |
// 最小分離距離を計る | |
var normals = [], | |
d, | |
normal; | |
if (object2.minPos().x <= object1.minPos().x && | |
object1.minPos().x <= object2.maxPos().x) { | |
// 左衝突 | |
d = (object2.maxPos().x - object1.minPos().x)/2; | |
normal = new Vec(d, 0); | |
normal.direction = new Vec(1, 0); | |
normals.push(normal); | |
} else if (object2.minPos().x <= object1.maxPos().x && | |
object1.maxPos().x <= object2.maxPos().x) { | |
// 右衝突 | |
d = (object2.minPos().x - object1.maxPos().x)/2; | |
normal = new Vec(d, 0); | |
normal.direction = new Vec(-1, 0); | |
normals.push(normal); | |
} | |
if (object2.minPos().y <= object1.minPos().y && | |
object1.minPos().y <= object2.maxPos().y) { | |
// 上衝突 | |
d = (object2.maxPos().y - object1.minPos().y)/2; | |
normal = new Vec(0, d); | |
normal.direction = new Vec(0, 1); | |
normals.push(normal); | |
} else if (object2.minPos().y <= object1.maxPos().y && | |
object1.maxPos().y <= object2.maxPos().y) { | |
// 下衝突 | |
d = (object2.minPos().y - object1.maxPos().y)/2; | |
normal = new Vec(0, d); | |
normal.direction = new Vec(0, -1); | |
normals.push(normal); | |
} | |
normal = normals[0]; | |
for (var i=0,l=normals.length;i<l;i++) { | |
normal = normal.length() > normals[i].length() ? | |
normals[i] : | |
normal; | |
} | |
direction1 = normal.direction; | |
} | |
direction2 = direction1.multiply(-1); | |
var manifold1 = new Manifold(object1, object2), | |
manifold2 = new Manifold(object2, object1); | |
manifold1.direction = direction1; | |
manifold2.direction = direction2; | |
this.manifolds = [manifold1, manifold2]; | |
this.timeStep = 0; | |
this.world = object1.world; | |
this.object1 = object1; | |
this.object2 = object2; | |
this.update(); | |
var manifolds = this.manifolds; | |
for (var i=0,l=manifolds.length;i<l;i++) { | |
var manifold = manifolds[i]; | |
manifold.object.createContact(this, manifold); | |
} | |
}; | |
Contact.prototype = { | |
update: function() { | |
this.timeStep = this.world.timeStep; | |
var manifolds = this.manifolds; | |
for (var i=0,l=manifolds.length;i<l;i++) { | |
var manifold = manifolds[i]; | |
manifold.object.updateContact(this, manifold); | |
} | |
}, | |
solve: function() { | |
var manifolds = this.manifolds; | |
for (var i=0,l=manifolds.length;i<l;i++) { | |
var manifold = manifolds[i]; | |
manifold.object.beforeSolve(this, manifold); | |
manifold.solve(); | |
manifold.object.afterSolve(this, manifold); | |
} | |
}, | |
destroy: function() { | |
this._removeFromList(this.world.contacts); | |
this._removeFromList(this.object1.contacts); | |
this._removeFromList(this.object2.contacts); | |
var manifolds = this.manifolds; | |
for (var i=0,l=manifolds.length;i<l;i++) { | |
var manifold = manifolds[i]; | |
manifold.object.destroyContact(this, manifold); | |
} | |
}, | |
_removeFromList: function(contacts) { | |
for (var i=0,l=contacts.length;i<l;i++) { | |
if (contacts[i] == this) { | |
contacts.splice(i, 1); | |
} | |
} | |
} | |
}; | |
var Collide = function(object1, object2) { | |
if (!CheckContact(object1, object2)) { | |
return; | |
} | |
var list = object1.contacts, | |
contact = null; | |
for (var i=0,l=list.length;i<l;i++) { | |
var c = list[i]; | |
if (c.manifolds[0].opponent === object2 || | |
c.manifolds[1].opponent === object2) { | |
contact = c; | |
break; | |
} | |
} | |
if (contact) { | |
contact.update(); | |
} else { | |
contact = new Contact(object1, object2); | |
object1.contacts.push(contact); | |
object2.contacts.push(contact); | |
object1.world.contacts.push(contact); | |
} | |
}; | |
var CheckContact = function(object1, object2) { | |
return object1.maxPos().x >= object2.minPos().x && | |
object1.minPos().x <= object2.maxPos().x && | |
object1.maxPos().y >= object2.minPos().y && | |
object1.minPos().y <= object2.maxPos().y; | |
}; | |
var Model = function() { | |
var newObject = function(option) { | |
this._init(option); | |
}; | |
/* | |
object <- object.prototype | |
<- object.prototype.prototype( = Model.prototype) | |
*/ | |
var newModel = function() {}; | |
newModel.prototype = Model.defaults; | |
newObject.prototype = new newModel(); | |
return newObject; | |
}; | |
Model.defaults = { | |
speed: new Vec(), | |
force: new Vec(), | |
accel: new Vec(), | |
center: new Vec(), | |
centerTmp: new Vec(), | |
size: new Vec(0, 0), | |
gravity: new Vec(0, 10), | |
weight: 1, // 質量 | |
restitution: 0.7, // 反発係数 | |
friction: 0.1, // 摩擦係数 | |
fixed: false, | |
contacts: [], | |
init: function() {}, | |
beforeStep: function(time) {}, | |
afterStep: function(time) {}, | |
createContact: function(contact, manifold) {}, | |
destroyContact: function(contact, manifold) {}, | |
updateContact: function(contact, manifold) {}, | |
beforeSolve: function(contact, manifold) {}, | |
afterSolve: function(contact, manifold) {}, | |
_init: function(option) { | |
this.speed = this.speed.copy(); | |
this.accel = this.accel.copy(); | |
this.center = this.center.copy(); | |
this.centerTmp = this.center.copy(); | |
this.size = this.size.copy(); | |
this.gravity = this.gravity.copy(); | |
this.contacts = []; | |
Extend.call(this, option); | |
this.init(option); | |
}, | |
_move: function(time) { | |
this.centerTmp = this.center.copy(); | |
this.center = this.center.add(this.speed.multiply(time)); | |
}, | |
_step: function(time) { | |
this.beforeStep(time); | |
this.applyForce(this.gravity.multiply(this.weight)); | |
this.speed = this.speed.add(this.accel.multiply(time)); | |
this._move(time); | |
this.afterStep(time); | |
this.resetForce(); | |
}, | |
maxTmpPos: function() { | |
return this.centerTmp.add(this.size.divide(2)); | |
}, | |
minTmpPos: function() { | |
return this.centerTmp.subtract(this.size.divide(2)); | |
}, | |
maxPos: function() { | |
return this.center.add(this.size.divide(2)); | |
}, | |
minPos: function() { | |
return this.center.subtract(this.size.divide(2)); | |
}, | |
applyForce: function(force) { | |
this.force = this.force.add(force); | |
this.accel = this.accel.add(force.divide(this.weight)); | |
}, | |
resetForce: function() { | |
this.accel = new Vec(); | |
} | |
}; | |
var Rdc = new RDC(Collide); | |
var World = function(option) { | |
this.objects = []; | |
this.size = new Vec(0, 0); | |
this.timeStep = 0; | |
this.contacts = []; | |
Extend.call(this, option); | |
this.init(option); | |
}; | |
World.prototype = { | |
step: function(time) { | |
this.beforeStep(time); | |
this.timeStep += time; | |
// 衝突情報取得 | |
Rdc.recursiveClustering(this.objects, 0, 1); | |
// 衝突処理 | |
var contacts = this.contacts; | |
for (var i=0;i<contacts.length;i++) { | |
var contact = contacts[i]; | |
if (contact.timeStep !== this.timeStep) { | |
contact.destroy(); | |
i--; | |
} else { | |
contact.solve(); | |
} | |
} | |
// 各アイテムの進行 | |
for (var i=0,l=this.objects.length;i<l;i++) { | |
var object = this.objects[i]; | |
object._step(time); | |
} | |
this.afterStep(time); | |
return this; | |
}, | |
createObject: function(object) { | |
object.world = this; | |
this.objects.push(object); | |
}, | |
destroyObject: function(object) { | |
for (var i=0,l=this.objects.length;i<l;i++) { | |
if (this.objects[i] === object) { | |
this.objects.splice(i, 1); | |
} | |
} | |
delete object; | |
}, | |
init: function(option) {}, | |
beforeStep: function(time) {}, | |
afterStep: function(time) {} | |
}; | |
return { | |
Vec: Vec, | |
World: World, | |
Model: Model | |
}; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment