1 /*
2   Copyright (C) 2009 Facundo Domínguez
3 
4   This file is part of Spacejunk.
5 
6   Spacejunk is free software: you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation, either version 3 of the License, or
9   (at your option) any later version.
10 
11   Foobar 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.
15 
16   You should have received a copy of the GNU General Public License
17   along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #ifndef SIMULATION_H
21 #define SIMULATION_H
22 
23 #include "physicbody.h"
24 #include "gamebody.h"
25 #include "timer.h"
26 #include "functionexts.h"
27 #include <list>
28 #include "eventlistenerlist.h"
29 
30 template <class Item,class Data>
31 class CollisionEngine;
32 class Simulation;
33 class Collision;
34 
35 typedef TimerQueue<Simulation*>::TimerRef GeneralTimer;
36 typedef TimerQueue<Simulation*> Timer;
37 typedef TimerEvent<Simulation*> Event;
38 typedef _EmptyEvent<Simulation*> EmptyEvent;
39 typedef TimerQueue<Simulation*>::TimerId TimerId;
40 
41 
42 /** A listener for collision events. */
43 class SimCollisionListener {
44 public:
45     /**
46      * Called whenever a collision occurs.
47      * @param sim is the simulation where the collision occurred
48      * @param c1 one of the coliding bodies
49      * @param c2 the other coliding bodies
50      * @param normal the normal to the surface of contact between the bodies.
51      * */
52     virtual void evtCollision(Simulation * sim,PhysicBody * c1,PhysicBody * c2,const Vector2d & normal)=0;
~SimCollisionListener()53     virtual ~SimCollisionListener() {};
54 };
55 
56 /** A listener for object removals. */
57 class SimDeletionListener {
58 public:
59     /**
60      * Called whenever an object with identifier id is removed
61      * from a simulation sim.
62      */
63     virtual void evtDeleteBody(Simulation * sim,int id)=0;
~SimDeletionListener()64     virtual ~SimDeletionListener() {};
65 };
66 
67 /** Implementation of a physical simulation of a system with gravitational forces. */
68 class Simulation {
69 public:
70     mutable EventListenerList<SimDeletionListener*> deletionListeners;
71     mutable EventListenerList<SimCollisionListener*> collisionListeners;
72 
73     /** The PlayerState may need to be updated whenever an event happens. */
74     Simulation(const PlayerState & ps,const std::list<int> &onStep);
75 
76     /** Steps the simulation a period of time in milliseconds. */
77     virtual void step(int delta);
78     /**
79      * Steps the simulation a period of time in milliseconds.
80      * A call to this function may involve various substeps.
81      * The template parameter specifies a function object that
82      * is called after each substep in the simulation (passing it
83      * the size of the substep in milliseconds).
84      * A negative time may be passed to posfunction, which indicates that
85      * the simulation will go backwards the given amount
86      * of time.
87      * */
88     template <class PosUnaryFunction >
89     void step(int delta,PosUnaryFunction posfunction);
90 
91     virtual void clear();
92     virtual void purge();
93 
94     virtual void addBody(PhysicBody * c);
95     void addBody(int id,PhysicBody * c);
96     virtual void deleteBody(int id);
97 
98     /**
99      * Sets a timer.
100      * @param tm timer event to set.
101      * @returns a reference to the set timer.
102      */
set(const GeneralTimer & tm)103     virtual TimerId set(const GeneralTimer & tm) {
104         return tevents.set(tm);
105     };
106 
setPrecision(int p)107     virtual void setPrecision(int p) {
108         physic.bigStep=p;
109     }
setCollisionPrecision(int p)110     virtual void setCollisionPrecision(int p) {
111         colPrecision=p;
112     }
113 
getPlayerState()114     const PlayerState * getPlayerState() const {
115         return &physic.ps;
116     }
getPlayerState()117     PlayerState * getPlayerState() {
118         return &physic.ps;
119     }
120 
121     PhysicEngine physic;
122     Timer tevents;
123 
124     virtual ~Simulation();
125 
126     void operator = (const Simulation & orig);
127 
128 private:
129     CollisionEngine<PhysicBody,Simulation> * ce;
130     /**
131      * Pushes into the timer event queue the collisions that happened at the exact
132      * collision times. Collisions are detected after they occurred, therefore
133      * the simulation may need to undo a small amount of time so the collisions
134      * are produced at their real times with accuracy. This method unsteps the
135      * simulation if needed and returns the amount of undone time.
136      * */
137     int handle_collisions();
138 
139     const std::list<int> & onStep;
140     // how much time to wait between collision tests
141     int colPrecision;
142     // time remaining before the next collision test
143     int deltaCol;
144     std::list<std::pair<int,Collision> > coldata;
145     template <class PosUnaryFunction >
146     /** Steps the simulation without testing for collisions. */
147     void _step(int delta,PosUnaryFunction posfunction);
148     friend int handle_col(Simulation * sim,PhysicBody * c1,PhysicBody * c2,real t,const Vector2d & normal);
149     friend class Collision;
150     void deleteBody_without_event(int id);
151 };
152 
153 
154 template <class PosUnaryFunction >
step(int delta,PosUnaryFunction posfunction)155 void Simulation::step(int delta,PosUnaryFunction posfunction) {
156     int first_delta = delta;
157     if (physic.minDist<32 && deltaCol>10) {
158         // test collisions regardless the remaining time for the
159         // next test
160         int t=handle_collisions();
161         if (t) {
162             posfunction(-t);
163             _step(t,posfunction);
164         }
165         deltaCol=colPrecision;
166     }
167 
168     if (deltaCol<=delta) {
169         if (deltaCol) {
170             _step(deltaCol,posfunction);
171             delta-=deltaCol;
172             int t=handle_collisions();
173             if (t) {
174                 posfunction(-t);
175                 _step(t,posfunction);
176             }
177         }
178         while (delta>=colPrecision) {
179             _step(colPrecision,posfunction);
180             delta-=colPrecision;
181             int t=handle_collisions();
182             if (t) {
183                 posfunction(-t);
184                 _step(t,posfunction);
185             }
186         }
187         deltaCol=colPrecision-delta;
188     } else
189         deltaCol-=delta;
190 
191     // if step(0) was called force evaluation of _step
192     if (!first_delta || delta)
193         _step(delta,posfunction);
194 };
195 
196 
197 template <class PosUnaryFunction >
_step(int delta,PosUnaryFunction posfunction)198 void Simulation::_step(int delta,PosUnaryFunction posfunction) {
199     for (std::list<int>::const_iterator i=onStep.begin();i!=onStep.end();i++)
200         physic.bodies[(*i)]->onStep();
201     tevents.step(delta,bind2nd(
202                          bind1st3(
203                              mem_fun2(&PhysicEngine::best_step<PosUnaryFunction>),
204                              &physic),
205                      posfunction),
206                  bind2nd(
207                          bind1st3(
208                              mem_fun2(&PhysicEngine::step<PosUnaryFunction>),
209                              &physic),
210                      posfunction)
211                 );
212 };
213 
214 
215 #endif // SIMULATION_H
216