Main Page   Namespace List   Class Hierarchy   Compound List   File List   Namespace Members   Compound Members   File Members  

world.cxx

Go to the documentation of this file.
00001 //  $Id: world.cxx,v 1.31 2003/01/11 19:07:48 grumbel Exp $
00002 //
00003 //  Construo - A wire-frame construction game
00004 //  Copyright (C) 2002 Ingo Ruhnke <grumbel@gmx.de>
00005 //
00006 //  This program is free software; you can redistribute it and/or
00007 //  modify it under the terms of the GNU General Public License
00008 //  as published by the Free Software Foundation; either version 2
00009 //  of the License, or (at your option) any later version.
00010 //
00011 //  This program is distributed in the hope that it will be useful,
00012 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014 //  GNU General Public License for more details.
00015 //
00016 //  You should have received a copy of the GNU General Public License
00017 //  along with this program; if not, write to the Free Software
00018 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00019 
00020 #include <assert.h>
00021 #include <algorithm>
00022 #include "config.h"
00023 
00024 #ifdef HAVE_LIBZ
00025 #  include <zlib.h>
00026 #endif
00027 
00028 #include "math.hxx"
00029 #include "construo_error.hxx"
00030 #include "world.hxx"
00031 #include "particle_factory.hxx"
00032 #include "system_context.hxx"
00033 #include "controller.hxx"
00034 #include "rect.hxx"
00035 #include "rect_collider.hxx"
00036 #include "string_utils.hxx"
00037 
00038 World* World::current_world = 0; 
00039 
00040 World::World ()
00041   : particle_mgr (new ParticleFactory(this))
00042 {
00043   file_version = 0;
00044   has_been_run = false;
00045 }
00046 
00047 World::World (const std::string& filename)
00048   : particle_mgr (0)
00049 {
00050   std::cout << "World: Trying to load: " << filename << std::endl;
00051   file_version = 0;
00052 
00053   has_been_run = false;
00054   lisp_object_t* root_obj = 0;
00055   
00056   // Try to read a file and store the content in root_obj
00057   if (StringUtils::has_suffix(filename, ".construo.gz"))
00058     {
00059 #ifdef HAVE_LIBZ
00060       lisp_stream_t stream;
00061       int chunk_size = 128 * 1024; // allocate 256kb, should be enough for most levels
00062       char* buf;
00063       int buf_pos = 0;
00064       int try_number = 1;
00065       bool done = false;
00066 
00067       buf = static_cast<char*>(malloc(chunk_size));
00068       if (!buf)
00069         {
00070           throw ConstruoError ("World: Out of memory while opening " + filename);
00071         }
00072 
00073       gzFile in = gzopen(system_context->translate_filename(filename).c_str (), "rb");
00074 
00075       while (!done)
00076         {
00077           int ret = gzread(in, buf + buf_pos, chunk_size);
00078           if (ret == -1)
00079             {
00080               free (buf);
00081               throw ConstruoError ("World: Out of memory while opening " + filename);
00082             }
00083           else if (ret == chunk_size) // buffer got full, eof not yet there
00084             {
00085               std::cout << "World: Read buffer to small, allocating more space" << std::endl;
00086 
00087               buf_pos = chunk_size * try_number;
00088               try_number += 1;
00089               buf = static_cast<char*>(realloc(buf, chunk_size * try_number));
00090 
00091               if (!buf)
00092                 {
00093                   throw ConstruoError ("World: Out of memory while opening " + filename);
00094                 }
00095             }
00096           else // (ret < chunk_size)
00097             {
00098               // everything fine, encountered EOF 
00099               done = true;
00100             }
00101         }
00102       
00103       lisp_stream_init_string (&stream, buf);
00104       root_obj = lisp_read (&stream);
00105       
00106       free(buf);
00107       gzclose(in);
00108 #else
00109       throw ConstruoError ("World: Reading of compressed files not supported, recompile with zlib support or extract the levelfile manually, " + filename);
00110 #endif
00111     }
00112   else
00113     {
00114       lisp_stream_t stream;
00115       FILE* in = system_context->open_input_file(filename);
00116       if (!in)
00117         {
00118           throw ConstruoError ("World: Couldn't open " + filename);
00119           return;
00120         }
00121       lisp_stream_init_file (&stream, in);
00122       root_obj = lisp_read (&stream);
00123     }
00124   
00125   if (root_obj->type == LISP_TYPE_EOF || root_obj->type == LISP_TYPE_PARSE_ERROR)
00126     {
00127       std::cout << "World: Parse Error in file " << filename << std::endl;
00128     }
00129 
00130   lisp_object_t* cur = lisp_car(root_obj);
00131   
00132   if (!lisp_symbol_p (cur))
00133     {
00134       throw ConstruoError ("World: Read error in " + filename);
00135     }
00136   
00137   if (strcmp(lisp_symbol(cur), "construo-scene") == 0)
00138     {
00139       parse_scene (lisp_cdr(root_obj));
00140     }
00141   else
00142     {
00143       throw ConstruoError ("World: Read error in " + filename + ". Couldn't find 'construo-scene'");
00144     }
00145   
00146   lisp_free (root_obj);
00147 
00148   ConstruoAssert(particle_mgr, "No Particles given in file, load failed");
00149 
00150   //std::cout << "particles: " << particle_mgr->size () << std::endl;
00151   //std::cout << "springs:   " << springs.size () << std::endl;
00152 }
00153 
00154 void
00155 World::parse_scene (lisp_object_t* cursor)
00156 {
00157   while(!lisp_nil_p(cursor))
00158     {
00159       lisp_object_t* cur = lisp_car(cursor);
00160 
00161       if (!lisp_cons_p(cur) || !lisp_symbol_p (lisp_car(cur)))
00162         {
00163           throw ConstruoError ("World: Read error in parse_scene");
00164         }
00165       else
00166         {
00167           if (strcmp(lisp_symbol(lisp_car(cur)), "particles") == 0)
00168             {
00169               parse_particles(lisp_cdr(cur));
00170             }
00171           else if (strcmp(lisp_symbol(lisp_car(cur)), "springs") == 0)
00172             {
00173               parse_springs(lisp_cdr(cur));
00174             }
00175           else if (strcmp(lisp_symbol(lisp_car(cur)), "colliders") == 0)
00176             {
00177               parse_colliders(lisp_cdr(cur));
00178             }
00179           else if (strcmp(lisp_symbol(lisp_car(cur)), "version") == 0)
00180             {
00181               file_version = lisp_integer(lisp_car(lisp_cdr(cur)));
00182             }
00183           else
00184             {
00185               std::cout << "World: Read error in parse_scene. Unhandled tag '" 
00186                         << lisp_symbol(lisp_car(cur)) << "' skipping and continuing" << std::endl;
00187             }
00188         }
00189       cursor = lisp_cdr (cursor);
00190     }
00191 }
00192 
00193 void
00194 World::parse_springs (lisp_object_t* cursor)
00195 {
00196   while(!lisp_nil_p(cursor))
00197     {
00198       lisp_object_t* cur = lisp_car(cursor);
00199       springs.push_back(new Spring (this, cur));
00200       cursor = lisp_cdr (cursor);
00201     }  
00202 }
00203 
00204 void
00205 World::parse_colliders (lisp_object_t* cursor)
00206 {
00207   while(!lisp_nil_p(cursor))
00208     {
00209       lisp_object_t* cur = lisp_car(cursor);
00210       if (strcmp(lisp_symbol(lisp_car(cur)), "rect") == 0)
00211         {
00212           colliders.push_back(new RectCollider(lisp_cdr(cur)));
00213         }
00214       else
00215         {
00216           std::cout << "WARNING: Unknown collider type '" << lisp_symbol(lisp_car(cur))
00217                     << "' skipping" << std::endl;
00218         }
00219       cursor = lisp_cdr (cursor);
00220     }
00221 }
00222 
00223 void
00224 World::parse_particles (lisp_object_t* cursor)
00225 {
00226   particle_mgr = new ParticleFactory(this, cursor);
00227 }
00228 
00229 // Copy Constructor
00230 World::World (const World& old_world)
00231 {
00232   file_version = 0;
00233 
00234   for (Colliders::const_iterator i = old_world.colliders.begin(); 
00235        i != old_world.colliders.end();
00236        ++i)
00237     {
00238       colliders.push_back((*i)->duplicate());
00239     }
00240 
00241   // FIXME: Could need optimizations
00242   particle_mgr = new ParticleFactory (this, *old_world.particle_mgr);
00243   
00244   for (CSpringIter i = old_world.springs.begin (); i != old_world.springs.end (); ++i)
00245     {
00246       Particle* first  = particle_mgr->lookup_particle((*i)->particles.first->get_id());
00247       Particle* second = particle_mgr->lookup_particle((*i)->particles.second->get_id());
00248 
00249       if (first && second)
00250         {
00251           // FIXME: Use copy c'tor here maxstiffnes and Co. aren't copied correctly
00252           springs.push_back (new Spring (first, second, (*i)->length));
00253         }
00254       else
00255         {
00256           std::cout << "World: Error couldn't resolve particles" << std::endl;
00257         }
00258     }
00259 }
00260 
00261 World::~World ()
00262 { 
00263   clear ();
00264 }
00265 
00266 void
00267 World::draw (ZoomGraphicContext* gc)
00268 {
00269   current_world = this;
00270 
00271   draw_colliders(gc);
00272   draw_springs(gc);
00273   draw_particles(gc);
00274 }
00275 
00276 void 
00277 World::draw_springs(ZoomGraphicContext* gc)
00278 {
00279 #ifdef NEW_SPRING_CODE
00280   std::vector<GraphicContext::Line> lines (springs.size());
00281 
00282   Vector2d dist = springs[0]->particles.first->pos - springs[0]->particles.second->pos;
00283   float stretch = fabs(dist.norm ()/springs[0]->length - 1.0f) * 10.0f; 
00284   float color = fabs((stretch/springs[0]->max_stretch));
00285 
00286   for (unsigned int i = 0; i < springs.size(); ++i)
00287     {
00288       //(*i)->draw (gc);
00289       lines[i].x1 = springs[i]->particles.first->pos.x;
00290       lines[i].y1 = springs[i]->particles.first->pos.y;
00291       lines[i].x2 = springs[i]->particles.second->pos.x;
00292       lines[i].y2 = springs[i]->particles.second->pos.y;
00293     }
00294   gc->draw_lines (lines, Color(color, 1.0f - color, 0.0f), 2);
00295 #else
00296   for (SpringIter i = springs.begin(); i != springs.end(); ++i)
00297     {
00298       (*i)->draw (gc);
00299     }
00300 #endif
00301 }
00302 
00303 void 
00304 World::draw_particles(ZoomGraphicContext* gc)
00305 {
00306   particle_mgr->draw(gc);
00307 }
00308 
00309 void
00310 World::draw_colliders(ZoomGraphicContext* gc)
00311 {
00312   for (Colliders::iterator i = colliders.begin (); i != colliders.end (); ++i)
00313     {
00314       (*i)->draw(gc);
00315     }
00316 }
00317 
00318 void
00319 World::update (float delta)
00320 {
00321   current_world = this;
00322 
00323   has_been_run = true;
00324 
00325   // Main Movement and Forces
00326   // FIXME: Hardcoded Force Emitters
00327   for (ParticleFactory::ParticleIter i = particle_mgr->begin (); i != particle_mgr->end (); ++i)
00328     {
00329       // Gravity
00330       (*i)->add_force (Vector2d (0.0, 15.0f) * (*i)->get_mass ());
00331                     
00332       // Central Gravity force:
00333       /*Vector2d direction = ((*i)->pos - Vector2d (400, 300));
00334         if (direction.norm () != 0.0f)
00335         (*i)->add_force (direction * (-100.0f/(direction.norm () * direction.norm ())));
00336       */
00337             
00338       /*
00339         for (ParticleIter j = particles.begin (); j != particles.end (); ++j)
00340         {
00341         Vector2d diff = (*j)->pos - (*i)->pos;
00342         if (diff.norm () != 0.0f)
00343         (*i)->add_force (diff * ((10.0f - (*j)->mass)/(diff.norm () * diff.norm ())));
00344         }           */
00345     }
00346 
00347   for (SpringIter i = springs.begin (); i != springs.end (); ++i)
00348     (*i)->update (delta);
00349   
00350   particle_mgr->update(delta);
00351   
00352   //std::cout << "Colliders: " << colliders.size () << std::endl;
00353   for (Colliders::iterator i = colliders.begin (); i != colliders.end (); ++i)
00354     (*i)->bounce ();
00355 
00356   // Spring splitting
00357   std::vector<Spring*> new_springs;
00358   for (SpringIter i = springs.begin (); i != springs.end (); ++i)
00359     {
00360       if ((*i)->destroyed)
00361         {
00362           if ((*i)->length > 20.0f)
00363             {
00364               // Calc midpoint
00365               Vector2d pos = ((*i)->particles.first->pos
00366                                + (*i)->particles.second->pos) * 0.5f;
00367 
00368               // FIXME: particle mass needs to be recalculated
00369               Particle* p1 = particle_mgr->add_particle (pos, (*i)->particles.first->velocity * 0.5f, .1f);
00370               Particle* p2 = particle_mgr->add_particle (pos, (*i)->particles.second->velocity * 0.5f, .1f);
00371 
00372               // FIXME: Insert a more sofistikated string splitter here
00373               new_springs.push_back (new Spring ((*i)->particles.first, p1, (*i)->length/2));
00374               new_springs.push_back (new Spring ((*i)->particles.second, p2, (*i)->length/2));
00375             }
00376         }
00377     }
00378   springs.insert(springs.end(), new_springs.begin(), new_springs.end ());
00379 
00380   // Remove any springs that are marked as destroyed
00381   // FIXME: Could be faster
00382   for (SpringIter i = springs.begin (); i != springs.end ();)
00383     {
00384       if ((*i)->destroyed)
00385         {
00386           delete *i;
00387           i = springs.erase(i);
00388         }
00389       else
00390         {
00391           ++i;
00392         }
00393     }
00394 }
00395 
00396 Spring*
00397 World::get_spring (float x, float y)
00398 {
00399   Spring* spring = 0;
00400   float min_distance = 0.0f;
00401 
00402   float capture_threshold = 15;
00403 
00404   for (SpringIter i = springs.begin (); i != springs.end (); ++i)
00405     {
00406       float x0 = x;
00407       float y0 = y;
00408       float& x1 = (*i)->particles.first->pos.x;
00409       float& y1 = (*i)->particles.first->pos.y;
00410       float& x2 = (*i)->particles.second->pos.x;
00411       float& y2 = (*i)->particles.second->pos.y;
00412 
00413       // FIXME: optimize me
00414       float u = (((x0 - x1)*(x2-x1) + (y0 - y1)*(y2 - y1))
00415                  / ((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)));
00416       
00417       float distance = (fabs((x2 - x1)*(y1-y0) - (x1-x0)*(y2-y1))
00418                         / sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)));
00419       
00420       if (u >= 0 && u <= 1.0f
00421           && ((spring && min_distance > distance)
00422               || (!spring && distance <= capture_threshold))) // FIXME: threashold is dependend on view
00423         {
00424           spring = *i;
00425           min_distance = distance;
00426         }
00427     }
00428 
00429   return spring;
00430 }
00431 
00432 Particle* 
00433 World::get_particle (float x, float y)
00434 {
00435   Particle* particle = 0;
00436   float min_dist = 15;
00437   Vector2d mouse_pos (x, y);
00438 
00439   for (ParticleFactory::ParticleIter i = particle_mgr->begin (); i != particle_mgr->end (); ++i)
00440     {
00441       Vector2d diff = mouse_pos - (*i)->pos;
00442       if (diff.norm () < min_dist)
00443         {
00444           min_dist = diff.norm ();
00445           particle = *i;
00446         }
00447     }
00448 
00449   return particle;
00450 }
00451 
00452 std::vector<Particle*> 
00453 World::get_particles (float x1_, float y1_, float x2_, float y2_)
00454 {
00455   float x1 = Math::min(x1_, x2_);
00456   float x2 = Math::max(x1_, x2_);
00457   float y1 = Math::min(y1_, y2_);
00458   float y2 = Math::max(y1_, y2_);
00459 
00460   std::vector<Particle*> caputred_particles;
00461   for (ParticleFactory::ParticleIter i = particle_mgr->begin (); i != particle_mgr->end (); ++i)
00462     {
00463       if ((*i)->pos.x >= x1 && (*i)->pos.x < x2
00464           && (*i)->pos.y >= y1 && (*i)->pos.y < y2)
00465         caputred_particles.push_back(*i);
00466     }  
00467   return caputred_particles;
00468 }
00469 
00470 void 
00471 World::zero_out_velocity ()
00472 {
00473   std::cout << "Setting velocity to zero" << std::endl;
00474   for (ParticleFactory::ParticleIter i = get_particle_mgr()->begin(); 
00475        i != get_particle_mgr()->end (); ++i)
00476     {
00477       (*i)->velocity = Vector2d ();
00478     }
00479 }
00480 
00481 void
00482 World::add_spring (Particle* last_particle, Particle* particle)
00483 {
00484   assert (last_particle && particle);
00485   springs.push_back (new Spring (last_particle, particle));
00486 }
00487 
00488 void
00489 World::remove_particle (Particle* p)
00490 {
00491   // Remove everyting that references the particle
00492   for (SpringIter i = springs.begin (); i != springs.end ();)
00493     {
00494       if ((*i)->particles.first == p || (*i)->particles.second == p)
00495         {
00496           delete *i;
00497           // FIXME: this is potentially slow, since we don't care
00498           // about order, we could speed this up
00499           i = springs.erase(i);
00500         }
00501       else
00502         {
00503           ++i;
00504         }
00505     }
00506 
00507   particle_mgr->remove_particle(p);
00508 }
00509 
00510 void
00511 World::remove_spring (Spring* s)
00512 {
00513   std::cout << "particles: " << particle_mgr->size () << std::endl;
00514   std::cout << "springs:   " << springs.size () << std::endl;
00515 
00516   delete s;
00517   springs.erase(std::remove(springs.begin (), springs.end (), s), 
00518                 springs.end ());
00519 }
00520 
00521 void
00522 World::remove_collider (Collider* c)
00523 {
00524   delete c;
00525   colliders.erase(std::remove(colliders.begin (), colliders.end (), c), 
00526                   colliders.end ());  
00527 }
00528 
00529 void
00530 World::clear ()
00531 {
00532   particle_mgr->clear();
00533  
00534   for (SpringIter i = springs.begin (); i != springs.end (); ++i)
00535     delete *i;
00536 
00537   springs.clear ();
00538 }
00539 
00540 void
00541 World::write_lisp (const std::string& filename)
00542 {
00543   FILE* out;
00544 
00545   out = system_context->open_output_file(filename);
00546 
00547   if (!out)
00548     {
00549       std::cout << "World: Couldn't open '" << filename << "' for writing" << std::endl;
00550       return; 
00551     }
00552 
00553   std::cout << "Writing to: " << filename << std::endl;
00554 
00555   fputs(";; Written by " PACKAGE_STRING "\n", out);
00556   fputs("(construo-scene\n", out);
00557   fputs("  (version 3)\n", out);
00558 
00559   // FIXME: insert creation date here
00560   // FIXME: Filter '()"' here
00561   fprintf(out, "  (author \"%s\" \"%s\")\n", 
00562           system_context->get_user_realname().c_str(),
00563           system_context->get_user_email().c_str());
00564 
00565   particle_mgr->write_lisp(out);
00566 
00567 
00568   fputs("  (springs\n", out);
00569   for (CSpringIter i = springs.begin (); i != springs.end (); ++i)
00570     {
00571       lisp_object_t* obj = (*i)->serialize ();
00572       fputs("    ", out);
00573       lisp_dump (obj, out);
00574       fputc('\n', out);
00575       lisp_free(obj);
00576     }
00577   fputs("  )\n", out);
00578 
00579   fputs ("  (colliders\n", out);
00580   for (Colliders::iterator i = colliders.begin(); i != colliders.end(); ++i)
00581     {
00582       lisp_object_t* obj = (*i)->serialize ();
00583       fputs("    ", out);
00584       lisp_dump (obj, out);
00585       fputc('\n', out);
00586       lisp_free(obj);
00587     }
00588   fputs("  )", out);
00589 
00590 
00591   fputs(")\n\n;; EOF ;;\n", out);
00592 
00593   fclose(out);
00594 
00595   if (StringUtils::has_suffix(filename, ".gz"))
00596     { // Rewrite file compressed
00597       std::cout << "World: Filename ends with .gz, rewriting " << filename << " compressed" << std::endl;
00598 
00599       int len = 512*1024;
00600       int read_len;
00601       char* buf;
00602       buf = static_cast<char*>(malloc(len));
00603       if (!buf)
00604         {
00605           throw ConstruoError("Out of memory");
00606         }
00607       FILE* in = system_context->open_input_file(filename);
00608       read_len = fread (buf, sizeof (char), len, in);
00609       if (len >= read_len)
00610         {
00611           throw ConstruoError("World: Internal error, read buffer to small");
00612         }
00613       fclose (in);
00614       
00615       // Write the buffer in compressed format
00616       gzFile out = gzopen(system_context->translate_filename(filename).c_str(), "wb");
00617       gzwrite (out, buf, len);
00618       gzclose (out);
00619       free (buf);
00620     }
00621 }
00622 
00623 WorldBoundingBox
00624 World::calc_bounding_box()
00625 {
00626   WorldBoundingBox bbox;
00627 
00628   if (particle_mgr->size() > 0)
00629     {
00630       bbox.x1 = bbox.x2 = (*particle_mgr->begin ())->pos.x;
00631       bbox.y1 = bbox.y2 = (*particle_mgr->begin ())->pos.y;
00632     }
00633   else
00634     {
00635       bbox.x1 = 0;
00636       bbox.y1 = 0;
00637 
00638       bbox.x2 = 800;
00639       bbox.y2 = 600;
00640     }
00641 
00642   for (ParticleFactory::ParticleIter i = particle_mgr->begin (); i != particle_mgr->end (); ++i)
00643     {
00644       bbox.x1 = Math::min(bbox.x1, (*i)->pos.x);
00645       bbox.y1 = Math::min(bbox.y1, (*i)->pos.y);
00646 
00647       bbox.x2 = Math::max(bbox.x2, (*i)->pos.x);
00648       bbox.y2 = Math::max(bbox.y2, (*i)->pos.y);
00649     }
00650 
00651   return bbox;
00652 }
00653 
00654 int
00655 World::get_num_particles()
00656 {
00657   return particle_mgr->size ();
00658 }
00659 
00660 int
00661 World::get_num_springs()
00662 {
00663   return springs.size ();
00664 }
00665 
00666 void
00667 World::add_rect_collider(const Vector2d& pos1, const Vector2d& pos2)
00668 {
00669   Rect<float> rect (pos1.x, pos1.y, pos2.x, pos2.y);
00670 
00671   colliders.push_back(new RectCollider(rect.x1, rect.y1, rect.x2, rect.y2));
00672 }
00673 
00674 /* EOF */

Generated on Thu Jul 24 10:24:31 2003 for Construo by doxygen1.3-rc3