/* Copyright (c) 2007 Scott Lembcke
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/// **** Sleeping Functions
/**
* @function
* @param {cp.Body} body
*/
Space.prototype.activateBody = function(body)
{
assert(!body.isRogue(), "Internal error: Attempting to activate a rogue body.");
if(this.locked){
// cpSpaceActivateBody() is called again once the space is unlocked
if(this.rousedBodies.indexOf(body) === -1) this.rousedBodies.push(body);
} else {
this.bodies.push(body);
for(var i = 0; i < body.shapeList.length; i++){
var shape = body.shapeList[i];
this.staticShapes.remove(shape, shape.hashid);
this.activeShapes.insert(shape, shape.hashid);
}
for(var arb = body.arbiterList; arb; arb = arb.next(body)){
var bodyA = arb.body_a;
if(body === bodyA || bodyA.isStatic()){
//var contacts = arb.contacts;
// Restore contact values back to the space's contact buffer memory
//arb.contacts = cpContactBufferGetArray(this);
//memcpy(arb.contacts, contacts, numContacts*sizeof(cpContact));
//cpSpacePushContacts(this, numContacts);
// Reinsert the arbiter into the arbiter cache
var a = arb.a, b = arb.b;
this.cachedArbiters[hashPair(a.hashid, b.hashid)] = arb;
// Update the arbiter's state
arb.stamp = this.stamp;
arb.handler = this.lookupHandler(a.collision_type, b.collision_type);
this.arbiters.push(arb);
}
}
for(var constraint = body.constraintList; constraint; constraint = constraint.nodeNext){
var bodyA = constraint.a;
if(body === bodyA || bodyA.isStatic()) this.constraints.push(constraint);
}
}
};
/**
* @function
* @param {cp.Body} body
*/
Space.prototype.deactivateBody = function(body)
{
assert(!body.isRogue(), "Internal error: Attempting to deactivate a rogue body.");
deleteObjFromList(this.bodies, body);
for(var i = 0; i < body.shapeList.length; i++){
var shape = body.shapeList[i];
this.activeShapes.remove(shape, shape.hashid);
this.staticShapes.insert(shape, shape.hashid);
}
for(var arb = body.arbiterList; arb; arb = arb.next(body)){
var bodyA = arb.body_a;
if(body === bodyA || bodyA.isStatic()){
this.uncacheArbiter(arb);
// Save contact values to a new block of memory so they won't time out
//size_t bytes = arb.numContacts*sizeof(cpContact);
//cpContact *contacts = (cpContact *)cpcalloc(1, bytes);
//memcpy(contacts, arb.contacts, bytes);
//arb.contacts = contacts;
}
}
for(var constraint = body.constraintList; constraint; constraint = constraint.nodeNext){
var bodyA = constraint.a;
if(body === bodyA || bodyA.isStatic()) deleteObjFromList(this.constraints, constraint);
}
};
/**
* @function
* @param {cp.Body} body
* @return {cp.Body}
*/
var componentRoot = function(body)
{
return (body ? body.nodeRoot : null);
};
/**
* @function
* @param {cp.Body} root
*/
var componentActivate = function(root)
{
if(!root || !root.isSleeping(root)) return;
assert(!root.isRogue(), "Internal Error: componentActivate() called on a rogue body.");
var space = root.space;
var body = root;
while(body){
var next = body.nodeNext;
body.nodeIdleTime = 0;
body.nodeRoot = null;
body.nodeNext = null;
space.activateBody(body);
body = next;
}
deleteObjFromList(space.sleepingComponents, root);
};
/**
* @function
*/
Body.prototype.activate = function()
{
if(!this.isRogue()){
this.nodeIdleTime = 0;
componentActivate(componentRoot(this));
}
};
/**
* @function
* @param {cp.Shape} filter
*/
Body.prototype.activateStatic = function(filter)
{
assert(this.isStatic(), "Body.activateStatic() called on a non-static body.");
for(var arb = this.arbiterList; arb; arb = arb.next(this)){
if(!filter || filter == arb.a || filter == arb.b){
(arb.body_a == this ? arb.body_b : arb.body_a).activate();
}
}
// TODO should also activate joints!
};
/**
* @function
* @param {Arbiter} arb
*/
Body.prototype.pushArbiter = function(arb)
{
assertSoft((arb.body_a === this ? arb.thread_a_next : arb.thread_b_next) === null,
"Internal Error: Dangling contact graph pointers detected. (A)");
assertSoft((arb.body_a === this ? arb.thread_a_prev : arb.thread_b_prev) === null,
"Internal Error: Dangling contact graph pointers detected. (B)");
var next = this.arbiterList;
assertSoft(next === null || (next.body_a === this ? next.thread_a_prev : next.thread_b_prev) === null,
"Internal Error: Dangling contact graph pointers detected. (C)");
if(arb.body_a === this){
arb.thread_a_next = next;
} else {
arb.thread_b_next = next;
}
if(next){
if (next.body_a === this){
next.thread_a_prev = arb;
} else {
next.thread_b_prev = arb;
}
}
this.arbiterList = arb;
};
/**
* @function
* @param {cp.Body} root
* @param {cp.Body} body
*/
var componentAdd = function(root, body){
body.nodeRoot = root;
if(body !== root){
body.nodeNext = root.nodeNext;
root.nodeNext = body;
}
};
/**
* @function
* @param {cp.Body} root
* @param {cp.Body} body
*/
var floodFillComponent = function(root, body)
{
// Rogue bodies cannot be put to sleep and prevent bodies they are touching from sleeping anyway.
// Static bodies (which are a type of rogue body) are effectively sleeping all the time.
if(!body.isRogue()){
var other_root = componentRoot(body);
if(other_root == null){
componentAdd(root, body);
for(var arb = body.arbiterList; arb; arb = arb.next(body)){
floodFillComponent(root, (body == arb.body_a ? arb.body_b : arb.body_a));
}
for(var constraint = body.constraintList; constraint; constraint = constraint.next(body)){
floodFillComponent(root, (body == constraint.a ? constraint.b : constraint.a));
}
} else {
assertSoft(other_root === root, "Internal Error: Inconsistency detected in the contact graph.");
}
}
};
/**
* @function
* @param {cp.Body} root
* @param {number} threshold
* @return {boolean}
*/
var componentActive = function(root, threshold)
{
for(var body = root; body; body = body.nodeNext){
if(body.nodeIdleTime < threshold) return true;
}
return false;
};
/**
* @function
* @param {number} dt
*/
Space.prototype.processComponents = function(dt)
{
var sleep = (this.sleepTimeThreshold !== Infinity);
var bodies = this.bodies;
// These checks can be removed at some stage (if DEBUG == undefined)
for(var i=0; i<bodies.length; i++){
var body = bodies[i];
assertSoft(body.nodeNext === null, "Internal Error: Dangling next pointer detected in contact graph.");
assertSoft(body.nodeRoot === null, "Internal Error: Dangling root pointer detected in contact graph.");
}
// Calculate the kinetic energy of all the bodies
if(sleep){
var dv = this.idleSpeedThreshold;
var dvsq = (dv ? dv*dv : vlengthsq(this.gravity)*dt*dt);
for(var i=0; i<bodies.length; i++){
var body = bodies[i];
// Need to deal with infinite mass objects
var keThreshold = (dvsq ? body.m*dvsq : 0);
body.nodeIdleTime = (body.kineticEnergy() > keThreshold ? 0 : body.nodeIdleTime + dt);
}
}
// Awaken any sleeping bodies found and then push arbiters to the bodies' lists.
var arbiters = this.arbiters;
for(var i=0, count=arbiters.length; i<count; i++){
var arb = arbiters[i];
var a = arb.body_a, b = arb.body_b;
if(sleep){
if((b.isRogue() && !b.isStatic()) || a.isSleeping()) a.activate();
if((a.isRogue() && !a.isStatic()) || b.isSleeping()) b.activate();
}
a.pushArbiter(arb);
b.pushArbiter(arb);
}
if(sleep){
// Bodies should be held active if connected by a joint to a non-static rouge body.
var constraints = this.constraints;
for(var i=0; i<constraints.length; i++){
var constraint = constraints[i];
var a = constraint.a, b = constraint.b;
if(b.isRogue() && !b.isStatic()) a.activate();
if(a.isRogue() && !a.isStatic()) b.activate();
}
// Generate components and deactivate sleeping ones
for(var i=0; i<bodies.length;){
var body = bodies[i];
if(componentRoot(body) === null){
// Body not in a component yet. Perform a DFS to flood fill mark
// the component in the contact graph using this body as the root.
floodFillComponent(body, body);
// Check if the component should be put to sleep.
if(!componentActive(body, this.sleepTimeThreshold)){
this.sleepingComponents.push(body);
for(var other = body; other; other = other.nodeNext){
this.deactivateBody(other);
}
// deactivateBody() removed the current body from the list.
// Skip incrementing the index counter.
continue;
}
}
i++;
// Only sleeping bodies retain their component node pointers.
body.nodeRoot = null;
body.nodeNext = null;
}
}
};
/**
* @function
*/
Body.prototype.sleep = function()
{
this.sleepWithGroup(null);
};
/**
* @function
* @param {cp.Body} group
*/
Body.prototype.sleepWithGroup = function(group){
assert(!this.isStatic() && !this.isRogue(), "Rogue and static bodies cannot be put to sleep.");
var space = this.space;
assert(space, "Cannot put a rogue body to sleep.");
assert(!space.locked, "Bodies cannot be put to sleep during a query or a call to cpSpaceStep(). Put these calls into a post-step callback.");
assert(group === null || group.isSleeping(), "Cannot use a non-sleeping body as a group identifier.");
if(this.isSleeping()){
assert(componentRoot(this) === componentRoot(group), "The body is already sleeping and it's group cannot be reassigned.");
return;
}
for(var i = 0; i < this.shapeList.length; i++){
this.shapeList[i].update(this.p, this.rot);
}
space.deactivateBody(this);
if(group){
var root = componentRoot(group);
this.nodeRoot = root;
this.nodeNext = root.nodeNext;
this.nodeIdleTime = 0;
root.nodeNext = this;
} else {
this.nodeRoot = this;
this.nodeNext = null;
this.nodeIdleTime = 0;
space.sleepingComponents.push(this);
}
deleteObjFromList(space.bodies, this);
};
/**
* @function
* @param {cp.Shape} shape
*/
Space.prototype.activateShapesTouchingShape = function(shape){
if(this.sleepTimeThreshold !== Infinity){
this.shapeQuery(shape, function(shape, points) {
shape.body.activate();
});
}
};