1be52047bd64fbaf400cbfb89df81ad131a14711
[construo.git] / world.cxx
blob1be52047bd64fbaf400cbfb89df81ad131a14711
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"
38 World* World::current_world = 0;
40 World::World ()
41 : particle_mgr (new ParticleFactory(this))
43 file_version = 0;
44 has_been_run = false;
47 World::World (const std::string& filename)
48 : particle_mgr (0)
50 std::cout << "World: Trying to load: " << filename << std::endl;
51 file_version = 0;
53 has_been_run = false;
54 lisp_object_t* root_obj = 0;
56 // Try to read a file and store the content in root_obj
57 if (StringUtils::has_suffix(filename, ".construo.gz"))
59 #ifdef HAVE_LIBZ
60 lisp_stream_t stream;
61 int chunk_size = 128 * 1024; // allocate 256kb, should be enough for most levels
62 char* buf;
63 int buf_pos = 0;
64 int try_number = 1;
65 bool done = false;
67 buf = static_cast<char*>(malloc(chunk_size));
68 if (!buf)
70 throw ConstruoError ("World: Out of memory while opening " + filename);
73 gzFile in = gzopen(system_context->translate_filename(filename).c_str (), "rb");
75 while (!done)
77 int ret = gzread(in, buf + buf_pos, chunk_size);
78 if (ret == -1)
80 free (buf);
81 throw ConstruoError ("World: Out of memory while opening " + filename);
83 else if (ret == chunk_size) // buffer got full, eof not yet there
85 std::cout << "World: Read buffer to small, allocating more space" << std::endl;
87 buf_pos = chunk_size * try_number;
88 try_number += 1;
89 buf = static_cast<char*>(realloc(buf, chunk_size * try_number));
91 if (!buf)
93 throw ConstruoError ("World: Out of memory while opening " + filename);
96 else // (ret < chunk_size)
98 // everything fine, encountered EOF
99 done = true;
103 lisp_stream_init_string (&stream, buf);
104 root_obj = lisp_read (&stream);
106 free(buf);
107 gzclose(in);
108 #else
109 throw ConstruoError ("World: Reading of compressed files not supported, recompile with zlib support or extract the levelfile manually, " + filename);
110 #endif
112 else
114 lisp_stream_t stream;
115 FILE* in = system_context->open_input_file(filename);
116 if (!in)
118 throw ConstruoError ("World: Couldn't open " + filename);
119 return;
121 lisp_stream_init_file (&stream, in);
122 root_obj = lisp_read (&stream);
125 if (root_obj->type == LISP_TYPE_EOF || root_obj->type == LISP_TYPE_PARSE_ERROR)
127 std::cout << "World: Parse Error in file " << filename << std::endl;
130 lisp_object_t* cur = lisp_car(root_obj);
132 if (!lisp_symbol_p (cur))
134 throw ConstruoError ("World: Read error in " + filename);
137 if (strcmp(lisp_symbol(cur), "construo-scene") == 0)
139 parse_scene (lisp_cdr(root_obj));
141 else
143 throw ConstruoError ("World: Read error in " + filename + ". Couldn't find 'construo-scene'");
146 lisp_free (root_obj);
148 ConstruoAssert(particle_mgr, "No Particles given in file, load failed");
150 //std::cout << "particles: " << particle_mgr->size () << std::endl;
151 //std::cout << "springs: " << springs.size () << std::endl;
154 void
155 World::parse_scene (lisp_object_t* cursor)
157 while(!lisp_nil_p(cursor))
159 lisp_object_t* cur = lisp_car(cursor);
161 if (!lisp_cons_p(cur) || !lisp_symbol_p (lisp_car(cur)))
163 throw ConstruoError ("World: Read error in parse_scene");
165 else
167 if (strcmp(lisp_symbol(lisp_car(cur)), "particles") == 0)
169 parse_particles(lisp_cdr(cur));
171 else if (strcmp(lisp_symbol(lisp_car(cur)), "springs") == 0)
173 parse_springs(lisp_cdr(cur));
175 else if (strcmp(lisp_symbol(lisp_car(cur)), "colliders") == 0)
177 parse_colliders(lisp_cdr(cur));
179 else if (strcmp(lisp_symbol(lisp_car(cur)), "version") == 0)
181 file_version = lisp_integer(lisp_car(lisp_cdr(cur)));
183 else
185 std::cout << "World: Read error in parse_scene. Unhandled tag '"
186 << lisp_symbol(lisp_car(cur)) << "' skipping and continuing" << std::endl;
189 cursor = lisp_cdr (cursor);
193 void
194 World::parse_springs (lisp_object_t* cursor)
196 while(!lisp_nil_p(cursor))
198 lisp_object_t* cur = lisp_car(cursor);
199 springs.push_back(new Spring (this, cur));
200 cursor = lisp_cdr (cursor);
204 void
205 World::parse_colliders (lisp_object_t* cursor)
207 while(!lisp_nil_p(cursor))
209 lisp_object_t* cur = lisp_car(cursor);
210 if (strcmp(lisp_symbol(lisp_car(cur)), "rect") == 0)
212 colliders.push_back(new RectCollider(lisp_cdr(cur)));
214 else
216 std::cout << "WARNING: Unknown collider type '" << lisp_symbol(lisp_car(cur))
217 << "' skipping" << std::endl;
219 cursor = lisp_cdr (cursor);
223 void
224 World::parse_particles (lisp_object_t* cursor)
226 particle_mgr = new ParticleFactory(this, cursor);
229 // Copy Constructor
230 World::World (const World& old_world)
232 file_version = 0;
234 for (Colliders::const_iterator i = old_world.colliders.begin();
235 i != old_world.colliders.end();
236 ++i)
238 colliders.push_back((*i)->duplicate());
241 // FIXME: Could need optimizations
242 particle_mgr = new ParticleFactory (this, *old_world.particle_mgr);
244 for (CSpringIter i = old_world.springs.begin (); i != old_world.springs.end (); ++i)
246 Particle* first = particle_mgr->lookup_particle((*i)->particles.first->get_id());
247 Particle* second = particle_mgr->lookup_particle((*i)->particles.second->get_id());
249 if (first && second)
251 // FIXME: Use copy c'tor here maxstiffnes and Co. aren't copied correctly
252 springs.push_back (new Spring (first, second, (*i)->length));
254 else
256 std::cout << "World: Error couldn't resolve particles" << std::endl;
261 World::~World ()
263 clear ();
266 void
267 World::draw (ZoomGraphicContext* gc)
269 // FIXME: This is *not* used in the WorldViewComponent!
271 current_world = this;
273 draw_colliders(gc);
274 draw_springs(gc);
275 draw_particles(gc);
278 void
279 World::draw_springs(ZoomGraphicContext* gc)
281 #ifdef NEW_SPRING_CODE
282 std::vector<GraphicContext::Line> lines (springs.size());
284 Vector2d dist = springs[0]->particles.first->pos - springs[0]->particles.second->pos;
285 float stretch = fabs(dist.norm ()/springs[0]->length - 1.0f) * 10.0f;
286 float color = fabs((stretch/springs[0]->max_stretch));
288 for (unsigned int i = 0; i < springs.size(); ++i)
290 //(*i)->draw (gc);
291 lines[i].x1 = springs[i]->particles.first->pos.x;
292 lines[i].y1 = springs[i]->particles.first->pos.y;
293 lines[i].x2 = springs[i]->particles.second->pos.x;
294 lines[i].y2 = springs[i]->particles.second->pos.y;
296 gc->draw_lines (lines, Color(color, 1.0f - color, 0.0f), 2);
297 #else
298 for (SpringIter i = springs.begin(); i != springs.end(); ++i)
300 (*i)->draw (gc);
302 #endif
305 void
306 World::draw_particles(ZoomGraphicContext* gc)
308 particle_mgr->draw(gc);
311 void
312 World::draw_colliders(ZoomGraphicContext* gc)
314 for (Colliders::iterator i = colliders.begin (); i != colliders.end (); ++i)
316 (*i)->draw(gc);
320 void
321 World::update (float delta)
323 current_world = this;
325 has_been_run = true;
327 // Main Movement and Forces
328 // FIXME: Hardcoded Force Emitters
329 for (ParticleFactory::ParticleIter i = particle_mgr->begin (); i != particle_mgr->end (); ++i)
331 // Gravity
332 (*i)->add_force (Vector2d (0.0, 15.0f) * (*i)->get_mass ());
334 // Central Gravity force:
335 /*Vector2d direction = ((*i)->pos - Vector2d (400, 300));
336 if (direction.norm () != 0.0f)
337 (*i)->add_force (direction * (-100.0f/(direction.norm () * direction.norm ())));
341 for (ParticleIter j = particles.begin (); j != particles.end (); ++j)
343 Vector2d diff = (*j)->pos - (*i)->pos;
344 if (diff.norm () != 0.0f)
345 (*i)->add_force (diff * ((10.0f - (*j)->mass)/(diff.norm () * diff.norm ())));
346 } */
349 for (SpringIter i = springs.begin (); i != springs.end (); ++i)
350 (*i)->update (delta);
352 particle_mgr->update(delta);
354 //std::cout << "Colliders: " << colliders.size () << std::endl;
355 for (Colliders::iterator i = colliders.begin (); i != colliders.end (); ++i)
356 (*i)->bounce ();
358 // Spring splitting
359 std::vector<Spring*> new_springs;
360 for (SpringIter i = springs.begin (); i != springs.end (); ++i)
362 if ((*i)->destroyed)
364 if ((*i)->length > 20.0f)
366 // Calc midpoint
367 Vector2d pos = ((*i)->particles.first->pos
368 + (*i)->particles.second->pos) * 0.5f;
370 // FIXME: particle mass needs to be recalculated
371 Particle* p1 = particle_mgr->add_particle (pos, (*i)->particles.first->velocity * 0.5f, .1f);
372 Particle* p2 = particle_mgr->add_particle (pos, (*i)->particles.second->velocity * 0.5f, .1f);
374 // FIXME: Insert a more sofistikated string splitter here
375 new_springs.push_back (new Spring ((*i)->particles.first, p1, (*i)->length/2));
376 new_springs.push_back (new Spring ((*i)->particles.second, p2, (*i)->length/2));
380 springs.insert(springs.end(), new_springs.begin(), new_springs.end ());
382 // Remove any springs that are marked as destroyed
383 // FIXME: Could be faster
384 SpringIter i = springs.begin ();
385 while ( i != springs.end ())
387 if ((*i)->destroyed)
389 delete *i;
390 i = springs.erase(i);
392 else
394 ++i;
399 Spring*
400 World::get_spring (float x, float y)
402 Spring* spring = 0;
403 float min_distance = 0.0f;
405 float capture_threshold = 15;
407 for (SpringIter i = springs.begin (); i != springs.end (); ++i)
409 float x0 = x;
410 float y0 = y;
411 float& x1 = (*i)->particles.first->pos.x;
412 float& y1 = (*i)->particles.first->pos.y;
413 float& x2 = (*i)->particles.second->pos.x;
414 float& y2 = (*i)->particles.second->pos.y;
416 // FIXME: optimize me
417 float u = (((x0 - x1)*(x2-x1) + (y0 - y1)*(y2 - y1))
418 / ((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)));
420 float distance = (fabs((x2 - x1)*(y1-y0) - (x1-x0)*(y2-y1))
421 / sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)));
423 if (u >= 0 && u <= 1.0f
424 && ((spring && min_distance > distance)
425 || (!spring && distance <= capture_threshold))) // FIXME: threashold is dependend on view
427 spring = *i;
428 min_distance = distance;
432 return spring;
435 Particle*
436 World::get_particle (float x, float y)
438 Particle* particle = 0;
439 float min_dist = 15;
440 Vector2d mouse_pos (x, y);
442 for (ParticleFactory::ParticleIter i = particle_mgr->begin (); i != particle_mgr->end (); ++i)
444 Vector2d diff = mouse_pos - (*i)->pos;
445 if (diff.norm () < min_dist)
447 min_dist = diff.norm ();
448 particle = *i;
452 return particle;
455 std::vector<Particle*>
456 World::get_particles (float x1_, float y1_, float x2_, float y2_)
458 float x1 = Math::min(x1_, x2_);
459 float x2 = Math::max(x1_, x2_);
460 float y1 = Math::min(y1_, y2_);
461 float y2 = Math::max(y1_, y2_);
463 std::vector<Particle*> caputred_particles;
464 for (ParticleFactory::ParticleIter i = particle_mgr->begin (); i != particle_mgr->end (); ++i)
466 if ((*i)->pos.x >= x1 && (*i)->pos.x < x2
467 && (*i)->pos.y >= y1 && (*i)->pos.y < y2)
468 caputred_particles.push_back(*i);
470 return caputred_particles;
473 void
474 World::zero_out_velocity ()
476 std::cout << "Setting velocity to zero" << std::endl;
477 for (ParticleFactory::ParticleIter i = get_particle_mgr()->begin();
478 i != get_particle_mgr()->end (); ++i)
480 (*i)->velocity = Vector2d ();
484 void
485 World::add_spring (Particle* last_particle, Particle* particle)
487 assert (last_particle && particle);
488 springs.push_back (new Spring (last_particle, particle));
491 void
492 World::remove_particle (Particle* p)
494 // Remove everyting that references the particle
495 for (SpringIter i = springs.begin (); i != springs.end ();)
497 if ((*i)->particles.first == p || (*i)->particles.second == p)
499 delete *i;
500 // FIXME: this is potentially slow, since we don't care
501 // about order, we could speed this up
502 i = springs.erase(i);
504 else
506 ++i;
510 particle_mgr->remove_particle(p);
513 void
514 World::remove_spring (Spring* s)
516 //std::cout << "particles: " << particle_mgr->size () << std::endl;
517 //std::cout << "springs: " << springs.size () << std::endl;
519 delete s;
520 springs.erase(std::remove(springs.begin (), springs.end (), s),
521 springs.end ());
524 void
525 World::remove_collider (Collider* c)
527 delete c;
528 colliders.erase(std::remove(colliders.begin (), colliders.end (), c),
529 colliders.end ());
532 void
533 World::clear ()
535 particle_mgr->clear();
537 for (SpringIter i = springs.begin (); i != springs.end (); ++i)
538 delete *i;
540 springs.clear ();
543 void
544 World::write_lisp (const std::string& filename)
546 FILE* out;
548 out = system_context->open_output_file(filename);
550 if (!out)
552 std::cout << "World: Couldn't open '" << filename << "' for writing" << std::endl;
553 return;
556 std::cout << "Writing to: " << filename << std::endl;
558 fputs(";; Written by " PACKAGE_STRING "\n", out);
559 fputs("(construo-scene\n", out);
560 fputs(" (version 3)\n", out);
562 // FIXME: insert creation date here
563 // FIXME: Filter '()"' here
564 fprintf(out, " (author \"%s\" \"%s\")\n",
565 system_context->get_user_realname().c_str(),
566 system_context->get_user_email().c_str());
568 particle_mgr->write_lisp(out);
571 fputs(" (springs\n", out);
572 for (CSpringIter i = springs.begin (); i != springs.end (); ++i)
574 lisp_object_t* obj = (*i)->serialize ();
575 fputs(" ", out);
576 lisp_dump (obj, out);
577 fputc('\n', out);
578 lisp_free(obj);
580 fputs(" )\n", out);
582 fputs (" (colliders\n", out);
583 for (Colliders::iterator i = colliders.begin(); i != colliders.end(); ++i)
585 lisp_object_t* obj = (*i)->serialize ();
586 fputs(" ", out);
587 lisp_dump (obj, out);
588 fputc('\n', out);
589 lisp_free(obj);
591 fputs(" )", out);
594 fputs(")\n\n;; EOF ;;\n", out);
596 fclose(out);
598 if (StringUtils::has_suffix(filename, ".gz"))
599 { // Rewrite file compressed
600 std::cout << "World: Filename ends with .gz, rewriting " << filename << " compressed" << std::endl;
602 int len = 512*1024;
603 int read_len;
604 char* buf;
605 buf = static_cast<char*>(malloc(len));
606 if (!buf)
608 throw ConstruoError("Out of memory");
610 FILE* in = system_context->open_input_file(filename);
611 read_len = fread (buf, sizeof (char), len, in);
612 if (len >= read_len)
614 throw ConstruoError("World: Internal error, read buffer to small");
616 fclose (in);
618 // Write the buffer in compressed format
619 gzFile out = gzopen(system_context->translate_filename(filename).c_str(), "wb");
620 gzwrite (out, buf, len);
621 gzclose (out);
622 free (buf);
626 BoundingBox
627 World::calc_bounding_box()
629 BoundingBox bbox;
631 if (particle_mgr->size() > 0)
633 bbox.x1 = bbox.x2 = (*particle_mgr->begin ())->pos.x;
634 bbox.y1 = bbox.y2 = (*particle_mgr->begin ())->pos.y;
636 else
638 bbox.x1 = 0;
639 bbox.y1 = 0;
641 bbox.x2 = 800;
642 bbox.y2 = 600;
645 for (ParticleFactory::ParticleIter i = particle_mgr->begin (); i != particle_mgr->end (); ++i)
647 bbox.join((*i)->pos);
650 for (Colliders::iterator i = colliders.begin(); i != colliders.end(); ++i)
652 bbox.join((*i)->get_bounding_box());
655 return bbox;
659 World::get_num_particles()
661 return particle_mgr->size ();
665 World::get_num_springs()
667 return springs.size ();
670 void
671 World::add_rect_collider(const Vector2d& pos1, const Vector2d& pos2)
673 Rect<float> rect (pos1.x, pos1.y, pos2.x, pos2.y);
675 colliders.push_back(new RectCollider(rect.x1, rect.y1, rect.x2, rect.y2));
678 /* EOF */