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