1 //  Construo - A wire-frame construction game
2 //  Copyright (C) 2002 Ingo Ruhnke <grumbel@gmx.de>
3 //
4 //  This program is free software: you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation, either version 3 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 
17 #include <string.h>
18 #include <assert.h>
19 #include <algorithm>
20 #include "config.h"
21 
22 #ifdef HAVE_LIBZ
23 #  include <zlib.h>
24 #endif
25 
26 #include "math.hpp"
27 #include "construo_error.hpp"
28 #include "world.hpp"
29 #include "particle_factory.hpp"
30 #include "system_context.hpp"
31 #include "controller.hpp"
32 #include "rect.hpp"
33 #include "rect_collider.hpp"
34 #include "string_utils.hpp"
35 
36 World* World::current_world = 0;
37 
World()38 World::World ()
39   : particle_mgr (new ParticleFactory(this))
40 {
41   file_version = 0;
42   has_been_run = false;
43 }
44 
World(const std::string & filename)45 World::World (const std::string& filename)
46   : particle_mgr (0)
47 {
48   std::cout << "World: Loading '" << filename << "'..." << std::endl;
49   file_version = 0;
50 
51   has_been_run = false;
52   lisp_object_t* root_obj = 0;
53 
54   // Try to read a file and store the content in root_obj
55   if (StringUtils::has_suffix(filename, ".construo.gz"))
56     {
57 #ifdef HAVE_LIBZ
58       lisp_stream_t stream;
59       int chunk_size = 128 * 1024; // allocate 256kb, should be enough for most levels
60       char* buf;
61       int buf_pos = 0;
62       int try_number = 1;
63       bool done = false;
64 
65       buf = static_cast<char*>(malloc(chunk_size));
66       if (!buf)
67         {
68           throw ConstruoError ("World: Out of memory while opening " + filename);
69         }
70 
71       gzFile in = gzopen(system_context->translate_filename(filename).c_str (), "rb");
72 
73       while (!done)
74         {
75           int ret = gzread(in, buf + buf_pos, chunk_size);
76           if (ret == -1)
77             {
78               free (buf);
79               throw ConstruoError ("World: Out of memory while opening " + filename);
80             }
81           else if (ret == chunk_size) // buffer got full, eof not yet there
82             {
83               std::cout << "World: Read buffer to small, allocating more space" << std::endl;
84 
85               buf_pos = chunk_size * try_number;
86               try_number += 1;
87               buf = static_cast<char*>(realloc(buf, chunk_size * try_number));
88 
89               if (!buf)
90                 {
91                   throw ConstruoError ("World: Out of memory while opening " + filename);
92                 }
93             }
94           else // (ret < chunk_size)
95             {
96               // everything fine, encountered EOF
97               done = true;
98             }
99         }
100 
101       lisp_stream_init_string (&stream, buf);
102       root_obj = lisp_read (&stream);
103 
104       free(buf);
105       gzclose(in);
106 #else
107       throw ConstruoError ("World: Reading of compressed files not supported, recompile with zlib support or extract the levelfile manually, " + filename);
108 #endif
109     }
110   else
111     {
112       lisp_stream_t stream;
113       FILE* in = system_context->open_input_file(filename);
114       if (!in)
115         {
116           throw ConstruoError ("World: Couldn't open " + filename);
117           return;
118         }
119       lisp_stream_init_file (&stream, in);
120       root_obj = lisp_read (&stream);
121     }
122 
123   if (root_obj->type == LISP_TYPE_EOF || root_obj->type == LISP_TYPE_PARSE_ERROR)
124     {
125       std::cout << "World: Parse Error in file " << filename << std::endl;
126     }
127 
128   lisp_object_t* cur = lisp_car(root_obj);
129 
130   if (!lisp_symbol_p (cur))
131     {
132       throw ConstruoError ("World: Read error in " + filename);
133     }
134 
135   if (strcmp(lisp_symbol(cur), "construo-scene") == 0)
136     {
137       parse_scene (lisp_cdr(root_obj));
138     }
139   else
140     {
141       throw ConstruoError ("World: Read error in " + filename + ". Couldn't find 'construo-scene'");
142     }
143 
144   lisp_free (root_obj);
145 
146   ConstruoAssert(particle_mgr, "No Particles given in file, load failed");
147 
148   //std::cout << "particles: " << particle_mgr->size () << std::endl;
149   //std::cout << "springs:   " << springs.size () << std::endl;
150 }
151 
152 // Copy Constructor
World(const World & old_world)153 World::World (const World& old_world)
154 {
155   file_version = 0;
156 
157   for (Colliders::const_iterator i = old_world.colliders.begin();
158        i != old_world.colliders.end();
159        ++i)
160     {
161       colliders.push_back((*i)->duplicate());
162     }
163 
164   // FIXME: Could need optimizations
165   particle_mgr = new ParticleFactory (this, *old_world.particle_mgr);
166 
167   for (CSpringIter i = old_world.springs.begin (); i != old_world.springs.end (); ++i)
168     {
169       Particle* first  = particle_mgr->lookup_particle((*i)->particles.first->get_id());
170       Particle* second = particle_mgr->lookup_particle((*i)->particles.second->get_id());
171 
172       if (first && second)
173         {
174           // FIXME: Use copy c'tor here maxstiffnes and Co. aren't copied correctly
175           springs.push_back(new Spring (first, second, (*i)->length));
176         }
177       else
178         {
179           std::cout << "World: Error couldn't resolve particles" << std::endl;
180         }
181     }
182 }
183 
~World()184 World::~World ()
185 {
186   clear ();
187 }
188 
189 void
parse_scene(lisp_object_t * cursor)190 World::parse_scene (lisp_object_t* cursor)
191 {
192   while(!lisp_nil_p(cursor))
193     {
194       lisp_object_t* cur = lisp_car(cursor);
195 
196       if (!lisp_cons_p(cur) || !lisp_symbol_p (lisp_car(cur)))
197         {
198           throw ConstruoError ("World: Read error in parse_scene");
199         }
200       else
201         {
202           if (strcmp(lisp_symbol(lisp_car(cur)), "particles") == 0)
203             {
204               parse_particles(lisp_cdr(cur));
205             }
206           else if (strcmp(lisp_symbol(lisp_car(cur)), "springs") == 0)
207             {
208               parse_springs(lisp_cdr(cur));
209             }
210           else if (strcmp(lisp_symbol(lisp_car(cur)), "colliders") == 0)
211             {
212               parse_colliders(lisp_cdr(cur));
213             }
214           else if (strcmp(lisp_symbol(lisp_car(cur)), "version") == 0)
215             {
216               file_version = lisp_integer(lisp_car(lisp_cdr(cur)));
217             }
218           else
219             {
220               std::cout << "World: Read error in parse_scene. Unhandled tag '"
221                         << lisp_symbol(lisp_car(cur)) << "' skipping and continuing" << std::endl;
222             }
223         }
224       cursor = lisp_cdr (cursor);
225     }
226 }
227 
228 void
parse_springs(lisp_object_t * cursor)229 World::parse_springs (lisp_object_t* cursor)
230 {
231   while(!lisp_nil_p(cursor))
232     {
233       lisp_object_t* cur = lisp_car(cursor);
234       springs.push_back(new Spring (this, cur));
235       cursor = lisp_cdr (cursor);
236     }
237 }
238 
239 void
parse_colliders(lisp_object_t * cursor)240 World::parse_colliders (lisp_object_t* cursor)
241 {
242   while(!lisp_nil_p(cursor))
243     {
244       lisp_object_t* cur = lisp_car(cursor);
245       if (strcmp(lisp_symbol(lisp_car(cur)), "rect") == 0)
246         {
247           colliders.push_back(new RectCollider(lisp_cdr(cur)));
248         }
249       else
250         {
251           std::cout << "WARNING: Unknown collider type '" << lisp_symbol(lisp_car(cur))
252                     << "' skipping" << std::endl;
253         }
254       cursor = lisp_cdr (cursor);
255     }
256 }
257 
258 void
parse_particles(lisp_object_t * cursor)259 World::parse_particles (lisp_object_t* cursor)
260 {
261   particle_mgr = new ParticleFactory(this, cursor);
262 }
263 
264 
265 void
draw(ZoomGraphicContext * gc)266 World::draw (ZoomGraphicContext* gc)
267 {
268   // FIXME: This is *not* used in the WorldViewComponent!
269 
270   current_world = this;
271 
272   draw_colliders(gc);
273   draw_springs(gc);
274   draw_particles(gc);
275 }
276 
277 void
draw_springs(ZoomGraphicContext * gc)278 World::draw_springs(ZoomGraphicContext* gc)
279 {
280 #ifdef NEW_SPRING_CODE
281   std::vector<GraphicContext::Line> lines (springs.size());
282 
283   Vector2d dist = springs[0]->particles.first->pos - springs[0]->particles.second->pos;
284   float stretch = fabs(dist.norm ()/springs[0]->length - 1.0f) * 10.0f;
285   float color = fabs((stretch/springs[0]->max_stretch));
286 
287   for (unsigned int i = 0; i < springs.size(); ++i)
288     {
289       //(*i)->draw (gc);
290       lines[i].x1 = springs[i]->particles.first->pos.x;
291       lines[i].y1 = springs[i]->particles.first->pos.y;
292       lines[i].x2 = springs[i]->particles.second->pos.x;
293       lines[i].y2 = springs[i]->particles.second->pos.y;
294     }
295   gc->draw_lines (lines, Color(color, 1.0f - color, 0.0f), 2);
296 #else
297   for (SpringIter i = springs.begin(); i != springs.end(); ++i)
298     {
299       (*i)->draw (gc);
300     }
301 #endif
302 }
303 
304 void
draw_particles(ZoomGraphicContext * gc)305 World::draw_particles(ZoomGraphicContext* gc)
306 {
307   particle_mgr->draw(gc);
308 }
309 
310 void
draw_colliders(ZoomGraphicContext * gc)311 World::draw_colliders(ZoomGraphicContext* gc)
312 {
313   for (Colliders::iterator i = colliders.begin (); i != colliders.end (); ++i)
314     {
315       (*i)->draw(gc);
316     }
317 }
318 
319 void
update(float delta)320 World::update (float delta)
321 {
322   current_world = this;
323 
324   has_been_run = true;
325 
326   // Main Movement and Forces
327   // FIXME: Hardcoded Force Emitters
328   for (ParticleFactory::ParticleIter i = particle_mgr->begin (); i != particle_mgr->end (); ++i)
329     {
330       // Gravity
331       (*i)->add_force (Vector2d (0.0, 15.0f) * (*i)->get_mass ());
332 
333       // Central Gravity force:
334       /*Vector2d direction = ((*i)->pos - Vector2d (400, 300));
335         if (direction.norm () != 0.0f)
336         (*i)->add_force (direction * (-100.0f/(direction.norm () * direction.norm ())));
337       */
338 
339       /*
340         for (ParticleIter j = particles.begin (); j != particles.end (); ++j)
341         {
342         Vector2d diff = (*j)->pos - (*i)->pos;
343         if (diff.norm () != 0.0f)
344         (*i)->add_force (diff * ((10.0f - (*j)->mass)/(diff.norm () * diff.norm ())));
345         }	    */
346     }
347 
348   for (SpringIter i = springs.begin (); i != springs.end (); ++i)
349     (*i)->update (delta);
350 
351   particle_mgr->update(delta);
352 
353   //std::cout << "Colliders: " << colliders.size () << std::endl;
354   for (Colliders::iterator i = colliders.begin (); i != colliders.end (); ++i)
355     (*i)->bounce ();
356 
357   // Spring splitting
358   std::vector<Spring*> new_springs;
359   for (SpringIter i = springs.begin (); i != springs.end (); ++i)
360     {
361       if ((*i)->destroyed)
362         {
363           if ((*i)->length > 20.0f)
364             {
365               // Calc midpoint
366               Vector2d pos = ((*i)->particles.first->pos
367                                + (*i)->particles.second->pos) * 0.5f;
368 
369               // FIXME: particle mass needs to be recalculated
370               Particle* p1 = particle_mgr->add_particle (pos, (*i)->particles.first->velocity * 0.5f, .1f);
371               Particle* p2 = particle_mgr->add_particle (pos, (*i)->particles.second->velocity * 0.5f, .1f);
372 
373               // FIXME: Insert a more sofistikated string splitter here
374               new_springs.push_back (new Spring ((*i)->particles.first, p1, (*i)->length/2));
375               new_springs.push_back (new Spring ((*i)->particles.second, p2, (*i)->length/2));
376             }
377         }
378     }
379   springs.insert(springs.end(), new_springs.begin(), new_springs.end ());
380 
381   // Remove any springs that are marked as destroyed
382   // FIXME: Could be faster
383   SpringIter i = springs.begin ();
384   while ( i != springs.end ())
385     {
386       if ((*i)->destroyed)
387         {
388           delete *i;
389           i = springs.erase(i);
390         }
391       else
392         {
393           ++i;
394         }
395     }
396 }
397 
398 Spring*
get_spring(float x,float y)399 World::get_spring (float x, float y)
400 {
401   Spring* spring = 0;
402   float min_distance = 0.0f;
403 
404   float capture_threshold = 15;
405 
406   for (SpringIter i = springs.begin (); i != springs.end (); ++i)
407     {
408       float x0 = x;
409       float y0 = y;
410       float& x1 = (*i)->particles.first->pos.x;
411       float& y1 = (*i)->particles.first->pos.y;
412       float& x2 = (*i)->particles.second->pos.x;
413       float& y2 = (*i)->particles.second->pos.y;
414 
415       // FIXME: optimize me
416       float u = (((x0 - x1)*(x2-x1) + (y0 - y1)*(y2 - y1))
417                  / ((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)));
418 
419       float distance = (fabs((x2 - x1)*(y1-y0) - (x1-x0)*(y2-y1))
420                         / sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)));
421 
422       if (u >= 0 && u <= 1.0f
423           && ((spring && min_distance > distance)
424               || (!spring && distance <= capture_threshold))) // FIXME: threashold is dependend on view
425         {
426           spring = *i;
427           min_distance = distance;
428         }
429     }
430 
431   return spring;
432 }
433 
434 Particle*
get_particle(float x,float y)435 World::get_particle (float x, float y)
436 {
437   Particle* particle = 0;
438   float min_dist = 25.0f; // FIXME: Make this configurable
439   Vector2d mouse_pos (x, y);
440 
441   for (ParticleFactory::ParticleIter i = particle_mgr->begin (); i != particle_mgr->end (); ++i)
442     {
443       Vector2d diff = mouse_pos - (*i)->pos;
444       if (diff.norm () < min_dist)
445 	{
446 	  min_dist = diff.norm ();
447 	  particle = *i;
448 	}
449     }
450 
451   return particle;
452 }
453 
454 std::vector<Particle*>
get_particles(float x1_,float y1_,float x2_,float y2_)455 World::get_particles (float x1_, float y1_, float x2_, float y2_)
456 {
457   float x1 = Math::min(x1_, x2_);
458   float x2 = Math::max(x1_, x2_);
459   float y1 = Math::min(y1_, y2_);
460   float y2 = Math::max(y1_, y2_);
461 
462   std::vector<Particle*> caputred_particles;
463   for (ParticleFactory::ParticleIter i = particle_mgr->begin (); i != particle_mgr->end (); ++i)
464     {
465       if ((*i)->pos.x >= x1 && (*i)->pos.x < x2
466           && (*i)->pos.y >= y1 && (*i)->pos.y < y2)
467         caputred_particles.push_back(*i);
468     }
469   return caputred_particles;
470 }
471 
472 void
zero_out_velocity()473 World::zero_out_velocity ()
474 {
475   std::cout << "Setting velocity to zero" << std::endl;
476   for (ParticleFactory::ParticleIter i = get_particle_mgr()->begin();
477        i != get_particle_mgr()->end (); ++i)
478     {
479       (*i)->velocity = Vector2d ();
480     }
481 }
482 
483 void
add_spring(Particle * last_particle,Particle * particle)484 World::add_spring (Particle* last_particle, Particle* particle)
485 {
486   assert (last_particle && particle);
487   springs.push_back (new Spring (last_particle, particle));
488 }
489 
490 void
remove_particle(Particle * p)491 World::remove_particle (Particle* p)
492 {
493   // Remove everyting that references the particle
494   for (SpringIter i = springs.begin (); i != springs.end ();)
495     {
496       if ((*i)->particles.first == p || (*i)->particles.second == p)
497         {
498           delete *i;
499           // FIXME: this is potentially slow, since we don't care
500           // about order, we could speed this up
501           i = springs.erase(i);
502         }
503       else
504         {
505           ++i;
506         }
507     }
508 
509   particle_mgr->remove_particle(p);
510 }
511 
512 void
remove_spring(Spring * s)513 World::remove_spring (Spring* s)
514 {
515   //std::cout << "particles: " << particle_mgr->size () << std::endl;
516   //std::cout << "springs:   " << springs.size () << std::endl;
517 
518   delete s;
519   springs.erase(std::remove(springs.begin (), springs.end (), s),
520                 springs.end ());
521 }
522 
523 void
remove_collider(Collider * c)524 World::remove_collider (Collider* c)
525 {
526   delete c;
527   colliders.erase(std::remove(colliders.begin (), colliders.end (), c),
528                   colliders.end ());
529 }
530 
531 void
clear()532 World::clear ()
533 {
534   particle_mgr->clear();
535 
536   for (SpringIter i = springs.begin (); i != springs.end (); ++i)
537     delete *i;
538 
539   springs.clear ();
540 }
541 
542 void
write_lisp(const std::string & filename)543 World::write_lisp (const std::string& filename)
544 {
545   FILE* out;
546 
547   out = system_context->open_output_file(filename);
548 
549   if (!out)
550     {
551       std::cout << "World: Couldn't open '" << filename << "' for writing" << std::endl;
552       return;
553     }
554 
555   std::cout << "World: Writing to: " << filename << std::endl;
556 
557   fputs(";; Written by " PACKAGE_STRING "\n", out);
558   fputs("(construo-scene\n", out);
559   fputs("  (version 3)\n", out);
560 
561   // FIXME: insert creation date here
562   // FIXME: Filter '()"' here
563   fprintf(out, "  (author \"%s\" \"%s\")\n",
564           system_context->get_user_realname().c_str(),
565           system_context->get_user_email().c_str());
566 
567   particle_mgr->write_lisp(out);
568 
569 
570   fputs("  (springs\n", out);
571   for (CSpringIter i = springs.begin (); i != springs.end (); ++i)
572     {
573       lisp_object_t* obj = (*i)->serialize ();
574       fputs("    ", out);
575       lisp_dump (obj, out);
576       fputc('\n', out);
577       lisp_free(obj);
578     }
579   fputs("  )\n", out);
580 
581   fputs ("  (colliders\n", out);
582   for (Colliders::iterator i = colliders.begin(); i != colliders.end(); ++i)
583     {
584       lisp_object_t* obj = (*i)->serialize ();
585       fputs("    ", out);
586       lisp_dump (obj, out);
587       fputc('\n', out);
588       lisp_free(obj);
589     }
590   fputs("  )", out);
591 
592 
593   fputs(")\n\n;; EOF ;;\n", out);
594 
595   fclose(out);
596 
597   if (StringUtils::has_suffix(filename, ".gz"))
598     { // Rewrite file compressed
599       std::cout << "World: Filename ends with .gz, rewriting " << filename << " compressed" << std::endl;
600 
601       int len = 512*1024;
602       int read_len;
603       char* buf;
604       buf = static_cast<char*>(malloc(len));
605       if (!buf)
606         {
607           throw ConstruoError("Out of memory");
608         }
609       FILE* in = system_context->open_input_file(filename);
610       read_len = fread (buf, sizeof (char), len, in);
611       if (len >= read_len)
612         {
613           throw ConstruoError("World: Internal error, read buffer to small");
614         }
615       fclose (in);
616 
617       // Write the buffer in compressed format
618       gzFile out = gzopen(system_context->translate_filename(filename).c_str(), "wb");
619       gzwrite (out, buf, len);
620       gzclose (out);
621       free (buf);
622     }
623 }
624 
625 BoundingBox
calc_bounding_box()626 World::calc_bounding_box()
627 {
628   BoundingBox bbox;
629 
630   if (particle_mgr->size() > 0)
631     {
632       bbox.x1 = bbox.x2 = (*particle_mgr->begin ())->pos.x;
633       bbox.y1 = bbox.y2 = (*particle_mgr->begin ())->pos.y;
634     }
635   else
636     {
637       bbox.x1 = 0;
638       bbox.y1 = 0;
639 
640       bbox.x2 = 800;
641       bbox.y2 = 600;
642     }
643 
644   for (ParticleFactory::ParticleIter i = particle_mgr->begin (); i != particle_mgr->end (); ++i)
645     {
646       bbox.join((*i)->pos);
647     }
648 
649   for (Colliders::iterator i = colliders.begin(); i != colliders.end(); ++i)
650     {
651       bbox.join((*i)->get_bounding_box());
652     }
653 
654   return bbox;
655 }
656 
657 int
get_num_particles()658 World::get_num_particles()
659 {
660   return particle_mgr->size ();
661 }
662 
663 int
get_num_springs()664 World::get_num_springs()
665 {
666   return springs.size ();
667 }
668 
669 void
add_rect_collider(const Vector2d & pos1,const Vector2d & pos2)670 World::add_rect_collider(const Vector2d& pos1, const Vector2d& pos2)
671 {
672   Rect<float> rect (pos1.x, pos1.y, pos2.x, pos2.y);
673 
674   colliders.push_back(new RectCollider(rect.x1, rect.y1, rect.x2, rect.y2));
675 }
676 
677 /* EOF */
678