wip II
[sdk] / samples / guiAndGfx / openrider / game.ec
1 import "math"
2
3 define PI=3.14159265358979323846;
4 define BOX_TOLERANCE=0.5;
5
6 define max_version_code_supported=0;
7 define version_name="0.0";
8    //This string should be no longer than 15 characters
9
10 public:
11
12 //Coordinates are in units of pixels
13 //Velocity is in units of pixels per frame
14 //A frame is defined as 1/LOGICAL_FPS seconds, or one iteration of g_frame()
15
16 define LOGICAL_FPS=512;
17    //is really 512, but this is for testing
18 //define DRAWN_FPS=60;
19
20 enum GameLineType {regular,speed,slow,decor};
21 enum GameFloorOrCeiling {floor,ceiling,both};
22
23 enum GameVehicleType {none, ball};
24
25 struct GameLine {
26    double x0,y0,x1,y1;
27    GameLineType type;
28    GameFloorOrCeiling floor_or_ceiling;
29 };
30 class GameVehicle {
31    //complicated vehicles that can't be described by a single location/velocity need something
32    //  so the camera will center on it.
33    Coord2D location;
34         Vector2D velocity;
35    GameVehicleType type;
36
37    void OnSerialize(IOChannel channel)
38    {
39       channel.Put(type);
40       channel.Put(location);
41       channel.Put(velocity);
42    }
43
44    void OnUnserialize(IOChannel channel)
45    {
46       if(!this)
47       {
48          GameVehicleType vtype;
49          channel.Get(vtype);
50          if(vtype == ball)
51          {
52             GameBall::OnUnserialize(class(GameBall), this, channel);
53             return;
54          }
55          else
56             this = GameVehicle { };
57       }
58
59       if(!type)
60          channel.Get(type);         
61       channel.Get(location);
62       channel.Get(velocity);
63    }
64
65    virtual void Update(Game game);
66 };
67 class GameBall : GameVehicle {
68    type = ball;
69
70         double radius;
71         double elasticity;
72         double angle;
73         double angle_velocity;
74
75    void OnSerialize(IOChannel channel)
76    {
77       GameVehicle::OnSerialize(channel);
78       channel.Put(radius);
79       channel.Put(elasticity);
80       channel.Put(angle);
81       channel.Put(angle_velocity);
82    }
83
84    void OnUnserialize(IOChannel channel)
85    {
86       this = GameBall { };
87       GameVehicle::OnUnserialize(channel);
88       channel.Get(radius);
89       channel.Get(elasticity);
90       channel.Get(angle);
91       channel.Get(angle_velocity);
92    }
93
94    //support backtracing if ball is within box for line
95    void Update(Game game) {
96       //TODO:  check for collision with other balls
97       uint count;
98       double time_left=1.0;
99       BallLineReturn soonest = {INF,{0,0}};
100       GameLine *soonest_line;
101       Vector2D vb,vp;
102       BallLineReturn bl_result;
103       uint max_collisions_per_frame=16;
104       do
105       {
106          double x0 = location.x - radius;
107          double y0 = location.y - radius;
108          double x1 = location.x + radius;
109          double y1 = location.y + radius;
110          uint n=0;
111          if (velocity.x<0)
112             x0 += velocity.x;
113          else
114             x1 += velocity.x;
115          if (velocity.y<0)
116             y0 += velocity.y;
117          else
118             y1 += velocity.y;
119          game.FindLinesInBox(x0,y0,x1,y1, true);
120          soonest_line=null;
121          for (i:game.linesInBox)
122          {
123             double t;
124             n++;
125             //TODO:  check for floor/ceiling/decor
126             if (i.type == decor)
127                continue;
128             vb = {i.x1-i.x0,i.y1-i.y0};
129             vp = {i.x0-location.x,i.y0-location.y};
130
131             BallLineCollide(bl_result,radius,velocity,vb,vp);
132
133             t=bl_result.t;
134             //TODO:  Find a better way to deal with the teleport problem when ball sits in weird situations
135             //  Currently, I'm just filtering out outrageously low negative ts (negative ts don't exactly make sense, but they keep the ball from falling through lines)
136             //if (t<=time_left && t<soonest.t) {
137             if (t>-100.0 && t<=time_left && t<soonest.t) {
138                soonest = bl_result;
139                soonest_line=&i;
140                //printf("Collide with line %ld Q=(%f,%f,%f,%f) L=(%f,%f,%f,%f)\n", soonest_line-game.lines.array, x0,y0,x1,y1, );
141             }
142          }
143          if (soonest_line)
144          {
145             Vector2D v_parallel, v_normal;
146             Vector2D v_parallel_normalized;
147             double dp;
148             double elasticity_cur;
149             bool clockcont = false; //if positive angle velocity contributes to speed toward parallel of ball velocity
150             
151             //printf("COLLIDE! (location=(%f, %f), p = <%f, %f>)\n",location.x, location.y, soonest_line->x0-location.x,soonest_line->y0-location.y);
152
153             //if (soonest.t<0.0)
154             //   PrintLn("Negative time ", soonest.t);
155             //if (soonest.t>=0.0 && soonest.t<0.01)
156             //   PrintLn("Negative time after subtract", soonest.t);
157
158             soonest.t-=0.01; //don't fall all the way in, or we'll have multiple collisions after this
159             
160             //compensate for the time taken to get to the collision
161             location.x += velocity.x*soonest.t;
162             location.y += velocity.y*soonest.t;
163             time_left -= soonest.t;
164
165             //decompose into parallel and normal vectors (with relation to line of collision)
166             v_parallel = soonest.s;
167             dp = DotProduct(velocity, soonest.s);
168             v_parallel.x *= dp;
169             v_parallel.y *= dp;
170             v_normal.Sub(velocity, v_parallel);
171
172             /* //elastic collision:  v' = vp-e*vn;
173             v_normal.x *= elasticity;
174             v_normal.y *= elasticity;
175             velocity.Sub(v_parallel,v_normal);
176             */
177
178             if (v_normal.y!=0.0 && v_parallel.x!=0.0) {
179                if ((v_normal.y>0 && v_parallel.x>0) || (v_normal.y<0 && v_parallel.x<0))
180                   clockcont = true;
181             } else {
182                if ((v_normal.x<0 && v_parallel.y>0) || (v_normal.x>0 && v_parallel.y<0))
183                   clockcont = true;
184             }
185             
186             //we need a reference to this before v_normal gets zeroed if elasticity_cur is 0.
187             v_parallel_normalized = {-v_normal.y, v_normal.x};
188             v_parallel_normalized.Normalize();
189
190             elasticity_cur = -elasticity;
191             if (soonest_line->type != regular)
192                elasticity_cur *= 0.3; //to make speed lines easier to use
193
194             //elastic collision:  multiply the normal by negative elasticity
195             v_normal.x *= elasticity_cur;
196             v_normal.y *= elasticity_cur;
197             
198             //an attempt at the effects of angular velocity and friction
199             {
200                double gs; //goal velocity squared
201                double g; //goal velocity
202                double gsfa; //goal velocity contributed by angular velocity
203                bool gneg = false; //g is negative
204                double dampener = 0.01;
205
206                gs = 3*DotProduct(v_parallel,v_parallel);
207                gsfa = 2*angle_velocity*angle_velocity*PI*radius*radius*radius;
208                gsfa *= dampener;
209                if (angle_velocity<0)
210                   gsfa = -gsfa;
211                if (clockcont)
212                   gs += gsfa;
213                else
214                   gs -= gsfa;
215                gs /= 3+(2*PI*radius*dampener);
216                if (gs < 0.0) {
217                   gneg = true;
218                   gs = -gs;
219                }
220                g = sqrt(gs);
221                if (gneg)
222                   g = -g;
223                g *= 0.9993; //energy loss
224                v_parallel = v_parallel_normalized;
225                if (clockcont) {
226                   v_parallel.x = -v_parallel.x;
227                   v_parallel.y = -v_parallel.y;
228                }
229                v_parallel.x *= g;
230                v_parallel.y *= g;
231                if (clockcont)
232                   angle_velocity = g/radius;
233                else
234                   angle_velocity = -g/radius;
235             }
236             
237             velocity.Add(v_parallel, v_normal);
238
239             //at last, we'll handle speed/slow lines by simply changing the angular velocity
240             if (soonest_line->type == slow)
241                angle_velocity -= 0.001;
242             else if (soonest_line->type == speed)
243                angle_velocity += 0.001;
244          }
245          else
246             location.Add(location,velocity);
247          if (!max_collisions_per_frame--)
248             break;
249       } while (soonest_line);
250       velocity.Add(velocity, game.gravity);
251       angle += angle_velocity;
252       if (angle<0 || angle>=2*3.1415926535897932384626)
253                    angle = fmod(angle,2*3.1415926535897932384626); //prevents angle from accumulating an insanely high or low value
254    }
255 };
256
257 class Game {
258 public:
259    Instance master;
260    Array<GameLine> lines {};
261    Array<GameLine> linesInBox {};
262    Array<uint> linesInBoxIndices {};
263    Array<GameVehicle> vehicles {};
264    Vector2D gravity;
265
266    uint version_code; //which version of OpenRider to act like
267
268    version_code = max_version_code_supported;
269    
270    ~Game() { FreeAll(); }
271    void FreeAll(void) {
272       for (i:vehicles)
273          delete i;
274       //we don't need to worry about the other things because they are automatically freed
275    }
276
277    void OnUnserialize(IOChannel channel)
278    {
279       FreeAll();
280
281       channel.Get(gravity);
282       channel.Get(lines);
283       channel.Get(vehicles);
284    }
285
286    void OnSerialize(IOChannel channel)
287    {
288       channel.Put(gravity);
289       channel.Put(lines);
290       channel.Put(vehicles);
291    }
292
293    void Init(void) {
294       gravity = {0, 0.002};
295       Clear();
296       ResetBall();
297    }
298    void Clear(void) {
299       lines.size = 0;
300       version_code = max_version_code_supported;
301    }
302    void ResetBall(void) {
303       for (i:vehicles)
304          delete i;
305       vehicles.size = 1;
306
307       vehicles[0] = GameBall {
308          location = {-300+32,-200+32};
309          velocity = {0.0, 0.0};
310          radius = 24.0;
311          elasticity = 0.3; //if this is 0, the ball rolls the wrong way for some reason
312          angle = 0.0;
313          angle_velocity = 0.0;
314       };
315    }
316    void Frame(void)
317    {
318       for (i:vehicles)
319       {
320          GameVehicle v = i;
321          v.Update(this);
322       }
323    }
324    void FrameMulti(uint count) {
325       while (count--)
326          Frame();
327    }
328    //parameters are the camera's field of vision or bigger
329    void DrawFrame(double x0, double y0, double x1, double y1) {
330       FindLinesInBox(x0,y0,x1,y1, true);
331       for (i:linesInBox)
332          DrawLine(master, i);
333         for (i:vehicles) {
334          if (i.type==ball)
335                    DrawBall(master, (GameBall)i);
336       }
337    }
338
339    void AddLineSeries(double c_array[],uint count) {
340       double *c=c_array;
341       if (count<2)
342         return;
343       count--;
344       for (;count--;c+=2)
345          lines.Add({c[0],c[1],c[2],c[3],regular,floor});
346    }
347    
348    //returns true if any lines were erased
349    bool EraseAtBox(double x0, double y0, double x1, double y1) {
350       uint erasures = 0;
351       FindLinesInBox(x0,y0,x1,y1, false);
352       //the linesInBoxIndices array is assumed to be sorted
353       for (i:linesInBoxIndices) {
354          uint r = i-erasures;
355          //FindLinesInBox only gives us an estimate of lines in the box, so we will do a finer check here
356          if (!LineReallyInBox(lines[i].x0, lines[i].y0, lines[i].x1, lines[i].y1, x0,y0,x1,y1))
357             continue;
358          //lines.Remove((IteratorPointer)(lines.array+i-erasures));
359          memmove(lines.array+r, lines.array+r+1, (lines.size-r-1)*sizeof(*lines.array));
360          erasures++;
361       }
362       lines.size -= erasures;
363       return !!erasures;
364    }
365    
366    //used for efficiency, not for precision
367    //This only checks for lines in box by looking at the bounding boxes of lines in question
368    void FindLinesInBox(double x0, double y0, double x1, double y1, bool tolerance) {
369       #define Swap(a,b) {tmp=a;a=b;b=tmp;}
370       double tmp;
371       if (x0>x1)
372          Swap(x0,x1);
373       if (y0>y1)
374          Swap(y0,y1);
375       if (tolerance) {
376          x0 -= BOX_TOLERANCE;
377          y0 -= BOX_TOLERANCE;
378          x1 += BOX_TOLERANCE;
379          y1 += BOX_TOLERANCE;
380       }
381       linesInBox.minAllocSize = lines.count;
382       linesInBoxIndices.minAllocSize = lines.count;
383       linesInBox.RemoveAll();
384       linesInBoxIndices.RemoveAll();
385       for (i:lines) {
386          double lx0=i.x0,ly0=i.y0,lx1=i.x1,ly1=i.y1;
387          if (lx0>lx1)
388             Swap(lx0,lx1);
389          if (ly0>ly1)
390             Swap(ly0,ly1);
391          if (lx0>x1 || ly0>y1 || x0>lx1 || y0>ly1)
392             continue;
393          linesInBox.Add(i);
394          linesInBoxIndices.Add(&i - lines.array);
395       }
396       #undef Swap
397    }
398    
399    virtual void Instance::DrawLine(GameLine line);
400    virtual void Instance::DrawBall(GameBall ball);
401 }
402
403 /*void g_init(void);
404 void g_uninit(void);
405 void g_frame(void);
406 void g_frame_multi(uint count);
407 void g_draw_frame(void);
408
409 void g_add_line(double x0,double y0,double x1,double y1,char type,char floor_or_ceiling);
410 */