1 // $Id: world.cxx,v 1.35 2003/07/28 20:57:41 grumbel Exp $
3 // Construo - A wire-frame construction game
4 // Copyright (C) 2002 Ingo Ruhnke <grumbel@gmx.de>
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.
29 #include "construo_error.hxx"
31 #include "particle_factory.hxx"
32 #include "system_context.hxx"
33 #include "controller.hxx"
35 #include "rect_collider.hxx"
36 #include "string_utils.hxx"
39 World
* World::current_world
= 0;
42 : particle_mgr (new ParticleFactory(this))
48 World::World (const std::string
& filename
)
51 std::cout
<< "World: Trying to load: " << filename
<< std::endl
;
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"))
62 int chunk_size
= 128 * 1024; // allocate 256kb, should be enough for most levels
68 buf
= static_cast<char*>(malloc(chunk_size
));
71 throw ConstruoError ("World: Out of memory while opening " + filename
);
74 gzFile in
= gzopen(system_context
->translate_filename(filename
).c_str (), "rb");
78 int ret
= gzread(in
, buf
+ buf_pos
, chunk_size
);
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
;
90 buf
= static_cast<char*>(realloc(buf
, chunk_size
* try_number
));
94 throw ConstruoError ("World: Out of memory while opening " + filename
);
97 else // (ret < chunk_size)
99 // everything fine, encountered EOF
104 lisp_stream_init_string (&stream
, buf
);
105 root_obj
= lisp_read (&stream
);
110 throw ConstruoError ("World: Reading of compressed files not supported, recompile with zlib support or extract the levelfile manually, " + filename
);
115 lisp_stream_t stream
;
116 FILE* in
= system_context
->open_input_file(filename
);
119 throw ConstruoError ("World: Couldn't open " + filename
);
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
));
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;
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");
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
)));
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
);
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
);
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
)));
217 std::cout
<< "WARNING: Unknown collider type '" << lisp_symbol(lisp_car(cur
))
218 << "' skipping" << std::endl
;
220 cursor
= lisp_cdr (cursor
);
225 World::parse_particles (lisp_object_t
* cursor
)
227 particle_mgr
= new ParticleFactory(this, cursor
);
231 World::World (const World
& old_world
)
235 for (Colliders::const_iterator i
= old_world
.colliders
.begin();
236 i
!= old_world
.colliders
.end();
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());
252 // FIXME: Use copy c'tor here maxstiffnes and Co. aren't copied correctly
253 springs
.push_back (new Spring (first
, second
, (*i
)->length
));
257 std::cout
<< "World: Error couldn't resolve particles" << std::endl
;
268 World::draw (ZoomGraphicContext
* gc
)
270 // FIXME: This is *not* used in the WorldViewComponent!
272 current_world
= this;
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
)
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);
299 for (SpringIter i
= springs
.begin(); i
!= springs
.end(); ++i
)
307 World::draw_particles(ZoomGraphicContext
* gc
)
309 particle_mgr
->draw(gc
);
313 World::draw_colliders(ZoomGraphicContext
* gc
)
315 for (Colliders::iterator i
= colliders
.begin (); i
!= colliders
.end (); ++i
)
322 World::update (float delta
)
324 current_world
= this;
328 // Main Movement and Forces
329 // FIXME: Hardcoded Force Emitters
330 for (ParticleFactory::ParticleIter i
= particle_mgr
->begin (); i
!= particle_mgr
->end (); ++i
)
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 ())));
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
)
360 std::vector
<Spring
*> new_springs
;
361 for (SpringIter i
= springs
.begin (); i
!= springs
.end (); ++i
)
365 if ((*i
)->length
> 20.0f
)
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 ())
391 i
= springs
.erase(i
);
401 World::get_spring (float x
, float y
)
404 float min_distance
= 0.0f
;
406 float capture_threshold
= 15;
408 for (SpringIter i
= springs
.begin (); i
!= springs
.end (); ++i
)
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
429 min_distance
= distance
;
437 World::get_particle (float x
, float y
)
439 Particle
* particle
= 0;
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 ();
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
;
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 ();
486 World::add_spring (Particle
* last_particle
, Particle
* particle
)
488 assert (last_particle
&& particle
);
489 springs
.push_back (new Spring (last_particle
, particle
));
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
)
501 // FIXME: this is potentially slow, since we don't care
502 // about order, we could speed this up
503 i
= springs
.erase(i
);
511 particle_mgr
->remove_particle(p
);
515 World::remove_spring (Spring
* s
)
517 //std::cout << "particles: " << particle_mgr->size () << std::endl;
518 //std::cout << "springs: " << springs.size () << std::endl;
521 springs
.erase(std::remove(springs
.begin (), springs
.end (), s
),
526 World::remove_collider (Collider
* c
)
529 colliders
.erase(std::remove(colliders
.begin (), colliders
.end (), c
),
536 particle_mgr
->clear();
538 for (SpringIter i
= springs
.begin (); i
!= springs
.end (); ++i
)
545 World::write_lisp (const std::string
& filename
)
549 out
= system_context
->open_output_file(filename
);
553 std::cout
<< "World: Couldn't open '" << filename
<< "' for writing" << std::endl
;
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 ();
577 lisp_dump (obj
, out
);
583 fputs (" (colliders\n", out
);
584 for (Colliders::iterator i
= colliders
.begin(); i
!= colliders
.end(); ++i
)
586 lisp_object_t
* obj
= (*i
)->serialize ();
588 lisp_dump (obj
, out
);
595 fputs(")\n\n;; EOF ;;\n", 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
;
606 buf
= static_cast<char*>(malloc(len
));
609 throw ConstruoError("Out of memory");
611 FILE* in
= system_context
->open_input_file(filename
);
612 read_len
= fread (buf
, sizeof (char), len
, in
);
615 throw ConstruoError("World: Internal error, read buffer to small");
619 // Write the buffer in compressed format
620 gzFile out
= gzopen(system_context
->translate_filename(filename
).c_str(), "wb");
621 gzwrite (out
, buf
, len
);
628 World::calc_bounding_box()
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
;
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());
660 World::get_num_particles()
662 return particle_mgr
->size ();
666 World::get_num_springs()
668 return springs
.size ();
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
));