Created
March 31, 2011 06:40
-
-
Save tyage/895920 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 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 friction = Math.sqrt(move.friction * fixed.friction); | |
var spring = Math.sqrt(move.elastic * fixed.elastic); | |
var damp = Math.sqrt(move.viscosity * fixed.viscosity); | |
// var relSpeed = move.speed.subtract(fixed.speed); | |
var relSpeed = { | |
x: move.speed.x - fixed.speed.x, | |
y: move.speed.y - fixed.speed.y | |
}; | |
// 衝突ソルバー | |
// this.normal = new Vec(); | |
this.normal.x = 0; | |
this.normal.y = 0; | |
if (this.direction.x < 0) { | |
var diff = move.maxPos().x - fixed.minPos().x; | |
var adj = spring * diff + damp * relSpeed.x; | |
// this.normal = this.normal.add(-adj, 0); | |
this.normal.x += -adj; | |
} else if (this.direction.x > 0) { | |
var diff = fixed.maxPos().x - move.minPos().x; | |
var adj = spring * diff - damp * relSpeed.x; | |
// this.normal = this.normal.add(adj, 0); | |
this.normal.x += adj; | |
} | |
if (this.direction.y < 0) { | |
var diff = move.maxPos().y - fixed.minPos().y; | |
var adj = spring * diff + damp * relSpeed.y; | |
// this.normal = this.normal.add(0, -adj); | |
this.normal.y += -adj; | |
} else if (this.direction.y > 0) { | |
var diff = fixed.maxPos().y - move.minPos().y; | |
var adj = spring * diff - damp * relSpeed.y; | |
// this.normal = this.normal.add(0, adj); | |
this.normal.y += adj; | |
} | |
move.applyForce(this.normal); | |
// 摩擦 | |
var n = this.normal.length(), | |
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)); | |
!fixed.fixed && fixed.applyForce({ | |
x: force.x * -1, | |
y: force.y * -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); | |
direction1.x += -1; | |
} else if (object1.minTmpPos().x >= object2.maxTmpPos().x && | |
object1.minPos().x <= object2.maxPos().x) { | |
// direction1 = direction1.add(1, 0); | |
direction1.x += 1; | |
} | |
if (object1.maxTmpPos().y <= object2.minTmpPos().y && | |
object1.maxPos().y >= object2.minPos().y) { | |
// direction1 = direction1.add(0, -1); | |
direction1.y += -1; | |
} else if (object1.minTmpPos().y >= object2.maxTmpPos().y && | |
object1.minPos().y <= object2.maxPos().y) { | |
// direction1 = direction1.add(0, 1); | |
direction1.y += 1; | |
} | |
if (direction1.x === 0 && direction1.y === 0) { | |
// 最小分離距離を計る | |
var normals = []; | |
if (object2.minPos().x <= object1.minPos().x && | |
object1.minPos().x <= object2.maxPos().x) { | |
// 左衝突 | |
var d = (object2.maxPos().x - object1.minPos().x)/2; | |
// normal = new Vec(d, 0); | |
// normal.direction = new Vec(1, 0); | |
var normal = { | |
length: d, | |
direction: { | |
x: 1, | |
y: 0 | |
} | |
}; | |
normals.push(normal); | |
} else if (object2.minPos().x <= object1.maxPos().x && | |
object1.maxPos().x <= object2.maxPos().x) { | |
// 右衝突 | |
var d = (object2.minPos().x - object1.maxPos().x)/2; | |
// normal = new Vec(d, 0); | |
// normal.direction = new Vec(-1, 0); | |
var normal = { | |
length: d, | |
direction: { | |
x: -1, | |
y: 0 | |
} | |
}; | |
normals.push(normal); | |
} | |
if (object2.minPos().y <= object1.minPos().y && | |
object1.minPos().y <= object2.maxPos().y) { | |
// 上衝突 | |
var d = (object2.maxPos().y - object1.minPos().y)/2; | |
// normal = new Vec(0, d); | |
// normal.direction = new Vec(0, 1); | |
var normal = { | |
length: d, | |
direction: { | |
x: 0, | |
y: 1 | |
} | |
}; | |
normals.push(normal); | |
} else if (object2.minPos().y <= object1.maxPos().y && | |
object1.maxPos().y <= object2.maxPos().y) { | |
// 下衝突 | |
var d = (object2.minPos().y - object1.maxPos().y)/2; | |
// normal = new Vec(0, d); | |
// normal.direction = new Vec(0, -1); | |
var normal = { | |
length: d, | |
direction: { | |
x: 0, | |
y: -1 | |
} | |
}; | |
normals.push(normal); | |
} | |
var normal = normals[0]; | |
for (var i=0,l=normals.length;i<l;i++) { | |
// normal = normal.length() > normals[i].length() ? | |
normal = normal.length > normals[i].length ? | |
normals[i] : | |
normal; | |
} | |
// direction1 = normal.direction; | |
direction1.x = normal.direction.x; | |
direction1.y = normal.direction.y; | |
} | |
// direction2 = direction1.multiply(-1); | |
direction2.x = direction1.x * -1; | |
direction2.y = direction1.y * -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) { | |
delete contacts[i]; | |
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, // 質量 | |
elastic: 7, // 反発係数 → バネ定数 (侵入量が減る) | |
viscosity: 3, // 粘性係数 → ダンパ係数 (速度が落ちる) | |
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.centerTmp.x = this.center.x; | |
this.centerTmp.y = this.center.y; | |
//this.center = this.center.add(this.speed.multiply(time)); | |
this.center.x += this.speed.x * time; | |
this.center.y += this.speed.y * time; | |
}, | |
_step: function(time) { | |
this.beforeStep(time); | |
// this.applyForce(this.gravity.multiply(this.weight)); | |
this.applyForce({ | |
x: this.gravity.x * this.weight, | |
y: this.gravity.y * this.weight | |
}); | |
// this.speed = this.speed.add(this.accel.multiply(time)); | |
this.speed.x += this.accel.x * time; | |
this.speed.y += this.accel.y * time; | |
this._move(time); | |
this.afterStep(time); | |
this.resetForce(); | |
}, | |
maxTmpPos: function() { | |
// return this.centerTmp.add(this.size.divide(2)); | |
return { | |
x: this.centerTmp.x + this.size.x/2, | |
y: this.centerTmp.y + this.size.y/2 | |
}; | |
}, | |
minTmpPos: function() { | |
// return this.centerTmp.subtract(this.size.divide(2)); | |
return { | |
x: this.centerTmp.x - this.size.x/2, | |
y: this.centerTmp.y - this.size.y/2 | |
}; | |
}, | |
maxPos: function() { | |
// return this.center.add(this.size.divide(2)); | |
return { | |
x: this.center.x + this.size.x/2, | |
y: this.center.y + this.size.y/2 | |
}; | |
}, | |
minPos: function() { | |
// return this.center.subtract(this.size.divide(2)); | |
return { | |
x: this.center.x - this.size.x/2, | |
y: this.center.y - this.size.y/2 | |
}; | |
}, | |
applyForce: function(force) { | |
// this.force = this.force.add(force); | |
this.force.x += force.x; | |
this.force.y += force.y; | |
// this.accel = this.accel.add(force.divide(this.weight)); | |
this.accel.x += force.x/this.weight; | |
this.accel.y += force.y/this.weight; | |
}, | |
resetForce: function() { | |
// this.accel = new Vec(); | |
this.accel.x = 0; | |
this.accel.y = 0; | |
} | |
}; | |
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=object.contacts.length;i<l;i++) { | |
var contact = object.contacts[i]; | |
if (contact) { | |
contact.destroy(); | |
} | |
} | |
for (i=0,l=this.objects.length;i<l;i++) { | |
if (this.objects[i] === object) { | |
delete this.objects[i]; | |
this.objects.splice(i, 1); | |
} | |
} | |
}, | |
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