//============================================================================== // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Library General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. //============================================================================== //============================================================================== // File: cWorld.cpp // Project: Shooting Star // Author: Jarmo Hekkanen // Copyrights (c) 2003 2ndPoint ry (www.2ndpoint.fi) //------------------------------------------------------------------------------ // Revision history //============================================================================== //============================================================================== // Includes #include "cWorld.hpp" #include #include #include #include "Debug.hpp" #include "cObject.hpp" #include "cRenderable.hpp" #include "cCollidable.hpp" #include "Intersection.hpp" #include "cParticleSystem.hpp" #include "cPositionable.hpp" //------------------------------------------------------------------------------ // Namespaces using namespace ShootingStar; //============================================================================== //! Constructor cWorld::cWorld (void) { // Empty }; //! Destructor cWorld::~cWorld (void) { if ( mObjectList.size () != 0 ) RemoveAllObjects (); }; //! Return singleton cWorld & cWorld::GetInstance (void) { static cWorld singleton; return singleton; } //! Load map from file void cWorld::LoadMap (string filename) { try { dbgInfo ("cWorld") << "Loading map from file " << filename << '\n'; mMap.Load (filename); mWallList = mMap.GetWallList (); dbgInfo ("cWorld") << "Map contains " << mWallList.size () << " walls\n"; //BuildSectors (); } catch ( ... ) { dbgError () << "Exception thrown inside the cWorld::LoadMap!\n"; throw; } } //! Spawn object void cWorld::SpawnObject (cObject *pObject) { dbg::check_ptr (dbg::error, pObject, DBG_HERE); pObject->Spawn (); // Add object to the list pObject->AddReference (); mObjectList.push_back (pObject); // Test if new object is renderable cRenderable *pRenderable = dynamic_cast(pObject); if ( pRenderable != NULL ) { if ( mRenderList.empty () ) mRenderList.push_back (pRenderable); else { list::iterator object = mRenderList.begin (); while ( object != mRenderList.end () ) { if ( (*object)->GetLayer () >= pRenderable->GetLayer () ) break; object++; } if ( object == mRenderList.begin () ) mRenderList.push_front (pRenderable); else if ( object == mRenderList.end () ) mRenderList.push_back (pRenderable); else mRenderList.insert (object, pRenderable); } /*list::iterator object = mRenderList.begin (); while ( object != mRenderList.end () ) { cout << (*object)->GetLayer () << ' '; object++; } cout << endl;*/ } // Test if new object is collidable cCollidable *pCollidable = dynamic_cast(pObject); if ( pCollidable != NULL ) mCollisionList.push_back (pCollidable); } //! Kill object void cWorld::RemoveObject (cObject *pObject) { dbg::check_ptr (dbg::error, pObject, DBG_HERE); // Remove object from the list mObjectList.remove (pObject); // Remove object from the render list if necessary cRenderable *pRenderable = dynamic_cast(pObject); if ( pRenderable != NULL ) mRenderList.remove (pRenderable); // Remove object from the collision list if necessary cCollidable *pCollidable = dynamic_cast(pObject); if ( pCollidable != NULL ) mCollisionList.remove (pCollidable); pObject->ReleaseReference (); } //! Kill all objects void cWorld::RemoveAllObjects (void) { if ( (unsigned int) cObject::GetObjectCount () < mObjectList.size () ) { dbgWarning () << "cWorld::RemoveAllObjects: Object count is less than object list's size. Skipping release phase\n"; } else { // Loop through the object list list::iterator object = mObjectList.begin (); while ( object != mObjectList.end () ) { // Free the object (*object)->Kill (); (*object)->ReleaseReference (); object++; } } // Clear the list of objects mObjectList.clear (); // Clear render list mRenderList.clear (); // Clear collision list mCollisionList.clear (); } //! Return object with matching ID cObject * cWorld::GetObject (ObjectID ID) { // Loop through the object list list::iterator object = mObjectList.begin (); while ( object != mObjectList.end () ) { if ( (*object)->GetID () == ID ) return *object; object++; } return NULL; } //! Render the world void cWorld::Render (Uint32 deltaTime) { GLenum error = glGetError (); while ( error != GL_NO_ERROR ) { dbgError () << "cWorld before rendering: OpenGL error: " << gluErrorString (error) << '\n'; error = glGetError (); } // Render the map mMap.RenderBelow (); // TODO Sorting // TODO Culling glEnable (GL_TEXTURE_2D); // Loop through the object list glPushMatrix (); list::iterator object = mRenderList.begin (); while ( object != mRenderList.end () ) { if ( (*object)->IsAlive () ) { glPushMatrix (); const cVector2f &position = (*object)->GetPosition (); // All objects are rendered to layer 128 glTranslatef (position.mX, position.mY, 0.0f); glRotatef ((*object)->GetRotation () + 90.0f, 0.0f, 0.0f, 1.0f); // Render object (*object)->Render (deltaTime); glPopMatrix (); } object++; } glPopMatrix (); // Render the map mMap.RenderAbove (); //DebugRenderWalls (); error = glGetError (); while ( error != GL_NO_ERROR ) { dbgError () << "cWorld post rendering: OpenGL error: " << gluErrorString (error) << '\n'; error = glGetError (); } } //! Update the world void cWorld::Update (Uint32 deltaTime) { CollisionDetection (deltaTime); // Loop through the object list list::iterator object = mObjectList.begin (), temp; while ( object != mObjectList.end () ) { temp = object; temp++; if ( (*object)->IsAlive () ) (*object)->Update (deltaTime); else RemoveObject (*object); object = temp; } } void cWorld::CollisionDetection (Uint32 deltaTime) { // Loop through the collision list list::iterator object = mCollisionList.begin (), temp; while ( object != mCollisionList.end () ) { temp = object; temp++; // Perform collision detection & response if ( (*object)->IsAlive () ) MoveObject (*object, temp, deltaTime); object++; } } struct tCollisionInfo { bool collision; cVector2f point; float distance; Uint16 wall; }; void cWorld::MoveObject (cCollidable *pObject, list::iterator temp, Uint32 deltaTime) { // THIS IS A MAJOR BOTTLE NECK, FIX IT // THIS CODE SUCKS. !!!REPLACE IT!!! ASAP if ( !pObject->IsAlive () ) return; static cVector2f newPosition; newPosition = pObject->GetPosition () + pObject->GetVelocity () * deltaTime; tCollisionInfo info; info.collision = false; tParticle *pParticles = NULL; int numParticles = 0; cVector2f wall; switch ( pObject->GetCollisionModel () ) { case CollisionModel_Ray: if ( newPosition == pObject->GetPosition () ) break; for ( Uint16 i = 0; i < mWallList.size (); i++ ) { if ( RayRayIntersection (mWallList[i].begin, mWallList[i].end, pObject->GetPosition (), newPosition) != -1.0f ) { info.collision = true; info.wall = i; break; } } break; case CollisionModel_Circle: if ( newPosition == pObject->GetPosition () ) break; for ( Uint16 i = 0; i < mWallList.size (); i++ ) { if ( RayCircleIntersection (mWallList[i].begin, mWallList[i].end, newPosition, pObject->GetCollisionRadius ()) ) { info.collision = true; info.wall = i; break; } } break; case CollisionModel_Particle: pParticles = pObject->GetCollidableParticles (); numParticles = pObject->GetNumberOfParticles (); if ( pParticles == NULL ) break; for ( int i = 0; i < numParticles; i++ ) { if ( !pParticles[i].alive ) continue; // SLOW!!!! for ( Uint16 j = 0; j < mWallList.size (); j++ ) { if ( RayCircleIntersection (mWallList[j].begin, mWallList[j].end, pParticles[i].position + pParticles[i].velocity * deltaTime, pParticles[i].size * 0.5f) ) { // Old sliding code /*float lenght = pParticles[i].velocity.GetLenght (); pParticles[i].velocity.Project (mWallList[j].end - mWallList[j].begin); pParticles[i].velocity = pParticles[i].velocity.Normalize () * lenght;*/ wall = mWallList[j].end - mWallList[j].begin; pParticles[i].velocity = pParticles[i].velocity.GetProjection (wall) - pParticles[i].velocity.GetProjection (wall.GetNormal ()); pParticles[i].velocity *= 0.9f; } } } info.collision = false; // TEMP break; default: break; } if ( pObject->GetCollisionModel () == CollisionModel_Particle ) { // Particle systems can only collide with circles (players etc.) // Loop through the whole collision list because other objects // don't know how to collide with particle systems list::iterator object = mCollisionList.begin (); while ( object != mCollisionList.end () ) { if ( pObject->IsAlive () == false ) break; if ( (*object)->GetID () == pObject->GetID () || (*object)->GetCollisionModel () != CollisionModel_Circle ) { object++; continue; } // SO SLOW!!! for ( int i = 0; i < numParticles; i++ ) { if ( !pParticles[i].alive ) continue; if ( CircleCircleIntersection ((*object)->GetPosition (), (*object)->GetCollisionRadius (), pParticles[i].position + pParticles[i].velocity * deltaTime, pParticles[i].size * 0.5f) ) { pParticles[i].energy = 0; pObject->OnObjectCollision (*object); (*object)->OnObjectCollision (pObject); break; } } object++; } } if ( !info.collision ) { // TEMP Object vs Object collisions (ray vs circle & circle vs circle ) if ( pObject->GetCollisionModel () == CollisionModel_Ray ) { list::iterator object = mCollisionList.begin (); while ( object != mCollisionList.end () ) { if ( (*object)->GetID () == pObject->GetID () ) { object++; continue; } switch ( (*object)->GetCollisionModel () ) { case CollisionModel_Ray: break; case CollisionModel_Circle: if ( RayCircleIntersection (pObject->GetPosition (), newPosition, (*object)->GetPosition (), (*object)->GetCollisionRadius () )) { pObject->OnObjectCollision (*object); (*object)->OnObjectCollision (pObject); } break; default: break; } object++; } } else if ( pObject->GetCollisionModel () == CollisionModel_Circle ) { list::iterator object = temp; while ( object != mCollisionList.end () ) { if ( (*object)->GetID () == pObject->GetID () ) { object++; continue; } switch ( (*object)->GetCollisionModel () ) { case CollisionModel_Circle: if ( CircleCircleIntersection (newPosition, pObject->GetCollisionRadius (), (*object)->GetPosition (), (*object)->GetCollisionRadius () )) { pObject->OnObjectCollision (*object); (*object)->OnObjectCollision (pObject); } break; default: break; } object++; } } pObject->SetPosition (newPosition); } else { cVector2f velocity = pObject->GetVelocity (); switch ( pObject->GetContactModel () ) { case ContactModel_Slide: velocity *= 0.8; velocity.Project (mWallList[info.wall].end - mWallList[info.wall].begin); pObject->SetVelocity (velocity); pObject->OnMapCollision (mWallList[info.wall].begin, mWallList[info.wall].end); MoveObject (pObject, temp, deltaTime); break; case ContactModel_Stop: pObject->SetVelocity (cVector2f (0.0f, 0.0f)); pObject->OnMapCollision (mWallList[info.wall].begin, mWallList[info.wall].end); MoveObject (pObject, temp, deltaTime); break; default: break; } } } //! Select random position & rotation for positionable object void cWorld::RandomPlace (cPositionable *pObject, float range) { // THIS CAN GO TO INFINITE LOOP (TODO: FIXME) dbg::check_ptr (dbg::error, pObject, DBG_HERE); cVector2f position; bool positionOk = false; while ( 1 ) { position.mX = float (mMap.GetWidth ()) * rand () / (RAND_MAX + 1.0f); position.mY = float (mMap.GetHeight ()) * rand () / (RAND_MAX + 1.0f); if ( range != 0.0f ) { if ( (position - cVector2f (100.0f, 100.0f)).GetLenght () < range ) continue; } positionOk = true; for ( Uint16 i = 0; i < mWallList.size (); i++ ) { if ( RayCircleIntersection (mWallList[i].begin, mWallList[i].end, position, 50.0f) ) { positionOk = false; break; } } if ( positionOk ) { pObject->SetPosition (position); pObject->SetRotation (360.0f * rand () / (RAND_MAX + 1.0f)); break; } } } bool cWorld::CollidesWithWalls (cVector2f begin, cVector2f end) { for ( Uint16 i = 0; i < mWallList.size (); i++ ) { if ( RayRayIntersection (mWallList[i].begin, mWallList[i].end, begin, end) != -1.0f ) return true; } return false; } /* Stuff for more advanced collision detection void cWorld::DebugRenderWalls (void) { glDisable (GL_TEXTURE_2D); glColor4f (0.0f, 0.0f, 0.0f, 0.5f); glBegin (GL_QUADS); glVertex2f (0.0f, 0.0f); glVertex2f (0.0f, mMap.GetHeight ()); glVertex2f (mMap.GetWidth (), mMap.GetHeight ()); glVertex2f (mMap.GetWidth (), 0.0f); glEnd (); glLineWidth (3); glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); glBegin (GL_LINES); for ( int y = 0; y < mVSectors; y++ ) { for ( int x = 0; x < mHSectors; x++ ) { glColor3f (1.0f, 1.0f, 1.0f); for ( unsigned int i = 0; i < mSectors[y * mHSectors + x].size (); i++ ) { glVertex2fv ((float*) &mSectors[y * mHSectors + x][i].begin); glVertex2fv ((float*) &mSectors[y * mHSectors + x][i].end); } } } glEnd (); glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); glLineWidth (1); glPointSize (3.0f); glColor4f (1.0f, 1.0f, 0.0f, 1.0f); glBegin (GL_POINTS); for ( Uint16 i = 0; i < mPoints.size (); i++ ) { glVertex2fv ((float*) &mPoints[i]); } glEnd (); glPointSize (1.0f); } void cWorld::BuildSectors (void) { mSectorSize = 128; mHSectors = int (float (mMap.GetWidth ()) / float (mSectorSize) + 0.9999f); mVSectors = int (float (mMap.GetHeight ()) / float (mSectorSize) + 0.9999f); mSectors.clear (); mPoints.clear (); dbgInfo () << "Building " << mHSectors * mVSectors << " sectors (" << mHSectors << 'x' << mVSectors << ")\n"; for ( int y = 0; y < mVSectors; y++ ) { for ( int x = 0; x < mHSectors; x++ ) { mSectors.push_back (vector ()); for ( Uint16 i = 0; i < mWallList.size (); i++ ) { cVector2f borderBegin, borderEnd; float u, u1, u2; u1 = -1.0f; u2 = -1.0f; if ( mWallList[i].begin.mX >= x * mSectorSize && mWallList[i].begin.mX <= (x + 1) * mSectorSize && mWallList[i].begin.mY >= y * mSectorSize && mWallList[i].begin.mY <= (y + 1) * mSectorSize ) u1 = 0.0f; if ( mWallList[i].end.mX >= x * mSectorSize && mWallList[i].end.mX <= (x + 1) * mSectorSize && mWallList[i].end.mY >= y * mSectorSize && mWallList[i].end.mY <= (y + 1) * mSectorSize ) { if ( u1 != -1.0f ) u2 = 1.0f; else u1 = 1.0f; } if ( u1 == -1.0f || u2 == -1.0f ) { // Check left border borderBegin = cVector2f (x * mSectorSize, y * mSectorSize); borderEnd = cVector2f (x * mSectorSize, (y + 1) * mSectorSize); u = RayRayIntersection (mWallList[i].begin, mWallList[i].end, borderBegin, borderEnd); if ( u != -1.0f ) { if ( u1 != -1.0f && u != u1 ) u2 = u; else if ( u != u2 ) u1 = u; mPoints.push_back (mWallList[i].begin + (mWallList[i].end - mWallList[i].begin) * u); } } if ( u1 == -1.0f || u2 == -1.0f ) { // Check right border borderBegin = cVector2f ((x + 1) * mSectorSize, y * mSectorSize); borderEnd = cVector2f ((x + 1) * mSectorSize, (y + 1) * mSectorSize); u = RayRayIntersection (mWallList[i].begin, mWallList[i].end, borderBegin, borderEnd); if ( u != -1.0f && u != u1 ) { if ( u1 != -1.0f && u != u1 ) u2 = u; else if ( u != u2 ) u1 = u; mPoints.push_back (mWallList[i].begin + (mWallList[i].end - mWallList[i].begin) * u); } } if ( u1 == -1.0f || u2 == -1.0f ) { // Check top border borderBegin = cVector2f (x * mSectorSize, y * mSectorSize); borderEnd = cVector2f ((x + 1) * mSectorSize, y * mSectorSize); u = RayRayIntersection (mWallList[i].begin, mWallList[i].end, borderBegin, borderEnd); if ( u != -1.0f && u != u1 ) { if ( u1 != -1.0f && u != u1 ) u2 = u; else if ( u != u2 ) u1 = u; mPoints.push_back (mWallList[i].begin + (mWallList[i].end - mWallList[i].begin) * u); } } if ( u1 == -1.0f || u2 == -1.0f ) { // Check bottom border borderBegin = cVector2f (x * mSectorSize, (y + 1) * mSectorSize); borderEnd = cVector2f ((x + 1) * mSectorSize, (y + 1) * mSectorSize); u = RayRayIntersection (mWallList[i].begin, mWallList[i].end, borderBegin, borderEnd); if ( u != -1.0f && u != u1 ) { if ( u1 != -1.0f && u != u1 ) u2 = u; else if ( u != u2 ) u1 = u; mPoints.push_back (mWallList[i].begin + (mWallList[i].end - mWallList[i].begin) * u); } } if ( u1 != -1.0f && u2 != -1.0f && u1 != u2 ) { tWall newWall; newWall.begin = mWallList[i].begin + (mWallList[i].end - mWallList[i].begin) * u1; newWall.end = mWallList[i].begin + (mWallList[i].end - mWallList[i].begin) * u2; newWall.flags = mWallList[i].flags; mSectors[y * mHSectors + x].push_back (newWall); } // dbgInfo () << "Wall:" << i << ' ' << mWallList[i].begin.mX << ' ' << mWallList[i].begin.mY // << ' ' << mWallList[i].end.mX << ' ' << mWallList[i].end.mY << " u1:" << u1 << " u2:" << u2 << '\n'; } } } dbgInfo () << "Found " << mPoints.size () << " intersections\n"; }*/ //============================================================================== // EOF //==============================================================================