Rebuild autotool system
[construo.git] / world.cxx
blob5b4e298a07f73a30cc40e0fb6d5d150082283b88
1 // $Id: world.cxx,v 1.35 2003/07/28 20:57:41 grumbel Exp $
2 //
3 // Construo - A wire-frame construction game
4 // Copyright (C) 2002 Ingo Ruhnke <grumbel@gmx.de>
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 #include <assert.h>
21 #include <algorithm>
22 #include "config.h"
24 #ifdef HAVE_LIBZ
25 # include <zlib.h>
26 #endif
28 #include "math.hxx"
29 #include "construo_error.hxx"
30 #include "world.hxx"
31 #include "particle_factory.hxx"
32 #include "system_context.hxx"
33 #include "controller.hxx"
34 #include "rect.hxx"
35 #include "rect_collider.hxx"
36 #include "string_utils.hxx"
37 #include <string.h>
39 World* World::current_world = 0;
41 World::World ()
42 : particle_mgr (new ParticleFactory(this))
44 file_version = 0;
45 has_been_run = false;
48 World::World (const std::string& filename)
49 : particle_mgr (0)
51 std::cout << "World: Trying to load: " << filename << std::endl;
52 file_version = 0;
54 has_been_run = false;
55 lisp_object_t* root_obj = 0;
57 // Try to read a file and store the content in root_obj
58 if (StringUtils::has_suffix(filename, ".construo.gz"))
60 #ifdef HAVE_LIBZ
61 lisp_stream_t stream;
62 int chunk_size = 128 * 1024; // allocate 256kb, should be enough for most levels
63 char* buf;
64 int buf_pos = 0;
65 int try_number = 1;
66 bool done = false;
68 buf = static_cast<char*>(malloc(chunk_size));
69 if (!buf)
71 throw ConstruoError ("World: Out of memory while opening " + filename);
74 gzFile in = gzopen(system_context->translate_filename(filename).c_str (), "rb");
76 while (!done)
78 int ret = gzread(in, buf + buf_pos, chunk_size);
79 if (ret == -1)
81 free (buf);
82 throw ConstruoError ("World: Out of memory while opening " + filename);
84 else if (ret == chunk_size) // buffer got full, eof not yet there
86 std::cout << "World: Read buffer to small, allocating more space" << std::endl;
88 buf_pos = chunk_size * try_number;
89 try_number += 1;
90 buf = static_cast<char*>(realloc(buf, chunk_size * try_number));
92 if (!buf)
94 throw ConstruoError ("World: Out of memory while opening " + filename);
97 else // (ret < chunk_size)
99 // everything fine, encountered EOF
100 done = true;
104 lisp_stream_init_string (&stream, buf);
105 root_obj = lisp_read (&stream);
107 free(buf);
108 gzclose(in);
109 #else
110 throw ConstruoError ("World: Reading of compressed files not supported, recompile with zlib support or extract the levelfile manually, " + filename);
111 #endif
113 else
115 lisp_stream_t stream;
116 FILE* in = system_context->open_input_file(filename);
117 if (!in)
119 throw ConstruoError ("World: Couldn't open " + filename);
120 return;
122 lisp_stream_init_file (&stream, in);
123 root_obj = lisp_read (&stream);
126 if (root_obj->type == LISP_TYPE_EOF || root_obj->type == LISP_TYPE_PARSE_ERROR)
128 std::cout << "World: Parse Error in file " << filename << std::endl;
131 lisp_object_t* cur = lisp_car(root_obj);
133 if (!lisp_symbol_p (cur))
135 throw ConstruoError ("World: Read error in " + filename);
138 if (strcmp(lisp_symbol(cur), "construo-scene") == 0)
140 parse_scene (lisp_cdr(root_obj));
142 else
144 throw ConstruoError ("World: Read error in " + filename + ". Couldn't find 'construo-scene'");
147 lisp_free (root_obj);
149 ConstruoAssert(particle_mgr, "No Particles given in file, load failed");
151 //std::cout << "particles: " << particle_mgr->size () << std::endl;
152 //std::cout << "springs: " << springs.size () << std::endl;
155 void
156 World::parse_scene (lisp_object_t* cursor)
158 while(!lisp_nil_p(cursor))
160 lisp_object_t* cur = lisp_car(cursor);
162 if (!lisp_cons_p(cur) || !lisp_symbol_p (lisp_car(cur)))
164 throw ConstruoError ("World: Read error in parse_scene");
166 else
168 if (strcmp(lisp_symbol(lisp_car(cur)), "particles") == 0)
170 parse_particles(lisp_cdr(cur));
172 else if (strcmp(lisp_symbol(lisp_car(cur)), "springs") == 0)
174 parse_springs(lisp_cdr(cur));
176 else if (strcmp(lisp_symbol(lisp_car(cur)), "colliders") == 0)
178 parse_colliders(lisp_cdr(cur));
180 else if (strcmp(lisp_symbol(lisp_car(cur)), "version") == 0)
182 file_version = lisp_integer(lisp_car(lisp_cdr(cur)));
184 else
186 std::cout << "World: Read error in parse_scene. Unhandled tag '"
187 << lisp_symbol(lisp_car(cur)) << "' skipping and continuing" << std::endl;
190 cursor = lisp_cdr (cursor);
194 void
195 World::parse_springs (lisp_object_t* cursor)
197 while(!lisp_nil_p(cursor))
199 lisp_object_t* cur = lisp_car(cursor);
200 springs.push_back(new Spring (this, cur));
201 cursor = lisp_cdr (cursor);
205 void
206 World::parse_colliders (lisp_object_t* cursor)
208 while(!lisp_nil_p(cursor))
210 lisp_object_t* cur = lisp_car(cursor);
211 if (strcmp(lisp_symbol(lisp_car(cur)), "rect") == 0)
213 colliders.push_back(new RectCollider(lisp_cdr(cur)));
215 else
217 std::cout << "WARNING: Unknown collider type '" << lisp_symbol(lisp_car(cur))
218 << "' skipping" << std::endl;
220 cursor = lisp_cdr (cursor);
224 void
225 World::parse_particles (lisp_object_t* cursor)
227 particle_mgr = new ParticleFactory(this, cursor);
230 // Copy Constructor
231 World::World (const World& old_world)
233 file_version = 0;
235 for (Colliders::const_iterator i = old_world.colliders.begin();
236 i != old_world.colliders.end();
237 ++i)
239 colliders.push_back((*i)->duplicate());
242 // FIXME: Could need optimizations
243 particle_mgr = new ParticleFactory (this, *old_world.particle_mgr);
245 for (CSpringIter i = old_world.springs.begin (); i != old_world.springs.end (); ++i)
247 Particle* first = particle_mgr->lookup_particle((*i)->particles.first->get_id());
248 Particle* second = particle_mgr->lookup_particle((*i)->particles.second->get_id());
250 if (first && second)
252 // FIXME: Use copy c'tor here maxstiffnes and Co. aren't copied correctly
253 springs.push_back (new Spring (first, second, (*i)->length));
255 else
257 std::cout << "World: Error couldn't resolve particles" << std::endl;
262 World::~World ()
264 clear ();
267 void
268 World::draw (ZoomGraphicContext* gc)
270 // FIXME: This is *not* used in the WorldViewComponent!
272 current_world = this;
274 draw_colliders(gc);
275 draw_springs(gc);
276 draw_particles(gc);
279 void
280 World::draw_springs(ZoomGraphicContext* gc)
282 #ifdef NEW_SPRING_CODE
283 std::vector<GraphicContext::Line> lines (springs.size());
285 Vector2d dist = springs[0]->particles.first->pos - springs[0]->particles.second->pos;
286 float stretch = fabs(dist.norm ()/springs[0]->length - 1.0f) * 10.0f;
287 float color = fabs((stretch/springs[0]->max_stretch));
289 for (unsigned int i = 0; i < springs.size(); ++i)
291 //(*i)->draw (gc);
292 lines[i].x1 = springs[i]->particles.first->pos.x;
293 lines[i].y1 = springs[i]->particles.first->pos.y;
294 lines[i].x2 = springs[i]->particles.second->pos.x;
295 lines[i].y2 = springs[i]->particles.second->pos.y;
297 gc->draw_lines (lines, Color(color, 1.0f - color, 0.0f), 2);
298 #else
299 for (SpringIter i = springs.begin(); i != springs.end(); ++i)
301 (*i)->draw (gc);
303 #endif
306 void
307 World::draw_particles(ZoomGraphicContext* gc)
309 particle_mgr->draw(gc);
312 void
313 World::draw_colliders(ZoomGraphicContext* gc)
315 for (Colliders::iterator i = colliders.begin (); i != colliders.end (); ++i)
317 (*i)->draw(gc);
321 void
322 World::update (float delta)
324 current_world = this;
326 has_been_run = true;
328 // Main Movement and Forces
329 // FIXME: Hardcoded Force Emitters
330 for (ParticleFactory::ParticleIter i = particle_mgr->begin (); i != particle_mgr->end (); ++i)
332 // Gravity
333 (*i)->add_force (Vector2d (0.0, 15.0f) * (*i)->get_mass ());
335 // Central Gravity force:
336 /*Vector2d direction = ((*i)->pos - Vector2d (400, 300));
337 if (direction.norm () != 0.0f)
338 (*i)->add_force (direction * (-100.0f/(direction.norm () * direction.norm ())));
342 for (ParticleIter j = particles.begin (); j != particles.end (); ++j)
344 Vector2d diff = (*j)->pos - (*i)->pos;
345 if (diff.norm () != 0.0f)
346 (*i)->add_force (diff * ((10.0f - (*j)->mass)/(diff.norm () * diff.norm ())));
347 } */
350 for (SpringIter i = springs.begin (); i != springs.end (); ++i)
351 (*i)->update (delta);
353 particle_mgr->update(delta);
355 //std::cout << "Colliders: " << colliders.size () << std::endl;
356 for (Colliders::iterator i = colliders.begin (); i != colliders.end (); ++i)
357 (*i)->bounce ();
359 // Spring splitting
360 std::vector<Spring*> new_springs;
361 for (SpringIter i = springs.begin (); i != springs.end (); ++i)
363 if ((*i)->destroyed)
365 if ((*i)->length > 20.0f)
367 // Calc midpoint
368 Vector2d pos = ((*i)->particles.first->pos
369 + (*i)->particles.second->pos) * 0.5f;
371 // FIXME: particle mass needs to be recalculated
372 Particle* p1 = particle_mgr->add_particle (pos, (*i)->particles.first->velocity * 0.5f, .1f);
373 Particle* p2 = particle_mgr->add_particle (pos, (*i)->particles.second->velocity * 0.5f, .1f);
375 // FIXME: Insert a more sofistikated string splitter here
376 new_springs.push_back (new Spring ((*i)->particles.first, p1, (*i)->length/2));
377 new_springs.push_back (new Spring ((*i)->particles.second, p2, (*i)->length/2));
381 springs.insert(springs.end(), new_springs.begin(), new_springs.end ());
383 // Remove any springs that are marked as destroyed
384 // FIXME: Could be faster
385 SpringIter i = springs.begin ();
386 while ( i != springs.end ())
388 if ((*i)->destroyed)
390 delete *i;
391 i = springs.erase(i);
393 else
395 ++i;
400 Spring*
401 World::get_spring (float x, float y)
403 Spring* spring = 0;
404 float min_distance = 0.0f;
406 float capture_threshold = 15;
408 for (SpringIter i = springs.begin (); i != springs.end (); ++i)
410 float x0 = x;
411 float y0 = y;
412 float& x1 = (*i)->particles.first->pos.x;
413 float& y1 = (*i)->particles.first->pos.y;
414 float& x2 = (*i)->particles.second->pos.x;
415 float& y2 = (*i)->particles.second->pos.y;
417 // FIXME: optimize me
418 float u = (((x0 - x1)*(x2-x1) + (y0 - y1)*(y2 - y1))
419 / ((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)));
421 float distance = (fabs((x2 - x1)*(y1-y0) - (x1-x0)*(y2-y1))
422 / sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)));
424 if (u >= 0 && u <= 1.0f
425 && ((spring && min_distance > distance)
426 || (!spring && distance <= capture_threshold))) // FIXME: threashold is dependend on view
428 spring = *i;
429 min_distance = distance;
433 return spring;
436 Particle*
437 World::get_particle (float x, float y)
439 Particle* particle = 0;
440 float min_dist = 15;
441 Vector2d mouse_pos (x, y);
443 for (ParticleFactory::ParticleIter i = particle_mgr->begin (); i != particle_mgr->end (); ++i)
445 Vector2d diff = mouse_pos - (*i)->pos;
446 if (diff.norm () < min_dist)
448 min_dist = diff.norm ();
449 particle = *i;
453 return particle;
456 std::vector<Particle*>
457 World::get_particles (float x1_, float y1_, float x2_, float y2_)
459 float x1 = Math::min(x1_, x2_);
460 float x2 = Math::max(x1_, x2_);
461 float y1 = Math::min(y1_, y2_);
462 float y2 = Math::max(y1_, y2_);
464 std::vector<Particle*> caputred_particles;
465 for (ParticleFactory::ParticleIter i = particle_mgr->begin (); i != particle_mgr->end (); ++i)
467 if ((*i)->pos.x >= x1 && (*i)->pos.x < x2
468 && (*i)->pos.y >= y1 && (*i)->pos.y < y2)
469 caputred_particles.push_back(*i);
471 return caputred_particles;
474 void
475 World::zero_out_velocity ()
477 std::cout << "Setting velocity to zero" << std::endl;
478 for (ParticleFactory::ParticleIter i = get_particle_mgr()->begin();
479 i != get_particle_mgr()->end (); ++i)
481 (*i)->velocity = Vector2d ();
485 void
486 World::add_spring (Particle* last_particle, Particle* particle)
488 assert (last_particle && particle);
489 springs.push_back (new Spring (last_particle, particle));
492 void
493 World::remove_particle (Particle* p)
495 // Remove everyting that references the particle
496 for (SpringIter i = springs.begin (); i != springs.end ();)
498 if ((*i)->particles.first == p || (*i)->particles.second == p)
500 delete *i;
501 // FIXME: this is potentially slow, since we don't care
502 // about order, we could speed this up
503 i = springs.erase(i);
505 else
507 ++i;
511 particle_mgr->remove_particle(p);
514 void
515 World::remove_spring (Spring* s)
517 //std::cout << "particles: " << particle_mgr->size () << std::endl;
518 //std::cout << "springs: " << springs.size () << std::endl;
520 delete s;
521 springs.erase(std::remove(springs.begin (), springs.end (), s),
522 springs.end ());
525 void
526 World::remove_collider (Collider* c)
528 delete c;
529 colliders.erase(std::remove(colliders.begin (), colliders.end (), c),
530 colliders.end ());
533 void
534 World::clear ()
536 particle_mgr->clear();
538 for (SpringIter i = springs.begin (); i != springs.end (); ++i)
539 delete *i;
541 springs.clear ();
544 void
545 World::write_lisp (const std::string& filename)
547 FILE* out;
549 out = system_context->open_output_file(filename);
551 if (!out)
553 std::cout << "World: Couldn't open '" << filename << "' for writing" << std::endl;
554 return;
557 std::cout << "Writing to: " << filename << std::endl;
559 fputs(";; Written by " PACKAGE_STRING "\n", out);
560 fputs("(construo-scene\n", out);
561 fputs(" (version 3)\n", out);
563 // FIXME: insert creation date here
564 // FIXME: Filter '()"' here
565 fprintf(out, " (author \"%s\" \"%s\")\n",
566 system_context->get_user_realname().c_str(),
567 system_context->get_user_email().c_str());
569 particle_mgr->write_lisp(out);
572 fputs(" (springs\n", out);
573 for (CSpringIter i = springs.begin (); i != springs.end (); ++i)
575 lisp_object_t* obj = (*i)->serialize ();
576 fputs(" ", out);
577 lisp_dump (obj, out);
578 fputc('\n', out);
579 lisp_free(obj);
581 fputs(" )\n", out);
583 fputs (" (colliders\n", out);
584 for (Colliders::iterator i = colliders.begin(); i != colliders.end(); ++i)
586 lisp_object_t* obj = (*i)->serialize ();
587 fputs(" ", out);
588 lisp_dump (obj, out);
589 fputc('\n', out);
590 lisp_free(obj);
592 fputs(" )", out);
595 fputs(")\n\n;; EOF ;;\n", out);
597 fclose(out);
599 if (StringUtils::has_suffix(filename, ".gz"))
600 { // Rewrite file compressed
601 std::cout << "World: Filename ends with .gz, rewriting " << filename << " compressed" << std::endl;
603 int len = 512*1024;
604 int read_len;
605 char* buf;
606 buf = static_cast<char*>(malloc(len));
607 if (!buf)
609 throw ConstruoError("Out of memory");
611 FILE* in = system_context->open_input_file(filename);
612 read_len = fread (buf, sizeof (char), len, in);
613 if (len >= read_len)
615 throw ConstruoError("World: Internal error, read buffer to small");
617 fclose (in);
619 // Write the buffer in compressed format
620 gzFile out = gzopen(system_context->translate_filename(filename).c_str(), "wb");
621 gzwrite (out, buf, len);
622 gzclose (out);
623 free (buf);
627 BoundingBox
628 World::calc_bounding_box()
630 BoundingBox bbox;
632 if (particle_mgr->size() > 0)
634 bbox.x1 = bbox.x2 = (*particle_mgr->begin ())->pos.x;
635 bbox.y1 = bbox.y2 = (*particle_mgr->begin ())->pos.y;
637 else
639 bbox.x1 = 0;
640 bbox.y1 = 0;
642 bbox.x2 = 800;
643 bbox.y2 = 600;
646 for (ParticleFactory::ParticleIter i = particle_mgr->begin (); i != particle_mgr->end (); ++i)
648 bbox.join((*i)->pos);
651 for (Colliders::iterator i = colliders.begin(); i != colliders.end(); ++i)
653 bbox.join((*i)->get_bounding_box());
656 return bbox;
660 World::get_num_particles()
662 return particle_mgr->size ();
666 World::get_num_springs()
668 return springs.size ();
671 void
672 World::add_rect_collider(const Vector2d& pos1, const Vector2d& pos2)
674 Rect<float> rect (pos1.x, pos1.y, pos2.x, pos2.y);
676 colliders.push_back(new RectCollider(rect.x1, rect.y1, rect.x2, rect.y2));
679 /* EOF */