#include #include #include "libs.h" #include "space.h" #include "body.h" #include "frame.h" #include "star.h" #include "planet.h" #include "l3d.h" #include "player.h" #include "star_system.h" #include "space_station.h" #include "sbre/sbre.h" #include "serializer.h" #include "collider/collider.h" dWorldID Space::world; std::list Space::bodies; Frame* Space::rootFrame; static dJointGroupID _contactgroup; std::list Space::corpses; void Space::Init(void) { world = dWorldCreate(); rootFrame = new Frame(NULL, "System"); rootFrame->SetRadius(FLT_MAX); _contactgroup = dJointGroupCreate(0); //dWorldSetGravity(world, 0, -9.81, 0); } void Space::Serialize(void) { using namespace Serializer::Write; Serializer::IndexFrames(); Serializer::IndexBodies(); Serializer::IndexSystemBodies(L3D::currentSystem); Frame::Serialize(rootFrame); printf("%d bodies to write\n", bodies.size()); wr_int(bodies.size()); for(bodiesIter_t i = bodies.begin(); i != bodies.end(); ++i) { printf("Serializing %s\n", (*i)->GetLabel().c_str()); (*i)->Serialize(); } } void Space::Unserialize(void) { using namespace Serializer::Read; Serializer::IndexSystemBodies(L3D::currentSystem); rootFrame = Frame::Unserialize(0); Serializer::IndexFrames(); int num_bodies = rd_int(); printf("%d bodies to read\n", num_bodies); for(int i = 0; i < num_bodies; i++) { Body* b = Body::Unserialize(); if(b) bodies.push_back(b); if(b->IsType(Object::PLAYER)) L3D::player = (Player*)b; } printf("%d bodies read\n", bodies.size()); /* Bodies with references to others must fix these up. */ Serializer::IndexBodies(); for(bodiesIter_t i = bodies.begin(); i != bodies.end(); ++i) { (*i)->PostLoadFixup(); } Frame::PostUnserializeFixup(rootFrame); } void Space::Clear(void) { for(std::list::iterator i = bodies.begin(); i != bodies.end(); ++i) { (*i)->SetFrame(NULL); if((*i) != (Body*)L3D::player) { KillBody(*i); } } PruneCorpses(); L3D::player->SetFrame(rootFrame); for(std::list::iterator i = rootFrame->m_children.begin(); i != rootFrame->m_children.end(); ++i) delete *i; rootFrame->m_children.clear(); } void Space::MoveOrbitingObjectFrames(Frame* f) { if(f->m_sbody) { /* Not too efficient. */ vector3d pos = f->m_sbody->orbit.CartesianPosAtTime(L3D::GetGameTime()); vector3d pos2 = f->m_sbody->orbit.CartesianPosAtTime(L3D::GetGameTime()+1.0); vector3d vel = pos2 - pos; f->SetPosition(pos); f->SetVelocity(vel); } f->RotateInTimestep(L3D::GetTimeStep()); for(std::list::iterator i = f->m_children.begin(); i != f->m_children.end(); ++i) { MoveOrbitingObjectFrames(*i); } } static Frame* MakeFrameFor(StarSystem::SBody* sbody, Body* b, Frame* f) { Frame* orbFrame, *rotFrame; double frameRadius; if(!sbody->parent) { if(b) b->SetFrame(f); f->m_sbody = sbody; f->m_astroBody = b; return f; } if(sbody->type == StarSystem::TYPE_GRAVPOINT) { orbFrame = new Frame(f, sbody->name.c_str()); orbFrame->m_sbody = sbody; orbFrame->m_astroBody = b; orbFrame->SetRadius(sbody->GetMaxChildOrbitalDistance()*1.1); return orbFrame; } StarSystem::BodySuperType supertype = sbody->GetSuperType(); if((supertype == StarSystem::SUPERTYPE_GAS_GIANT) || (supertype == StarSystem::SUPERTYPE_ROCKY_PLANET)) { /* * For planets we want a non-rotating frame for a few radii * and a rotating frame in the same position but with maybe 1.1*radius, * which actually contains the object. */ frameRadius = sbody->GetMaxChildOrbitalDistance()*1.1; orbFrame = new Frame(f, sbody->name.c_str()); orbFrame->m_sbody = sbody; orbFrame->SetRadius(frameRadius ? frameRadius : 10*sbody->GetRadius()); assert(sbody->GetRotationPeriod() != 0); rotFrame = new Frame(orbFrame, sbody->name.c_str()); rotFrame->SetRadius(1.1*sbody->GetRadius()); rotFrame->SetAngVelocity(vector3d(0,2*M_PI/sbody->GetRotationPeriod(), 0)); rotFrame->m_astroBody = b; b->SetFrame(rotFrame); return orbFrame; } else if(supertype == StarSystem::SUPERTYPE_STAR) { /* Stars want a single small non-rotating frame. */ orbFrame = new Frame(f, sbody->name.c_str()); orbFrame->m_sbody = sbody; orbFrame->m_astroBody = b; orbFrame->SetRadius(sbody->GetMaxChildOrbitalDistance()*1.1); b->SetFrame(orbFrame); return orbFrame; } else if(sbody->type == StarSystem::TYPE_STARPORT_ORBITAL) { /* * Space stations want non-rotating frame to some distance * and a much closer rotating frame. */ frameRadius = 1000000.0; /* XXX No idea. */ orbFrame = new Frame(f, sbody->name.c_str()); orbFrame->m_sbody = sbody; orbFrame->SetRadius(frameRadius ? frameRadius : 10*sbody->GetRadius()); assert(sbody->GetRotationPeriod() != 0); rotFrame = new Frame(orbFrame, sbody->name.c_str()); rotFrame->SetRadius(5000.0); rotFrame->SetAngVelocity(vector3d(0,2*M_PI/sbody->GetRotationPeriod(), 0)); b->SetFrame(rotFrame); return orbFrame; } else if(sbody->type == StarSystem::TYPE_STARPORT_SURFACE) { /* * Just put body into rotating frame of planet, not in its own frame * (Because collisions only happen between objects in same frame, * and we want collisions on starport and on planet itself). */ Frame* frame = *f->m_children.begin(); b->SetFrame(frame); b->SetPosition(sbody->orbit.rotMatrix * (frame->m_astroBody->GetRadius()*vector3d(0, 1, 0))); b->SetRotMatrix(sbody->orbit.rotMatrix); return frame; } else { assert(0); } } void Space::GenBody(StarSystem::SBody* sbody, Frame* f) { Body* b = 0; if(sbody->type != StarSystem::TYPE_GRAVPOINT) { if(sbody->GetSuperType() == StarSystem::SUPERTYPE_STAR) { Star* star = new Star(sbody); b = star; } else if(sbody->type == StarSystem::TYPE_STARPORT_ORBITAL) { SpaceStation* ss = new SpaceStation(SpaceStation::JJHOOP); b = ss; } else if(sbody->type == StarSystem::TYPE_STARPORT_SURFACE) { SpaceStation* ss = new SpaceStation(SpaceStation::GROUND_FLAVOURED); b = ss; } else { Planet* planet = new Planet(sbody); b = planet; } b->SetLabel(sbody->name.c_str()); b->SetPosition(vector3d(0,0,0)); AddBody(b); } f = MakeFrameFor(sbody, b, f); for(std::vector::iterator i = sbody->children.begin(); i != sbody->children.end(); ++i) { GenBody(*i, f); } } void Space::BuildSystem(void) { GenBody(L3D::currentSystem->rootBody, rootFrame); MoveOrbitingObjectFrames(rootFrame); } void Space::AddBody(Body* b) { bodies.push_back(b); } void Space::KillBody(Body* const b) { b->MarkDead(); corpses.push_back(b); } void Space::UpdateFramesOfReference(void) { for(std::list::iterator i = bodies.begin(); i != bodies.end(); ++i) { Body* b = *i; if(!b->GetFlags() & Body::FLAG_CAN_MOVE_FRAME) continue; /* Falling out of frames. */ if(!b->GetFrame()->IsLocalPosInFrame(b->GetPosition())) { printf("%s leaves frame %s\n", b->GetLabel().c_str(), b->GetFrame()->GetLabel()); vector3d oldFrameVel = b->GetFrame()->GetVelocity(); Frame* new_frame = b->GetFrame()->m_parent; if(new_frame) { /* Don't fall out of root frame. */ matrix4x4d m = matrix4x4d::Identity(); b->GetFrame()->ApplyLeavingTransform(m); vector3d new_pos = m * b->GetPosition(); matrix4x4d rot; b->GetRotMatrix(rot); b->SetRotMatrix(m * rot); b->SetFrame(new_frame); b->SetPosition(new_pos); /* Get rid of transforms. */ m.ClearToRotOnly(); b->SetVelocity(m*b->GetVelocity() + oldFrameVel); } else { b->SetVelocity(b->GetVelocity() + oldFrameVel); } } /* Entering into frames. */ for(std::list::iterator j = b->GetFrame()->m_children.begin(); j != b->GetFrame()->m_children.end(); ++j) { Frame* child = *j; matrix4x4d m; Frame::GetFrameTransform(b->GetFrame(), child, m); vector3d pos = m * b->GetPosition(); if(child->IsLocalPosInFrame(pos)) { printf("%s enters frame %s\n", b->GetLabel().c_str(), child->GetLabel()); b->SetPosition(pos); b->SetFrame(child); matrix4x4d rot; b->GetRotMatrix(rot); b->SetRotMatrix(m * rot); /* Get rid of transforms. */ m.ClearToRotOnly(); b->SetVelocity(m*b->GetVelocity()-child->GetVelocity()); break; } } } } static bool _OnCollision(dGeomID g1, dGeomID g2, Object* o1, Object* o2, int numContacts, dContact contacts[]) { if((o1->IsType(Object::LASER)) || (o2->IsType(Object::LASER))) { if(o1->IsType(Object::LASER)) { std::swap(o1, o2); std::swap(g1, g2); } Ship::LaserObj* lobj = static_cast(o2); if(o1 == lobj->owner) return false; if(o1->IsType(Object::SHIP)) { DynamicBody* rb = (DynamicBody*)o1; dVector3 start, dir; dGeomRayGet(g2, start, dir); dBodyAddForceAtPos(rb->m_body, 100*dir[0], 100*dir[1], 100*dir[2], contacts[0].geom.pos[0], contacts[0].geom.pos[1], contacts[0].geom.pos[2]); } return false; } else { Body* pb1, *pb2; int flags = 0; /* Geom bodies point to their parents. */ if(o1->IsType(Object::GEOM)) { pb1 = static_cast(o1)->parent; flags |= static_cast(o1)->flags; } else pb1 = static_cast(o1); if(o2->IsType(Object::GEOM)) { pb2 = static_cast(o2)->parent; flags |= static_cast(o2)->flags; } else pb2 = static_cast(o2); if((pb1 && !pb1->OnCollision(pb2, flags)) || (pb2 && !pb2->OnCollision(pb1, flags))) return false; } return true; } static void dump_contact(const dContact* c) { printf("pos %f,%f,%f\n", c->geom.pos[0], c->geom.pos[1], c->geom.pos[2]); printf("normal %f,%f,%f\n", c->geom.normal[0], c->geom.normal[1], c->geom.normal[2]); printf("depth %f\n", c->geom.depth); printf("side1:side2 %d:%d\n", c->geom.side1, c->geom.side2); printf("fdir1 %f,%f,%f\n", c->fdir1[0], c->fdir1[1], c->fdir1[2]); } static bool _OnCollision2(Object* o1, Object* o2, CollisionContact* c) { Body* pb1, *pb2; int flags = c->geomFlag; //printf("Collision flags %x (triIdx %d)\n", flags, c->triIdx); /* Geom bodies point to their parents. */ if(o1->IsType(Object::GEOM)) { pb1 = static_cast(o1)->parent; flags |= static_cast(o1)->flags; } else pb1 = static_cast(o1); if(o2->IsType(Object::GEOM)) { pb2 = static_cast(o2)->parent; flags |= static_cast(o2)->flags; } else pb2 = static_cast(o2); if((pb1 && !pb1->OnCollision(pb2, flags)) || (pb2 && !pb2->OnCollision(pb1, flags))) return false; return true; } #define MAX_CONTACTS 1 static int contact_num; static void hitCallback(CollisionContact* c) { if(contact_num++ >= MAX_CONTACTS) return; printf("AUCH!! %x\n", SDL_GetTicks()); dContact contact; contact.surface.mode = dContactBounce; contact.surface.mu = 0.8; contact.surface.mu2 = 0; contact.surface.bounce = 0.1; contact.surface.bounce_vel = 0.1; contact.geom.pos[0] = c->pos.x; contact.geom.pos[1] = c->pos.y; contact.geom.pos[2] = c->pos.z; contact.geom.pos[3] = 1; contact.geom.normal[0] = c->normal.x; contact.geom.normal[1] = c->normal.y; contact.geom.normal[2] = c->normal.z; contact.geom.normal[3] = 1; contact.geom.depth = c->depth; contact.geom.g1 = 0; contact.geom.g2 = 0; contact.fdir1[0] = 0; contact.fdir1[1] = 0; contact.fdir1[2] = 0; Object* po1 = static_cast(c->userData1); Object* po2 = static_cast(c->userData2); if(!_OnCollision2(po1, po2, c)) return; dBodyID b1 = 0; dBodyID b2 = 0; /* Get the dynamics body for each geom. */ if(po1->IsType(Object::DYNAMICBODY)) b1 = static_cast(po1)->m_body; if(po2->IsType(Object::DYNAMICBODY)) b2 = static_cast(po2)->m_body; dJointID j = dJointCreateContact(Space::world, _contactgroup, &contact); dJointAttach(j, b1, b2); } #if 0 static void nearCallback(void* data, dGeomID oO, dGeomID o1) { /* Create an array of dContact objects to hold the contact joints. */ static const int MAX_CONTACTS = 100; dContact contact[MAX_CONTACTS]; for(int i = 0; i < MAX_CONTACTS; i++) { contact[i].surface.mode = dContactBounce; contact[i].surface.mu = 0.8; contact[i].surface.mu2 = 0; contact[i].surface.bounce = 0.1; contact[i].surface.bounce_vel = 0.1; } if(int numc = dCollide(oO, o1, MAX_CONTACTS, &contact[0].geom, sizeof(dContact))) { Object* po1 = static_cast(dGeomGetData(oO)); Object* po2 = static_cast(dGeomGetData(o1)); if(!_OnCollision(oO, o1, po1, po2, numc, contact)) return; /* Get the dynamics body for each geom. */ dBodyID b1 = dGeomGetBody(oO); dBodyID b2 = dGeomGetBody(o1); /* * To add contact point found to our joint group we call dJointCreateContact * which is just one of the many different types available. */ for(int i = 0; i < numc; i++) { printf("\nODE collision:\n); dump_contact(contact+i); /* * dJointCreateContact needs to know which world and joint group to work * with as well as the dContact object itself. * It returns a new dJointID which we then use with dJointAttach to * finally create the temp contact joint between the two geom bodies. */ //dJointID c = dJointCreateContact(Space::world, _contactgroup, contact + i); #if 0 struct dContactGeom { dVector3 pos; /* Constact position. */ dVector3 normal; /* Normal vector. */ dReal depth; /* Penetration depth. */ dGeomId g1, g2; /* The colliding geoms. */ }; #endif //dJointAttach(c, b1, b2); } } } #endif void Space::CollideFrame(Frame* f) { f->GetCollisionSpace()->Collide(&hitCallback); //dSpaceCollide(f->GetSpaceID(), NULL, &nearCallback); for(std::list::iterator i = f->m_children.begin(); i != f->m_children.end(); ++i) { CollideFrame(*i); } } void Space::ApplyGravity(void) { Body* lump = 0; /* * Gravity is applied when our frame contains an 'astroBody', ie a star or planet, * or when our frame contains a rotating frame which contains this body. */ if(L3D::player->GetFrame()->m_astroBody) { lump = L3D::player->GetFrame()->m_astroBody; } else if(L3D::player->GetFrame()->m_sbody && (L3D::player->GetFrame()->m_children.begin() != L3D::player->GetFrame()->m_children.end())) { lump = (*L3D::player->GetFrame()->m_children.begin())->m_astroBody; } /* Just to the player and only in the most stupid way for the moment. */ if(lump) { vector3d b1b2 = lump->GetPosition() - L3D::player->GetPosition(); const double m1m2 = L3D::player->GetMass() * lump->GetMass(); const double r = b1b2.Length(); const double force = G*m1m2 / (r*r); b1b2.Normalize(); b1b2 = b1b2 * force; dBodyAddForce(L3D::player->m_body, b1b2.x, b1b2.y, b1b2.z); } } void Space::TimeStep(float step) { ApplyGravity(); contact_num = 0; CollideFrame(rootFrame); dWorldQuickStep(world, step); dJointGroupEmpty(_contactgroup); /* TODO: Does not need to be done this often. */ UpdateFramesOfReference(); MoveOrbitingObjectFrames(rootFrame); for(bodiesIter_t i = bodies.begin(); i != bodies.end(); ++i) { (*i)->TimeStepUpdate(step); } PruneCorpses(); } void Space::PruneCorpses(void) { for(bodiesIter_t corpse = corpses.begin(); corpse != corpses.end(); ++corpse) { for(bodiesIter_t i = bodies.begin(); i != bodies.end(); i++) (*i)->NotifyDeath(*corpse); bodies.remove(*corpse); delete *corpse; } corpses.clear(); } struct body_zsort_t { double dist; Body* b; }; struct body_zsort_compare : public std::binary_function { bool operator()(body_zsort_t a, body_zsort_t b) { return a.dist > b.dist; } }; void Space::Render(const Frame* cam_frame) { /* Simple z-sort. */ body_zsort_t* bz = new body_zsort_t[bodies.size()]; int idx = 0; for(std::list::iterator i = bodies.begin(); i != bodies.end(); ++i) { vector3d toBody = (*i)->GetPositionRelTo(cam_frame); bz[idx].dist = toBody.Length(); bz[idx].b = *i; idx++; } sort(bz, bz+bodies.size(), body_zsort_compare()); /* Probably the right place for this when partitioning is done. */ sbreSetDepthRange(L3D::GetScrWidth()*0.5f, 0.0f, 1.0f); for(unsigned int i = 0; i < bodies.size(); i++) { bz[i].b->Render(cam_frame); } delete [] bz; }