Tekkotsu Homepage
Demos
Overview
Downloads
Dev. Resources
Reference
Credits

PhysicsBody.cc

Go to the documentation of this file.
00001 #ifdef HAVE_BULLET
00002 
00003 #include "PhysicsBody.h"
00004 #include "PhysicsWorld.h"
00005 #include "MotorControllers.h"
00006 #include "Shared/ReferenceCounter.h"
00007 #include "Shared/debuget.h"
00008 
00009 #ifdef HAVE_OGRE
00010 #  ifdef __APPLE__
00011 #    include <Ogre/OgreMesh.h>
00012 #    include <Ogre/OgreSubMesh.h>
00013 #  else
00014 #    include <OGRE/OgreMesh.h>
00015 #    include <OGRE/OgreSubMesh.h>
00016 #  endif
00017 #endif
00018 
00019 #ifdef __APPLE__
00020 #include <BulletCollision/btBulletCollisionCommon.h>
00021 #include <BulletDynamics/btBulletDynamicsCommon.h>
00022 #else
00023 #include <btBulletCollisionCommon.h>
00024 #include <btBulletDynamicsCommon.h>
00025 #endif
00026 
00027 #include <stdexcept>
00028 
00029 using namespace std; 
00030 
00031 std::ostream& operator<<(std::ostream& os, const btTransform& t) {
00032   fmat::Matrix<4,4> ft;
00033   for(unsigned int i=0; i<3; ++i)
00034     for(unsigned int j=0; j<3; ++j)
00035       ft(i,j) = t.getBasis()[i][j];
00036   (fmat::SubVector<3>)ft.column(3) = t.getOrigin();
00037   ft(3,3)=1;
00038   return os << ft;
00039 }
00040 
00041 std::ostream& operator<<(std::ostream& os, const btVector3& v) {
00042   return os << fmat::SubVector<3>(const_cast<float*>(&v[0]));
00043 }
00044 
00045 
00046 struct CollisionData : public ReferenceCounter {
00047 #ifdef HAVE_OGRE
00048   CollisionData(const std::string& meshName, Ogre::MeshPtr mesh);
00049 #endif
00050   ~CollisionData() {
00051     delete vertices; vertices=NULL;
00052     delete indices; indices=NULL;
00053     PhysicsBody::collisionData.erase(name);
00054   }
00055   std::string name;
00056   float * vertices;
00057   unsigned int * indices;
00058 };
00059 
00060 std::map<std::string,CollisionData*> PhysicsBody::collisionData;
00061 
00062 
00063 PhysicsBody::~PhysicsBody() {
00064   //std::cout << "Destructing " << getPath() << std::endl;
00065   // unregister with parent
00066   if(parentBody!=NULL)
00067     parentBody->children.erase(this);
00068   // give children to parent
00069   for(std::set<PhysicsBody*>::const_iterator it=children.begin(); it!=children.end(); ++it) {
00070     (*it)->parentBody = parentBody;
00071     if(parentBody!=NULL)
00072       parentBody->children.insert(*it);
00073   }
00074   ASSERT(children.size()==0 || parentBody!=NULL, "deleting PhysicsBody with children but no parent");
00075   children.clear();
00076   clear();
00077   parentBody=NULL;
00078   std::map<LinkComponent*, PhysicsBody*>::iterator it = world.bodies.find(&link);
00079   if(it==world.bodies.end()) {
00080     std::cerr << "WARNING PhysicsBody: body was not found in world for removal" << std::endl;
00081   } else {
00082     world.bodies.erase(it);
00083   }
00084 }
00085 
00086 std::string PhysicsBody::getPath() const {
00087   stringstream ss;
00088   if(const KinematicJoint* kj = dynamic_cast<const KinematicJoint*>(&link)) {
00089     ss << kj->getPath() << ":" << getModel();
00090   } else {
00091     ss << "Free-" << getModel();
00092   }
00093   ss << "@" << &link;
00094   return ss.str();
00095 }
00096 
00097 void PhysicsBody::getCurrent(fmat::Column<3>& pos, fmat::Quaternion& quat) const {
00098   ASSERTRET(body!=NULL,"PhysicsData::getCurrent called with NULL body");
00099   //std::cout << " Get current " << kj.outputOffset.get() << ":\n" << body->getWorldTransform() << '\n' << centerOffset << std::endl;
00100   const btTransform& t = body->getWorldTransform();
00101   //std::cout << getPath() <<'\n'<< t << std::endl;
00102   pos.importFrom(t * -centerOffset / world.spaceScale);
00103   //std::cout << fmat::transpose(fmat::SubMatrix<4,3>(t.getBasis()[0])) << std::endl;
00104   btQuaternion q = t.getRotation();
00105   quat = fmat::Quaternion(q.w(),q.x(),q.y(),q.z());
00106 }
00107 
00108 bool PhysicsBody::hasJoint() const { return jointInterface!=NULL; }
00109 
00110 float PhysicsBody::getJoint() const { return jointInterface->getValue(0); }
00111 
00112 void PhysicsBody::teleport(const fmat::Column<3>& pos) {
00113   btTransform cur = body->getWorldTransform();
00114   float sc = world.spaceScale;
00115   cur.setOrigin(btVector3(pos[0]*sc,pos[1]*sc,pos[2]*sc));
00116   teleport(cur);
00117 }
00118 
00119 void PhysicsBody::teleport(const fmat::Quaternion& quat) {
00120   btTransform cur = body->getWorldTransform();
00121   cur.setOrigin(cur.getOrigin() - quatRotate(cur.getRotation(),centerOffset));
00122   cur.setRotation(btQuaternion(quat.getX(),quat.getY(),quat.getZ(),quat.getW()));
00123   teleport(cur);
00124 }
00125 
00126 void PhysicsBody::teleport(const fmat::Column<3>& pos, const fmat::Quaternion& quat) {
00127   float sc = world.spaceScale;
00128   btTransform cur(btQuaternion(quat.getX(),quat.getY(),quat.getZ(),quat.getW()),btVector3(pos[0]*sc,pos[1]*sc,pos[2]*sc));
00129   teleport(cur);
00130 }
00131 
00132 void PhysicsBody::teleport(const btTransform& tr) {
00133   std::vector<std::pair<PhysicsBody*,btTransform> > cts; // will child transforms relative to current to reset at end
00134   //const btTransform mt = body->getWorldTransform().inverse(); // my transform, inverted
00135   for(std::set<PhysicsBody*>::const_iterator it=children.begin(); it!=children.end(); ++it) {
00136     btTransform cur = (*it)->body->getWorldTransform();
00137     cur.setOrigin(cur.getOrigin() - quatRotate(cur.getRotation(),(*it)->centerOffset));
00138     cts.push_back(std::make_pair(*it,cur));
00139   }
00140   clear();
00141   lastTransform = tr;
00142   build();
00143   for(std::vector<std::pair<PhysicsBody*,btTransform> >::const_iterator it=cts.begin(); it!=cts.end(); ++it)
00144     it->first->teleport(it->second);
00145 }
00146 
00147 void PhysicsBody::setVelocity(const fmat::Column<3>& linear, const fmat::Column<3>& angular) {
00148   float sc = world.spaceScale;
00149   setVelocity(btVector3(linear[0]*sc,linear[1]*sc,linear[2]*sc), btVector3(angular[0],angular[1],angular[2]));
00150 }
00151 void PhysicsBody::setVelocity(const btVector3& linear, const btVector3& angular) {
00152   assert(!isnan(linear[0]));
00153   assert(!isnan(angular[0]));
00154   // these values are at link origin, but bullet applies these at the center of mass, so need to offset linear velocity by radius of center of mass
00155   const btTransform& t = body->getWorldTransform();
00156   //std::cout << getPath() << " curvel " << body->getLinearVelocity() << " setvel " << linear << " + " << angular.cross(t.getBasis() * centerOffset) << std::endl;
00157   body->activate();
00158   body->setLinearVelocity(linear + angular.cross(t.getBasis() * centerOffset));
00159   body->setAngularVelocity(angular);
00160   for(std::set<PhysicsBody*>::const_iterator it=children.begin(); it!=children.end(); ++it) {
00161     // similarly offset linear velocity for children
00162     //const btTransform& ct = (*it)->body->getWorldTransform();
00163     (*it)->setVelocity(linear,angular); // + angular.cross(ct.getOrigin() - t.getOrigin())
00164   }
00165 }
00166 
00167 void PhysicsBody::init() {
00168   //std::cout << "Creating PhysicsBody for " << getPath() << std::endl;
00169   if(parentBody!=NULL) {
00170     // see if parent has any child bodies that are actually this body's children
00171     for(std::set<PhysicsBody*>::const_iterator it=parentBody->children.begin(); it!=parentBody->children.end(); ++it) {
00172       if(isChild(link,(*it)->link)) {
00173         children.insert(*it);
00174       }
00175     }
00176     // steal our children back from parent
00177     for(std::set<PhysicsBody*>::const_iterator it=children.begin(); it!=children.end(); ++it) {
00178       (*it)->parentBody=this;
00179       parentBody->children.erase(*it);
00180     }
00181     // register with parent
00182     parentBody->children.insert(this);
00183   }
00184   ASSERTIF(world.bodies.find(&link)==world.bodies.end(),"WARNING PhysicsBody: body already added to world!") {
00185     world.bodies[&link] = this;
00186   }
00187   build();
00188 }
00189 
00190 void PhysicsBody::build() {
00191   //std::cout << "Building PhysicsBody for " << getPath() << std::endl;
00192   if(body==NULL) {
00193     ASSERT(compound==NULL,"have compound for collision shape but no body?");
00194     compound = new btCompoundShape;
00195     //compound->setMargin(0);  // we'll set margins on the individual sub-shapes, compound defaults to 0 margin anyway
00196   } else {
00197     ASSERT(compound!=NULL,"have body but no compound for collision shape?");
00198   }
00199   
00200   fmat::Column<3> com;
00201   link.sumLinkCenterOfMass(com,totalMass);
00202   (com * world.spaceScale).exportTo(centerOffset);
00203   totalMass*=world.massScale;
00204   
00205   inertia.setValue(0,0,0);
00206   
00207   //if(const KinematicJoint* kj = dynamic_cast<const KinematicJoint*>(&link))
00208   buildLink(link, fmat::Transform::identity());
00209   //else addComponent(link, fmat::Transform::identity());
00210   //compound->setMargin(6*world.spaceScale);
00211 
00212   //std::cout << "built inertia for " << getPath() << " is " << inertia << std::endl;
00213   
00214   btVector3 localInertia;
00215   compound->calculateLocalInertia(totalMass,localInertia);
00216   inertia+=localInertia;
00217   
00218   //std::cout << "local inertia for " << getPath() << " is " << localInertia << std::endl;
00219   //std::cout << "total inertia for " << getPath() << " is " << inertia << std::endl;
00220 
00221   ASSERT(totalMass==0 || compound->getNumChildShapes()>0,"PhysicsBody created with mass but no collision shape")
00222   
00223   if(body!=NULL) {
00224     //std::cout << "Reusing body for " << getPath() << std::endl;
00225     // can't do resetTransform() as currently written... reusing body implies rebuild, need to update children for change in transform,
00226     // and even then the teleport within bullet is a bad idea, essentially have to remove and reconstruct children from scratch to handle constraints (?)
00227     //resetTransform();
00228     
00229     // This seems to work in order enable the PhysicsBody::teleport() functionality, but kind of questionable.
00230     world.dynamicsWorld->removeRigidBody(body);
00231     resetTransform();
00232     world.dynamicsWorld->addRigidBody(body);
00233 
00234   } else {
00235     //std::cout << "Body construction for " << getPath() << std::endl;
00236     //std::cout << "MIRROR btRigidBodyConstructionInfo(" << totalMass << "," << inertia <<")" << std::endl;
00237     //btRigidBody::btRigidBodyConstructionInfo rbInfo(totalMass,new btDefaultMotionState,compound,inertia);
00238     btRigidBody::btRigidBodyConstructionInfo rbInfo(totalMass,NULL,compound,inertia);
00239     /*rbInfo.m_linearDamping = 0.1;
00240     rbInfo.m_angularDamping = 0.1;
00241     rbInfo.m_additionalDamping=true;
00242     rbInfo.m_additionalDampingFactor=.4;
00243     rbInfo.m_additionalAngularDampingFactor=.4;
00244     rbInfo.m_additionalAngularDampingThresholdSqr=.5;*/
00245     body = new btRigidBody(rbInfo);
00246     body->setDamping(world.linearDamping, world.angularDamping);
00247     body->setUserPointer(this);
00248     //body->setMassProps(totalMass,btVector3(inertia[0],inertia[1],inertia[2]));
00249     
00250     resetTransform();
00251     world.dynamicsWorld->addRigidBody(body);
00252   }
00253   
00254   // Set up constraints
00255   if(parentBody==NULL) {
00256     // no-op, root body doesn't have constraint
00257   } else {
00258     btTransform pT;
00259     const fmat::Transform fm = link.getT(parentBody->link);
00260     pT.setBasis(btMatrix3x3(fm(0,0),fm(0,1),fm(0,2), fm(1,0),fm(1,1),fm(1,2), fm(2,0),fm(2,1),fm(2,2)));
00261     pT.setOrigin(btVector3(fm(0,3),fm(1,3),fm(2,3)) * world.spaceScale - parentBody->centerOffset);
00262     //pT.setOrigin(pT * centerOffset);
00263     btTransform lT;
00264     lT.setBasis(btMatrix3x3::getIdentity());
00265     lT.setOrigin(-centerOffset);
00266     if(qpos!=NULL) {
00267       btMatrix3x3& rot = lT.getBasis();
00268       rot[0][0] = rot[1][1] = std::cos(*qpos);
00269       rot[0][1] = -(rot[1][0] = std::sin(*qpos));
00270     }
00271 
00272     //std::cout << "CENTER " << parentBody->centerOffset << '\n' << pT << std::endl;
00273     
00274     ASSERT(joint==NULL,"PhysicsBody already has joint!?");
00275     joint = new btGeneric6DofConstraint(*parentBody->body,*body,pT,lT,true);
00276     
00277     //const float ERP=0.95f, SOFT=0.65f;
00278     const float ERP=0.f, SOFT=0.0f;
00279     joint->getTranslationalLimitMotor()->m_restitution=0;
00280     joint->getTranslationalLimitMotor()->m_limitSoftness=SOFT;
00281     for(unsigned int i=0; i<3; ++i) {
00282       joint->getRotationalLimitMotor(i)->m_limitSoftness=SOFT;
00283 #if BT_BULLET_VERSION<276
00284       joint->getRotationalLimitMotor(i)->m_ERP=ERP;
00285 #else
00286       joint->getTranslationalLimitMotor()->m_stopERP[i] = ERP;
00287       joint->getTranslationalLimitMotor()->m_stopCFM[i] = SOFT;
00288       joint->getTranslationalLimitMotor()->m_normalCFM[i] = SOFT;
00289       joint->getRotationalLimitMotor(i)->m_stopERP = ERP;
00290       joint->getRotationalLimitMotor(i)->m_stopCFM = SOFT;
00291       joint->getRotationalLimitMotor(i)->m_normalCFM = SOFT;
00292 #endif
00293     }
00294     if(qpos==NULL) {
00295       // static constraint
00296       /*joint->setLinearLowerLimit(btVector3(0,0,0));
00297       joint->setLinearUpperLimit(btVector3(0,0,0));*/
00298       joint->setAngularLowerLimit(btVector3(0,0,0));
00299       joint->setAngularUpperLimit(btVector3(0,0,0));
00300     } else if(link.controllerInfo.velocity) {
00301       // constraint controlled by velocity
00302       joint->setAngularLowerLimit(btVector3(0,0,1));
00303       joint->setAngularUpperLimit(btVector3(0,0,-1));
00304       jointInterface = new GenericAngularMotorInterface(*joint);
00305       float scale=1;
00306       const fmat::Column<3> aabbdim = link.getAABB().getDimensions();
00307       if(aabbdim[0]!=aabbdim[1])
00308         std::cerr << "non-symmetric wheel boundaries?  what's the radius?" << std::endl;
00309       if(aabbdim[0]<=0) {
00310         std::cerr << "wheel diameter is non-positive" << std::endl;
00311       } else {
00312         scale = 2/aabbdim[0];
00313       }
00314       jointController = new LinearMotorController(*qpos,*jointInterface,scale);
00315     } else {
00316       // constraint controlled by position
00317       joint->setAngularLowerLimit(btVector3(0,0,-link.qmax));
00318       joint->setAngularUpperLimit(btVector3(0,0,-link.qmin));
00319       for(unsigned int i=0; i<3; ++i) {
00320         joint->getRotationalLimitMotor(i)->m_maxMotorForce=8000;
00321         joint->getRotationalLimitMotor(i)->m_maxLimitForce=8000;
00322       }
00323       if(link.controllerInfo.forceControl) {
00324         jointInterface = new GenericAngularMotorInterface(*joint);
00325         PIDMotorController * tmp = new PIDMotorController(*qpos,*jointInterface);
00326         tmp->p = 10;
00327         tmp->i = 0;
00328         tmp->d = 0;
00329         jointController = tmp;
00330         
00331         /*jointController->p = 150;
00332          jointController->i = 0;
00333          jointController->d = 0;
00334          jointController->derrGamma = 0.5;
00335          jointController->maxResponse=-1;
00336          jointController->minResponse=15;
00337          jointController->punch=5;
00338          jointController->friction=0.5;
00339          //jointController->linearff=1;*/
00340       } else {
00341         jointInterface = new GenericAngularPositionInterface(*joint);
00342         jointController = new LinearMotorController(*qpos,*jointInterface);
00343       }
00344     }
00345 
00346     //btHingeConstraint* hinge = new btHingeConstraint(*parentBody->body,*body,pT,lT,true);
00347     
00348     /*jointInterface = new HingeAngularMotorInterface(*hinge);
00349     DirectController * pid = new DirectController(*qpos,*jointInterface);*/
00350     
00351     /*jointInterface = new HingeAngularMotorInterface(*hinge);
00352     PIDController * pid = new PIDController(*qpos,*jointInterface);
00353     pid->p=.75;
00354     pid->i=10;
00355     pid->d = 0;
00356     pid->minResponse=0; pid->punch=0; pid->friction=0;*/
00357     
00358     /*jointInterface = new HingeVelocityInterface(*hinge);
00359     PIDController * pid = new PIDController(*qpos,*jointInterface);
00360     pid->p=.02;
00361     pid->i=.25;
00362     pid->d = 0.001;
00363     pid->minResponse=0; pid->punch=0; pid->friction=0;*/
00364     
00365     /*jointInterface = new HingePositionInterface(*hinge);
00366     PIDController * pid = new PIDController(*qpos,*jointInterface);
00367     pid->p = 90;
00368     pid->i = 40;
00369     pid->d = 0.2;
00370     pid->minResponse=10; pid->punch=0; pid->friction=70;*/
00371     
00372     /*jointInterface = new HingePositionInterface(*hinge);
00373     PIDController * pid = new PIDController(*qpos,*jointInterface);
00374     pid->p = 300;//200000;
00375     pid->i = 0;
00376     pid->d = 0;//20000;
00377     pid->minResponse=10; pid->punch=0; pid->friction=2;//20000;*/
00378     
00379     if(jointController!=NULL) {
00380       jointController->set(link.controllerInfo);
00381       world.motorControllers.insert(jointController);
00382     }
00383     if(joint!=NULL)
00384       world.dynamicsWorld->addConstraint(joint, true);
00385     /*if(jointController!=NULL)
00386       jointController->updateControllerResponse(0);*/
00387     //setVelocity(btVector3(0,0,0), btVector3(0,0,0));
00388   }
00389     
00390   //body->setWorldTransform(btTransform(btQuaternion(0,0,0,1),btVector3(centerOffset[0],centerOffset[1],centerOffset[2])));
00391   
00392   //setQ(kj.getQ(),findParentBody()); // updates body position
00393   
00394   //std::cout << kj.getPath() << " set body at " << fmat::SubVector<3>(body->getWorldTransform().getOrigin()) << std::endl;
00395   // todo offset geom -m.c
00396   //dGeomSetOffsetPosition(box, centerOffset[0], centerOffset[1], centerOffset[2]);
00397   
00398   //updateJoint();
00399   updateFrictionForce();
00400   updateAnistropicFriction();
00401 }
00402 
00403 void PhysicsBody::buildLink(const KinematicJoint& kj, const fmat::Transform& tr) {
00404   addComponent(kj,tr);
00405   for(KinematicJoint::component_iterator it=kj.components.begin(); it!=kj.components.end(); ++it) {
00406     addComponent(**it,tr);
00407   }
00408   for(KinematicJoint::branch_iterator it=kj.getBranches().begin(); it!=kj.getBranches().end(); ++it) {
00409     if(!(*it)->hasMass()) {
00410       buildLink(**it,tr * (*it)->getTq());
00411     }
00412   }
00413 }
00414 
00415 void PhysicsBody::addComponent(const LinkComponent& comp, const fmat::Transform& tr) {
00416   if(comp.collisionModel.size()==0) {
00417     if(comp.mass>0) {
00418       // todo: should use bb or something instead of point mass (what if CoM at origin?  no inertia?)
00419       float m = comp.mass * world.massScale;
00420       fmat::Column<3> com;
00421       com.importFrom(comp.centerOfMass);
00422       com = tr * com * world.spaceScale;
00423       btVector3 localInertia = btVector3(com[0],com[1],com[2]) - centerOffset;
00424       inertia += btVector3(localInertia[0]*localInertia[0]*m, localInertia[1]*localInertia[1]*m, localInertia[2]*localInertia[2]*m);
00425     }
00426     return;
00427   }
00428   //std::cout << "Adding " << getModel() << " on " << getPath() << std::endl;
00429   const fmat::Column<3> dim = fmat::Column<3>().importFrom(comp.collisionModelScale);
00430   const fmat::Column<3> pos = fmat::Column<3>().importFrom(comp.collisionModelOffset);
00431   fmat::Quaternion rot = fmat::Quaternion::fromAxis(comp.collisionModelRotation);
00432   btVector3 dim_2, origin;
00433   (dim/2 * world.spaceScale).exportTo(dim_2);
00434   (tr * pos * world.spaceScale).exportTo(origin);
00435   rot = fmat::Quaternion::fromMatrix(tr.rotation()) * rot;
00436   if(comp.collisionModel=="Cube") {
00437     btCollisionShape * collide = new btBoxShape(dim_2);
00438     collide->setMargin(world.collisionMargin*world.spaceScale);
00439     const btTransform btr(btQuaternion(rot.getX(),rot.getY(),rot.getZ(),rot.getW()), origin - centerOffset);
00440     compound->addChildShape(btr,collide);
00441     //std::cout << kj.getPath() << " set BB " << dim << std::endl;
00442   } else if(comp.collisionModel=="Sphere") {
00443     btCollisionShape * collide=NULL;
00444     if(dim[0]==dim[1] && dim[0]==dim[2]) {
00445       collide = new btSphereShape(dim_2[0]);
00446     } else {
00447       btVector3 center(0,0,0);
00448       btScalar radi=1;
00449       collide = new btMultiSphereShape(&center,&radi,1);
00450       collide->setLocalScaling(dim_2);
00451     }
00452     collide->setMargin(world.collisionMargin*world.spaceScale);
00453     const btTransform btr(btQuaternion(rot.getX(),rot.getY(),rot.getZ(),rot.getW()), origin - centerOffset);
00454     compound->addChildShape(btr,collide);
00455   } else if(comp.collisionModel=="Cylinder") {
00456     btCollisionShape * collide = new btCylinderShapeZ(dim_2);
00457     collide->setMargin(world.collisionMargin*world.spaceScale);
00458     const btTransform btr(btQuaternion(rot.getX(),rot.getY(),rot.getZ(),rot.getW()), origin - centerOffset);
00459     compound->addChildShape(btr,collide);
00460   } else if(comp.collisionModel=="Plane") {
00461     fmat::Column<3> normal = rot * fmat::UNIT3_Z;
00462     fmat::fmatReal dist = fmat::dotProduct(normal,pos) * world.spaceScale;
00463     btCollisionShape * collide = new btStaticPlaneShape(normal.exportTo<btVector3>(),dist);
00464     //collide->setMargin(world.collisionMargin*world.spaceScale); // 0 by default, maybe all static shapes should be 0...?
00465     compound->addChildShape(btTransform::getIdentity(),collide);
00466   } else {
00467 #ifdef HAVE_OGRE
00468     /*
00469     CollisionData*& cd = collisionData[collisionDataName = comp.collisionModel];
00470     if(cd==NULL)
00471       cd = new CollisionData(comp.collisionModel,Client::loadMesh(comp.collisionModel));
00472     cd->addReference();
00473      */
00474     // TODO add collision data to compound
00475     //collide->setMargin(2*world.spaceScale);
00476     //collide->recalculateLocalAabb(); // need manual recalc aabb on bullet collision shape after set margin
00477 #endif
00478   }
00479 }
00480 
00481 void PhysicsBody::resetTransform() {
00482   btTransform pT_w;
00483   if(parentBody==NULL) {
00484     pT_w.setBasis(lastTransform.getBasis());
00485     pT_w.setOrigin(lastTransform.getOrigin());
00486   } else {
00487     pT_w = parentBody->body->getWorldTransform();
00488     const btVector3& parentOffset = parentBody->centerOffset;
00489     pT_w.setOrigin(pT_w * -parentOffset);
00490   }
00491   //std::cout << getPath() << " pT_w is\n" << pT_w << std::endl;
00492   
00493   if(qpos!=NULL)
00494     link.tryQ(*qpos);
00495   btTransform kjT_p;
00496   if(parentBody==NULL) {
00497     if(KinematicJoint* kj = dynamic_cast<KinematicJoint*>(&link)) {
00498       const fmat::Transform fm = kj->getFullT();
00499       kjT_p.setBasis(btMatrix3x3(fm(0,0),fm(0,1),fm(0,2), fm(1,0),fm(1,1),fm(1,2), fm(2,0),fm(2,1),fm(2,2)));
00500       kjT_p.setOrigin(btVector3(fm(0,3),fm(1,3),fm(2,3)) * world.spaceScale);
00501     } else {
00502       kjT_p = btTransform::getIdentity();
00503     }
00504   } else {
00505     KinematicJoint& parentKJ = dynamic_cast<KinematicJoint&>(parentBody->link);
00506     const fmat::Transform fm = dynamic_cast<KinematicJoint&>(link).getT(parentKJ);
00507     kjT_p.setBasis(btMatrix3x3(fm(0,0),fm(0,1),fm(0,2), fm(1,0),fm(1,1),fm(1,2), fm(2,0),fm(2,1),fm(2,2)));
00508     kjT_p.setOrigin(btVector3(fm(0,3),fm(1,3),fm(2,3)) * world.spaceScale);
00509   }
00510   //std::cout << getPath() << " kjT_p is\n" << kjT_p << std::endl;
00511   const btTransform kjT_w = pT_w * kjT_p;
00512   const btVector3 b_w = kjT_w * centerOffset;
00513   
00514   body->setWorldTransform(btTransform(kjT_w.getBasis(),b_w));
00515   /*if(string_util::beginsWith(getPath(),"/BaseFrame:Chiara/Body2")) {
00516     std::cout << getPath() << " set body at " << body->getWorldTransform().getOrigin() << std::endl;
00517     stacktrace::displayCurrentStackTrace();
00518   }*/
00519 } 
00520 
00521 void PhysicsBody::clear() {
00522   //std::cout << "Clearing " << getPath() << std::endl;
00523   if(body!=NULL) {
00524     ASSERT( (jointController==NULL && jointInterface==NULL) || (jointController!=NULL && jointInterface!=NULL), "Have some joint constraint objects, but not all?");
00525     if(jointController!=NULL) {
00526       world.motorControllers.erase(jointController);
00527       delete jointController; jointController=NULL;
00528     }
00529     if(jointInterface!=NULL) {
00530       delete jointInterface; jointInterface=NULL;
00531     }
00532     if(joint!=NULL) {
00533       world.dynamicsWorld->removeConstraint(joint);
00534       delete joint; joint=NULL;
00535     }
00536     lastTransform = body->getWorldTransform();
00537     lastTransform.setOrigin(lastTransform.getOrigin() - quatRotate(lastTransform.getRotation(),centerOffset));
00538     
00539     for(int i=compound->getNumChildShapes()-1; i>=0; --i) {
00540       delete compound->getChildShape(i);
00541       compound->removeChildShapeByIndex(i);
00542     }
00543     if(collisionDataName.size()>0)
00544       collisionData[collisionDataName]->removeReference();
00545     collisionDataName.clear();
00546     
00547     if(children.size()>0) {
00548       //std::cout << "Leaving " << getPath() << " for reuse (and not to break child constraints)" << std::endl;
00549     } else {
00550       world.dynamicsWorld->removeRigidBody(body);
00551       delete body; body=NULL;
00552       delete compound; compound = NULL;
00553     }
00554 
00555     /*
00556     if(kj.getParent()!=NULL)
00557       client.kjInfos[kj.getParent()]->physic.updateMass();
00558     for(KinematicJoint::branch_iterator it=kj.getBranches().begin(); it!=kj.getBranches().end(); ++it) {
00559       client.kjInfos[*it]->physic.updateBody(false);
00560     }
00561      */
00562   } else {
00563     ASSERT(jointController==NULL,"have jointController but no body?");
00564     ASSERT(jointInterface==NULL,"have jointInterface but no body?");
00565     ASSERT(joint==NULL,"have joint but no body?");
00566     ASSERT(compound->getNumChildShapes()==0,"have collision shape but no body?");
00567     ASSERT(collisionDataName.size()>0,"have collisionDataName but no body?");
00568   }
00569 }
00570 
00571 void PhysicsBody::updateFrictionForce() {
00572   body->setFriction(link.frictionForce);
00573 }
00574 
00575 void PhysicsBody::updateAnistropicFriction() {
00576   body->setAnisotropicFriction(btVector3(link.anistropicFrictionRatio[0],link.anistropicFrictionRatio[1],link.anistropicFrictionRatio[2]));
00577 }
00578 
00579 bool PhysicsBody::isChild(const LinkComponent& pb, const LinkComponent& child) {
00580   if(const KinematicJoint* kj = dynamic_cast<const KinematicJoint*> (&pb)) {
00581     if(std::find(kj->components.begin(),kj->components.end(),&child) != kj->components.end())
00582       return true;
00583     for(KinematicJoint::branch_iterator it=kj->getBranches().begin(); it!=kj->getBranches().end(); ++it)
00584       if(isChild(**it,child))
00585         return true;
00586   }
00587   return false;
00588 }
00589 
00590 
00591 
00592 PhysicsBody::ComponentListener::~ComponentListener() {
00593   if(KinematicJoint* kj = dynamic_cast<KinematicJoint*>(&comp)) {
00594     kj->removeBranchListener(this);
00595     kj->components.removeCollectionListener(this);
00596   }
00597   for(std::map<LinkComponent*, ComponentListener*>::iterator it=subComps.begin(); it!=subComps.end(); ++it)
00598     delete it->second;
00599   subComps.clear();
00600   freeBody();
00601 }
00602 
00603 std::string PhysicsBody::ComponentListener::getPath() const {
00604   stringstream ss;
00605   if(const KinematicJoint* kj = dynamic_cast<const KinematicJoint*>(&comp)) {
00606     ss << kj->getPath() << ":" << getModel();
00607   } else if(parent!=NULL) {
00608     ss << parent->getPath() << "/" << getModel();
00609   } else {
00610     ss << "Free-" << getModel();
00611   }
00612   ss << "@" << &comp;
00613   return ss.str();
00614 }
00615 
00616 void PhysicsBody::ComponentListener::init() {
00617   //std::cout << "Creating listener for " << getPath() << std::endl;
00618   //if(parent==NULL)
00619   //  allocBody();
00620   if(KinematicJoint* kj = dynamic_cast<KinematicJoint*>(&comp)) {
00621     //std::cout << kj << ' ' << body << ' ' << kj->hasMass() << ' ' << hasCollisionShape(*kj) << std::endl;
00622     bool hasMass = kj->hasMass();
00623     bool hasCollision = hasCollisionShape(*kj);
00624     kj->components.addCollectionListener(this);
00625     kj->addBranchListener(this);
00626     
00627     // if mass and shape, valid independent body; if root and shape, it's a static obstacle; otherwise wait for a sub-branch to be added
00628     if(body==NULL && ( (hasMass && hasCollision) || (parent==NULL && hasCollision) ) )
00629       allocBody();
00630 
00631     plistCollectionEntriesChanged(kj->components);
00632   }
00633 }
00634 
00635 void PhysicsBody::ComponentListener::allocBody() {
00636   ASSERTRET(body==NULL,"attempted to allocate a new body without freeing previous")
00637   btTransform tr(btTransform::getIdentity());
00638   if(bodyLocation!=NULL)
00639     tr.getOrigin() = btVector3((*bodyLocation)[0],(*bodyLocation)[1],(*bodyLocation)[2]) * world.spaceScale;
00640   if(bodyOrientation!=NULL) {
00641     fmat::Quaternion quat = fmat::Quaternion::fromAxis(*bodyOrientation);
00642     tr.setRotation(btQuaternion(quat.getX(),quat.getY(),quat.getZ(),quat.getW()));
00643   }
00644   PhysicsBody* bodyParent=NULL;
00645   if(parent!=NULL)
00646     bodyParent = &parent->findBody();
00647   KinematicJoint& kj = dynamic_cast<KinematicJoint&>(comp);
00648   plist::Primitive<float>* pos = (kj.outputOffset==plist::OutputSelector::UNUSED) ? NULL : &positions[kj.outputOffset.get()];
00649   ASSERT(body==NULL,"creating new body, already had one!?");
00650   body = new PhysicsBody(world, kj, pos, tr, bodyParent);
00651   updateListeners();
00652 }
00653 
00654 void PhysicsBody::ComponentListener::freeBody() {
00655   if(body!=NULL) {
00656     delete collisionModelScaleListener; collisionModelScaleListener=NULL;
00657     delete collisionModelRotationListener; collisionModelRotationListener=NULL;
00658     delete collisionModelOffsetListener; collisionModelOffsetListener=NULL;
00659     delete centerOfMassListener; centerOfMassListener=NULL;
00660     delete body; body=NULL;
00661   }
00662 }
00663 
00664 PhysicsBody& PhysicsBody::ComponentListener::findBody() {
00665   if(body!=NULL)
00666     return *body;
00667   if(parent==NULL) {
00668     //throw std::runtime_error("Could not find body for ComponentListener");
00669     allocBody();
00670     return *body;
00671   }
00672   return parent->findBody();
00673 }
00674 
00675 PhysicsBody::ComponentListener& PhysicsBody::ComponentListener::findLinkRoot() {
00676   if(dynamic_cast<KinematicJoint*>(&comp) || parent==NULL)
00677     return *this;
00678   ASSERT(dynamic_cast<KinematicJoint*>(&parent->comp),"Parent is not a kinematic joint?")
00679   return *parent;
00680 }
00681 
00682 void PhysicsBody::ComponentListener::updateListeners() {
00683   delete collisionModelScaleListener; collisionModelScaleListener=NULL;
00684   delete collisionModelRotationListener; collisionModelRotationListener=NULL;
00685   delete collisionModelOffsetListener; collisionModelOffsetListener=NULL;
00686   delete centerOfMassListener; centerOfMassListener=NULL;
00687   PhysicsBody& b = findBody();
00688   if(comp.collisionModel=="Cube") {
00689     collisionModelScaleListener = new plist::CollectionCallbackMember<PhysicsBody>(comp.collisionModelScale,b,&PhysicsBody::rebuild, false);
00690     collisionModelRotationListener = new plist::CollectionCallbackMember<PhysicsBody>(comp.collisionModelRotation,b,&PhysicsBody::rebuild, false);
00691     collisionModelOffsetListener = new plist::CollectionCallbackMember<PhysicsBody>(comp.collisionModelOffset,b,&PhysicsBody::rebuild, false);
00692   }
00693   if(comp.mass!=0)
00694     centerOfMassListener = new plist::CollectionCallbackMember<PhysicsBody>(comp.centerOfMass,b,&PhysicsBody::rebuild, false);
00695   for(std::map<LinkComponent*, ComponentListener*>::iterator it=subComps.begin(); it!=subComps.end(); ++it)
00696     if(it->second->body==NULL)
00697       it->second->updateListeners();
00698 }
00699 
00700 void PhysicsBody::ComponentListener::plistCollectionEntryAdded(plist::Collection& /*col*/, plist::ObjectBase& entry) {
00701   LinkComponent& lc = dynamic_cast<LinkComponent&>(entry);
00702   ComponentListener * listener = new ComponentListener(world,lc,positions,this);
00703   subComps[&lc] = listener;
00704   if(listener->body==NULL && (lc.hasMass() || hasCollisionShape(lc)))
00705     rebuildBody();
00706 }
00707 
00708 void PhysicsBody::ComponentListener::plistCollectionEntryRemoved(plist::Collection& /*col*/, plist::ObjectBase& entry) {
00709   LinkComponent& lc = dynamic_cast<LinkComponent&>(entry);
00710   bool willBeMissed = (lc.hasMass() || hasCollisionShape(lc));
00711   std::map<LinkComponent*, ComponentListener*>::iterator it=subComps.find(&lc);
00712   if(it==subComps.end()) {
00713     std::cerr << "ERROR: PhysicsBody::LinkComponentListener could not find entry in component list for removed component" << std::endl;
00714   } else {
00715     delete it->second;
00716     subComps.erase(it);
00717   }
00718   if(willBeMissed)
00719     rebuildBody();
00720 }
00721 
00722 void PhysicsBody::ComponentListener::plistCollectionEntriesChanged(plist::Collection& /*col*/) {
00723   size_t oldSize = subComps.size();
00724   for(std::map<LinkComponent*, ComponentListener*>::iterator it=subComps.begin(); it!=subComps.end(); ++it)
00725     delete it->second;
00726   subComps.clear();
00727   KinematicJoint& kj = dynamic_cast<KinematicJoint&>(comp);
00728   for(KinematicJoint::component_iterator it=kj.components.begin(); it!=kj.components.end(); ++it) {
00729     ComponentListener * cl = new ComponentListener(world,**it,positions,this);
00730     subComps[*it] = cl; // finish construction before adding to map so we don't have intermediary NULLs
00731   }
00732   for(KinematicJoint::branch_iterator it=kj.getBranches().begin(); it!=kj.getBranches().end(); ++it) {
00733     ComponentListener * cl = new ComponentListener(world,**it,positions,this);
00734     subComps[*it] = cl; // finish construction before adding to map so we don't have intermediary NULLs
00735   }
00736   if(oldSize!=0 || (subComps.size()!=0 && comp.hasMass() && hasCollisionShape(comp)))
00737     rebuildBody();
00738 }
00739 
00740 
00741 void PhysicsBody::ComponentListener::kinematicJointBranchAdded(KinematicJoint& kjparent, KinematicJoint& branch) {
00742   plistCollectionEntryAdded(kjparent, branch);
00743 }
00744 
00745 void PhysicsBody::ComponentListener::kinematicJointBranchRemoved(KinematicJoint& kjparent, KinematicJoint& branch) {
00746   plistCollectionEntryRemoved(kjparent, branch);
00747 }
00748 
00749 void PhysicsBody::ComponentListener::kinematicJointReconfigured(KinematicJoint& /*joint*/) {}
00750 
00751 
00752 void PhysicsBody::ComponentListener::updateMass() {
00753   if(comp.mass==0) {
00754     // if mass is zero, center of mass doesn't matter
00755     delete centerOfMassListener;
00756     centerOfMassListener=NULL;
00757     // check that we haven't lost the body
00758     if(parent!=NULL) {
00759       // on a sub-branch, must have both mass and shape to keep independent body
00760       // (the root of an object can be zero mass or no shape -- indicates its base is bolted to ground (or otherwise not subject to simulation)
00761       ComponentListener& l = findLinkRoot();
00762       if(l.body!=NULL) {
00763         if(!l.comp.hasMass() || !hasCollisionShape(l.comp)) {
00764           l.freeBody();
00765         }
00766       }
00767     }
00768   } else {
00769     if(centerOfMassListener==NULL)
00770       centerOfMassListener = new plist::CollectionCallbackMember<PhysicsBody>(comp.centerOfMass,findBody(),&PhysicsBody::rebuild, false);
00771     // check that we might need a new body
00772     if(parent!=NULL) {
00773       // on a sub-branch (root of an object always has a body)
00774       ComponentListener& l = findLinkRoot();
00775       if(l.body==NULL) {
00776         if(l.comp.hasMass() && hasCollisionShape(l.comp)) {
00777           rebuildBody(); // first trigger rebuild on the body we're splitting from
00778           l.allocBody(); // now make our own body
00779           return; // just created a new body, don't fall through to setup stuff below
00780         }
00781       }
00782     }
00783   }
00784   rebuildBody();
00785 }
00786 
00787 void PhysicsBody::ComponentListener::updateModel() {
00788   if(comp.collisionModel.size()==0) {
00789     // if collisionModel is blank, these don't matter
00790     delete collisionModelScaleListener; collisionModelScaleListener=NULL;
00791     delete collisionModelRotationListener; collisionModelRotationListener=NULL;
00792     delete collisionModelOffsetListener; collisionModelOffsetListener=NULL;
00793     // check that we haven't lost the body
00794     if(parent!=NULL) {
00795       // on a sub-branch, must have both mass and shape to keep independent body
00796       // (the root of an object can be zero mass or no shape -- indicates its base is bolted to ground (or otherwise not subject to simulation)
00797       ComponentListener& l = findLinkRoot();
00798       if(l.body!=NULL) {
00799         if(!l.comp.hasMass() || !hasCollisionShape(l.comp)) {
00800           l.freeBody();
00801         }
00802       }
00803     }
00804   } else {
00805     // check that we might need a new body
00806     if(parent!=NULL) {
00807       // on a sub-branch (root of an object always has a body)
00808       ComponentListener& l = findLinkRoot();
00809       if(l.body==NULL) {
00810         if(l.comp.hasMass() && hasCollisionShape(l.comp)) {
00811           rebuildBody(); // first trigger rebuild on the body we're splitting from
00812           l.allocBody(); // now make our own body
00813           return; // just created a new body, don't fall through to setup stuff below
00814         }
00815       }
00816     }
00817     if(collisionModelOffsetListener==NULL) {
00818       PhysicsBody& b = findBody();
00819       collisionModelScaleListener = new plist::CollectionCallbackMember<PhysicsBody>(comp.collisionModelScale,b,&PhysicsBody::rebuild, false);
00820       collisionModelRotationListener = new plist::CollectionCallbackMember<PhysicsBody>(comp.collisionModelRotation,b,&PhysicsBody::rebuild, false);
00821       collisionModelOffsetListener = new plist::CollectionCallbackMember<PhysicsBody>(comp.collisionModelOffset,b,&PhysicsBody::rebuild, false);
00822     }
00823   }
00824   rebuildBody();
00825 }
00826   
00827 bool PhysicsBody::ComponentListener::hasCollisionShape(const LinkComponent& c) {
00828   if(c.collisionModel.size()>0)
00829     return true;
00830   if(const KinematicJoint* kj = dynamic_cast<const KinematicJoint*>(&c)) {
00831     for(KinematicJoint::component_iterator it=kj->components.begin(); it!=kj->components.end(); ++it) {
00832       if((*it)->collisionModel.size()>0)
00833         return true;
00834     }
00835     for(KinematicJoint::branch_iterator it=kj->getBranches().begin(); it!=kj->getBranches().end(); ++it) {
00836       if(!(*it)->hasMass()) {
00837         if(hasCollisionShape(**it))
00838           return true;
00839       }
00840     }
00841   }
00842   return false;
00843 }
00844 
00845 
00846 
00847 #ifdef HAVE_OGRE
00848 CollisionData::CollisionData(const std::string& meshName, Ogre::MeshPtr mesh)
00849   : name(meshName), vertices(NULL), indices(NULL) 
00850 {
00851   // Calculate how many vertices and indices we're going to need
00852   bool added_shared = false;
00853   size_t vertex_count=0, index_count=0;
00854   for ( unsigned short i = 0; i < mesh->getNumSubMeshes(); ++i) {
00855     Ogre::SubMesh* submesh = mesh->getSubMesh(i);
00856     // We only need to add the shared vertices once
00857     if(submesh->useSharedVertices) {
00858       if( !added_shared ) {
00859         vertex_count += mesh->sharedVertexData->vertexCount;
00860         added_shared = true;
00861       }
00862     } else {
00863       vertex_count += submesh->vertexData->vertexCount;
00864     }
00865     // Add the indices
00866     index_count += submesh->indexData->indexCount;
00867   }
00868   
00869   // Allocate space for the vertices and indices
00870   
00871   vertices = new float[vertex_count*3];
00872   indices = new unsigned int[index_count];
00873   
00874   // Run through the submeshes again, adding the data into the arrays
00875   added_shared = false;
00876   size_t current_offset = 0;
00877   size_t shared_offset = 0;
00878   size_t next_offset = 0;
00879   size_t index_offset = 0;
00880   for (unsigned short i = 0; i < mesh->getNumSubMeshes(); ++i) {
00881     Ogre::SubMesh* submesh = mesh->getSubMesh(i);
00882     
00883     Ogre::VertexData* vertex_data = submesh->useSharedVertices ? mesh->sharedVertexData : submesh->vertexData;
00884     
00885     if ((!submesh->useSharedVertices) || (submesh->useSharedVertices && !added_shared)) {
00886       if(submesh->useSharedVertices) {
00887         added_shared = true;
00888         shared_offset = current_offset;
00889       }
00890       
00891       const Ogre::VertexElement* posElem = vertex_data->vertexDeclaration->findElementBySemantic(Ogre::VES_POSITION);
00892       Ogre::HardwareVertexBufferSharedPtr vbuf = vertex_data->vertexBufferBinding->getBuffer(posElem->getSource());
00893       unsigned char* vertex = static_cast<unsigned char*>(vbuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
00894       
00895       float* pReal;
00896       for( size_t j = 0; j < vertex_data->vertexCount; ++j, vertex += vbuf->getVertexSize())
00897       {
00898         posElem->baseVertexPointerToElement(vertex, &pReal);
00899         vertices[(current_offset + j)*3 + 0] = pReal[0]/1000;
00900         vertices[(current_offset + j)*3 + 1] = pReal[1]/1000;
00901         vertices[(current_offset + j)*3 + 2] = pReal[2]/1000;
00902         /*Ogre::Vector3 pt(pReal[0], pReal[1], pReal[2]);
00903          vertices[current_offset + j] = (orient * (pt * scale)) + position;*/
00904         //std::cout << "Vertex " << j << ": " << fmat::SubVector<3>(&vertices[(current_offset + j)*3]) << std::endl;
00905       }
00906       
00907       vbuf->unlock();
00908       next_offset += vertex_data->vertexCount;
00909     }
00910     
00911     
00912     Ogre::IndexData* index_data = submesh->indexData;
00913     const size_t indexCnt = index_data->indexCount;
00914     Ogre::HardwareIndexBufferSharedPtr ibuf = index_data->indexBuffer;
00915     
00916     bool use32bitindexes = (ibuf->getType() == Ogre::HardwareIndexBuffer::IT_32BIT);
00917     
00918     unsigned long* pLong = static_cast<unsigned long*>(ibuf->lock(Ogre::HardwareBuffer::HBL_READ_ONLY));
00919     unsigned short* pShort = reinterpret_cast<unsigned short*>(pLong);
00920     
00921     size_t offset = (submesh->useSharedVertices)? shared_offset : current_offset;
00922     
00923     if ( use32bitindexes ) {
00924       for ( size_t k = 0; k < indexCnt; k+=3) {
00925         indices[index_offset++] = pLong[k+0] + static_cast<unsigned long>(offset);
00926         indices[index_offset++] = pLong[k+1] + static_cast<unsigned long>(offset);
00927         indices[index_offset++] = pLong[k+2] + static_cast<unsigned long>(offset);
00928         //std::cout << "Triangle " << k/3 << ": " << fmat::SubVector<3,dTriIndex>(&indices[index_offset-3]) << std::endl;
00929       }
00930     } else {
00931       for ( size_t k = 0; k < indexCnt; k+=3) {
00932         indices[index_offset++] = static_cast<unsigned long>(pShort[k+0]) + static_cast<unsigned long>(offset);
00933         indices[index_offset++] = static_cast<unsigned long>(pShort[k+1]) + static_cast<unsigned long>(offset);
00934         indices[index_offset++] = static_cast<unsigned long>(pShort[k+2]) + static_cast<unsigned long>(offset);
00935         //std::cout << "Triangle " << k/3 << ": " << fmat::SubVector<3,dTriIndex>(&indices[index_offset-3]) << std::endl;
00936       }
00937     }
00938     
00939     ibuf->unlock();
00940     current_offset = next_offset;
00941   }
00942   
00943   std::cout << "Vertices: " << vertex_count << std::endl;
00944   for(unsigned int i=0; i<vertex_count; ++i)
00945     std::cout << vertices[i*3+0] << ' ' << vertices[i*3+1] << ' ' << vertices[i*3+2] << std::endl;
00946   std::cout << "Indices: " << index_count << std::endl;
00947   for(unsigned int i=0; i<index_count/3; ++i)
00948     std::cout << indices[i*3+0] << ' ' << indices[i*3+1] << ' ' << indices[i*3+2] << std::endl;
00949 }
00950 #endif
00951 
00952 #endif // HAVE_BULLET
00953 
00954 /*! @file
00955  * @brief 
00956  * @author Ethan Tira-Thompson (ejt) (Creator)
00957  */

Tekkotsu v5.1CVS
Generated Mon May 9 04:58:46 2016 by Doxygen 1.6.3