renamed function, changed where it gets called.
[spacedolphin:spacedolphin.git] / src / shape.c
1 /*  Copyright 2011 Andrew Engelbrecht <sudoman@ninthfloor.org>
2
3     This program is free software: you can redistribute it and/or modify
4     it under the terms of the GNU General Public License as published by
5     the Free Software Foundation, either version 3 of the License, or
6     (at your option) any later version.
7
8     This program is distributed in the hope that it will be useful,
9     but WITHOUT ANY WARRANTY; without even the implied warranty of
10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11     GNU General Public License for more details.
12
13     You should have received a copy of the GNU General Public License
14     along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16  */
17
18 #include "spacedolphin.h"
19
20 #define BHOLE       1           // the isloated layer for black holes
21 #define NONBH       1 << 1      // layer so things don't collide with black holes
22
23 enum groups { FLOATG = 1 };
24
25
26 struct objnode *makenode(struct objnode *objx);
27 void initplayer(struct objnode *player);
28 void insertobj(cpSpace * space, struct objnode *objx);
29 cpVect randfit(struct objnode *objx, cpFloat r);
30 cpFloat randrange(cpFloat min, cpFloat max);
31 bool nearobjs(struct objnode *objlast, cpVect randv, cpFloat radius);
32 void giverandspin(struct objnode *objx);
33
34 struct objnode *makecirc(struct objnode *objx, cpSpace * space, bool statb,
35                          cpFloat mass, cpFloat radius, cpVect pos);
36 struct objnode *makepoly(struct objnode *objx, cpSpace * space, bool statb,
37                          cpFloat mass, int nverts, cpVect * verts,
38                          cpVect pos);
39 struct objnode *makeline(struct objnode *objx, cpSpace * space, bool statb,
40                          cpVect v1, cpVect v2);
41
42 struct objnode *makefloat(struct objnode *objx, cpSpace * space,
43                           cpFloat mass, cpFloat radius, cpVect pos);
44 struct objnode *makebhole(struct objnode *objx, cpSpace * space,
45                           cpFloat mass, cpFloat radius, cpVect pos);
46 struct objnode *makerect(struct objnode *objx, cpSpace * space,
47                          cpFloat mass, cpFloat width, cpVect pos);
48 struct objnode *makeplayer(struct objnode *objx, cpSpace * space,
49                            int playernum);
50 struct objnode *maketria(struct objnode *objx, cpSpace * space,
51                          cpFloat mass, cpFloat len, cpFloat width,
52                          cpVect pos);
53
54
55 // initialize a space and then add a whole bunch of shapes and boundaries
56 cpSpace *makeshapes(struct objnode *objroot)
57 {
58     int i;
59     struct timespec time;
60     cpSpace *space;
61     struct objnode *objx;
62
63     space = cpSpaceNew();
64     cpVect gravity = cpv(0, VGRAV);
65     cpSpaceSetGravity(space, gravity);
66
67     objx = objroot;
68
69 /* boundaries for the game... */
70     objx = makeline(objx, space, true, cpv(XMIN, YMIN + 1),
71                     cpv(XMAX, YMIN + 1));
72     objx = makeline(objx, space, true, cpv(XMIN, YMAX - 1),
73                     cpv(XMAX, YMAX - 1));
74
75     objx = makeline(objx, space, true, cpv(XMIN + 1, YMAX),
76                     cpv(XMIN + 1, YMIN));
77     objx = makeline(objx, space, true, cpv(XMAX - 1, YMAX),
78                     cpv(XMAX - 1, YMIN));
79
80     // needed before random intial velocities, placement and rotation
81     curtime(&time);
82     srandom(time.tv_nsec);
83
84 /* deterministically placed objects... */
85     objx = makebhole(objx, space, 0.25, 5, cpv(100, 70));
86     objx = makebhole(objx, space, 0.25, 5, cpv(40, 80));
87
88     objx = makecirc(objx, space, false, 0.5, 10, cpv(120, 57));
89     giverandspin(objx);
90     objx = makecirc(objx, space, false, 0.15, 3, cpv(120, 45));
91     giverandspin(objx);
92
93     objx = makerect(objx, space, 0.25, 10, cpv(80, 20));
94     giverandspin(objx);
95
96
97 /* randomly placed objects... */
98
99     // create player one
100     objx = makeplayer(objx, space, P_ONE);
101     giverandspin(objx);
102
103     // create player two
104     objx = makeplayer(objx, space, P_TWO);
105     giverandspin(objx);
106
107     for (i = 0; i < 7; i++) {
108         objx = makecirc(objx, space, false, 0.25, 5, randfit(objx, 5));
109         giverandspin(objx);
110         objx = makefloat(objx, space, 0.08, 2.0, randfit(objx, 2));
111         giverandspin(objx);
112         objx = makefloat(objx, space, 0.08, 2.0, randfit(objx, 2));
113         giverandspin(objx);
114     }
115
116     cpSpaceAddCollisionHandler(space, C_SHIP, C_LARGE, NULL, *chcolor,
117                                NULL, NULL, objroot);
118     cpSpaceAddCollisionHandler(space, C_LARGE, C_SMALL, NULL, *chcolor,
119                                NULL, NULL, objroot);
120     cpSpaceAddCollisionHandler(space, C_SMALL, C_SHIP, NULL, *chcolor,
121                                NULL, NULL, objroot);
122
123     return space;
124 }
125
126 // makes a circle, adds it to the space and then returns a handle
127 struct objnode *makecirc(struct objnode *objx, cpSpace * space, bool statb,
128                          cpFloat mass, cpFloat radius, cpVect pos)
129 {
130     objx = makenode(objx);
131     objx->geom = S_CIRC;
132
133     if (statb) {
134         objx->s = cpCircleShapeNew(space->staticBody, radius, cpvzero);
135         objx->b = space->staticBody;
136     } else {
137         cpFloat moment = cpMomentForCircle(mass, 0, radius, cpvzero);
138         objx->b = cpBodyNew(mass, moment);
139         objx->s = cpCircleShapeNew(objx->b, radius, cpvzero);
140     }
141
142     insertobj(space, objx);
143     if (!statb)
144         cpBodySetPos(objx->b, pos);
145
146     objx->radius = radius;
147
148     cpShapeSetLayers(objx->s, NONBH);
149
150     cpBodySetAngVel(objx->b, 20);
151     cpShapeSetFriction(objx->s, 0.7);
152     cpShapeSetElasticity(objx->s, 0.7f);
153
154     // defualts
155     objx->colortype = COLOR_LARGE;
156     objx->s->collision_type = C_LARGE;
157
158     return objx;
159 }
160
161 // creates any sort of polygon...
162 struct objnode *makepoly(struct objnode *objx, cpSpace * space, bool statb,
163                          cpFloat mass, int nverts, cpVect * verts,
164                          cpVect pos)
165 {
166     objx = makenode(objx);
167     objx->geom = S_POLY;
168
169     if (statb) {
170         objx->s =
171             cpPolyShapeNew(space->staticBody, nverts, verts, cpvzero);
172         objx->b = space->staticBody;
173     } else {
174         cpFloat moment = cpMomentForPoly(mass, nverts, verts, cpvzero);
175         objx->b = cpBodyNew(mass, moment);
176         objx->s = cpPolyShapeNew(objx->b, nverts, verts, cpvzero);
177     }
178
179     insertobj(space, objx);
180     if (!statb)
181         cpBodySetPos(objx->b, pos);
182
183     int i;
184     for (i = 0; i < nverts; i++)
185         if (cpvlength(verts[i]) > objx->radius)
186             objx->radius = cpvlength(verts[i]);
187
188     cpShapeSetLayers(objx->s, NONBH);
189
190     cpShapeSetFriction(objx->s, 0.7f);
191     cpShapeSetElasticity(objx->s, 0.4f);
192
193     // defaults
194     objx->colortype = COLOR_LARGE;
195     objx->s->collision_type = C_LARGE;
196
197     return objx;
198 }
199
200 // creates a static boundary...
201 struct objnode *makeline(struct objnode *objx, cpSpace * space,
202                          bool statb, cpVect v1, cpVect v2)
203 {
204     objx = makenode(objx);
205     objx->geom = S_LSEG;
206
207     if (statb) {
208         objx->s = cpSegmentShapeNew(space->staticBody, v1, v2, 0);
209         objx->b = space->staticBody;
210     } else {
211         fprintf(stderr,
212                 "Error, non-static line segments are not supported.\n");
213         exit(3);
214     }
215
216     insertobj(space, objx);
217
218     cpShapeSetFriction(objx->s, 1);
219     cpShapeSetElasticity(objx->s, 0.7f);
220
221     objx->colortype = COLOR_LINE;
222     objx->s->collision_type = C_NONE;
223
224     return objx;
225 }
226
227
228
229 // makes a circle unaffected by vertical gravity. attracted to bholes
230 struct objnode *makefloat(struct objnode *objx, cpSpace * space,
231                           cpFloat mass, cpFloat radius, cpVect pos)
232 {
233     objx = makecirc(objx, space, false, mass, radius, pos);
234
235     objx->b->velocity_func = &orbit;
236
237     // don't collide with black holes
238     cpShapeSetGroup(objx->s, FLOATG);
239
240     cpBodySetAngVel(objx->b, 2);
241
242     objx->colortype = COLOR_SMALL;
243     objx->s->collision_type = C_SMALL;
244
245     return objx;
246 }
247
248 // makes a black hole that does not move. it attracts "float" objects.
249 struct objnode *makebhole(struct objnode *objx, cpSpace * space,
250                           cpFloat mass, cpFloat radius, cpVect pos)
251 {
252     // black holes aren't 'static', because we want them to rotate
253     objx = makecirc(objx, space, false, mass, radius, pos);
254
255     // ...but they should not change their 0 velocity:
256     objx->b->velocity_func = &dontfall;
257     objx->bhole = true;
258
259     // don't collide
260     cpShapeSetGroup(objx->s, FLOATG);
261     cpShapeSetLayers(objx->s, BHOLE);
262
263     cpBodySetVelLimit(objx->b, 0);
264     cpBodySetAngVel(objx->b, 10);
265
266     objx->colortype = COLOR_BHOLE;
267
268     return objx;
269 }
270
271 // make a ship for player number playernum
272 struct objnode *makeplayer(struct objnode *objx, cpSpace * space,
273                            int playernum)
274 {
275     objx = maketria(objx, space, 1.33, 20, 8, randfit(objx, 10));
276     objx->ownedby = playernum;
277     initplayer(objx);
278
279     objx->colortype = COLOR_SHIP;
280     objx->s->collision_type = C_SHIP;
281
282     return objx;
283 }
284
285 // makes a triangle...
286 struct objnode *maketria(struct objnode *objx, cpSpace * space,
287                          cpFloat mass, cpFloat len, cpFloat width,
288                          cpVect pos)
289 {
290     cpVect verts[3];
291
292     verts[0] = cpv(0, 2.0 * len / 3.0);
293     verts[1] = cpv(width / 2.0, -len / 3.0);
294     verts[2] = cpv(-width / 2.0, -len / 3.0);
295
296     cpShapeSetFriction(objx->s, 0.50);
297
298     objx = makepoly(objx, space, false, mass, 3, verts, pos);
299
300     return objx;
301 }
302
303 // makes a rectangle...
304 struct objnode *makerect(struct objnode *objx, cpSpace * space,
305                          cpFloat mass, cpFloat width, cpVect pos)
306 {
307     cpFloat hw;
308     cpVect verts[4];
309
310     hw = width / 2;
311
312     verts[0] = cpv(-hw, -hw);
313     verts[1] = cpv(-hw, hw);
314     verts[2] = cpv(hw, hw);
315     verts[3] = cpv(hw, -hw);
316
317     objx = makepoly(objx, space, false, mass, 4, verts, pos);
318
319     return objx;
320 }
321
322 // sets the object spinning
323 void giverandspin(struct objnode *objx)
324 {
325     cpBodySetVel(objx->b, cpv(randrange(-60, 60), randrange(-60, 60)));
326 }
327
328 // picks a random spot to place an object, until nearobjs() returns false.
329 cpVect randfit(struct objnode *objlast, cpFloat r)
330 {
331     int i;
332     cpVect xymin, xymax;
333     cpVect randv;
334
335     xymin = cpv(XMIN + XYBUF, YMIN + XYBUF);
336     xymax = cpv(XMAX - XYBUF, YMAX - XYBUF);
337
338     for (i = 0; i < 1000; i++) {
339         randv.x = randrange(xymin.x, xymax.x);
340         randv.y = randrange(xymin.y, xymax.y);
341         if (nearobjs(objlast, randv, r))
342             return randv;
343     }
344
345     fprintf(stderr,
346             "Error: cannot find a random place to fit an object.\n");
347     exit(4);
348
349     return randv;
350 }
351
352 // returns a random number between min and max
353 cpFloat randrange(cpFloat min, cpFloat max)
354 {
355     return min + random() / (double) RAND_MAX *(max - min);
356 }
357
358 // returns true if an object with intersects with another object.
359 // (crudely calculated with max radius from center of gravity.)
360 bool nearobjs(struct objnode *objlast, cpVect randv, cpFloat radius)
361 {
362     cpFloat dist;
363     struct objnode *objch = objlast;
364
365     while ((objch = objch->prev)) {
366
367         if (objch->geom == S_CIRC || objch->geom == S_POLY)
368             dist = cpvlength(cpvsub(randv, cpBodyGetPos(objch->b)));
369         else if (objch->geom == S_LSEG) {
370             cpVect enda, endb, segv, posv;
371             cpFloat angle;
372
373             enda = cpSegmentShapeGetA(objch->s);
374             endb = cpSegmentShapeGetB(objch->s);
375             if (cpvlength(cpvsub(randv, enda)) <
376                 cpvlength(cpvsub(randv, endb))) {
377                 segv = cpvsub(enda, endb);
378                 posv = cpvsub(randv, endb);
379             } else {
380                 segv = cpvsub(endb, enda);
381                 posv = cpvsub(randv, enda);
382             }
383             angle = cpvtoangle(posv) - cpvtoangle(segv);
384             if (fabs(angle) < PI / 2)
385                 dist = fabs(sin(angle) * cpvlength(posv));
386             else
387                 dist = cpvlength(posv);
388         } else
389             dist = INFINITY;
390
391         if (dist > objch->radius + radius)
392             continue;
393         else
394             return false;
395     }
396
397     return true;
398 }
399
400 // inserts a struct for pointing to a new body/shape
401 struct objnode *makenode(struct objnode *objx)
402 {
403     struct objnode *objnew;
404
405     objnew = malloc(sizeof(struct objnode));
406
407     objnew->geom = S_NONE;
408     objnew->radius = 0;
409     objnew->b = NULL;
410     objnew->s = NULL;
411     objnew->bhole = false;
412     objnew->pinfo = NULL;
413     objnew->ownedby = P_NONE;
414     objnew->colortype = COLOR_NONE;
415
416     objnew->next = objx->next;
417     objnew->prev = objx;
418     objx->next = objnew;
419
420     return objnew;
421 }
422
423 // initializes playerinfo struct. starts with no thrust and default hp
424 void initplayer(struct objnode *player)
425 {
426     player->pinfo = malloc(sizeof(struct playerinfo));
427
428     player->pinfo->hp = HPSTART;
429
430     player->pinfo->thrust.prevf.force = cpvzero;
431     player->pinfo->thrust.prevf.tforce = cpvzero;
432     player->pinfo->thrust.markt.tv_sec = 0;
433     player->pinfo->thrust.markt.tv_nsec = 0;
434
435     player->pinfo->thrust.up = false;
436     player->pinfo->thrust.down = false;
437     player->pinfo->thrust.left = false;
438     player->pinfo->thrust.right = false;
439     player->pinfo->thrust.cw = false;
440     player->pinfo->thrust.cwt = 0;
441     player->pinfo->thrust.ccw = false;
442     player->pinfo->thrust.ccwt = 0;
443
444 }
445
446 // inserts object's body and shape into the space
447 void insertobj(cpSpace * space, struct objnode *objx)
448 {
449     if (!cpBodyIsStatic(objx->b))
450         cpSpaceAddBody(space, objx->b);
451     if (objx->s != NULL)
452         cpSpaceAddShape(space, objx->s);
453 }
454
455 // removes an object and frees the memory associated with it.
456 void rmobj(struct objnode *objx)
457 {
458     if (objx->prev == NULL) {
459         fprintf(stderr, "*** Error: attempted to remove root object.\n");
460         exit(2);
461     }
462
463     cpShapeFree(objx->s);
464     if (!cpBodyIsStatic(objx->b))
465         cpBodyFree(objx->b);
466     free(objx->pinfo);
467
468     objx->prev->next = objx->next;
469     if (objx->next)
470         objx->next->prev = objx->prev;
471
472     free(objx);
473 }
474
475 // removes every object after the current one
476 void rmobjs(struct objnode *objroot)
477 {
478     while (objroot->next)
479         rmobj(objroot->next);
480 }