1 /* bzflag
2  * Copyright (c) 1993-2021 Tim Riker
3  *
4  * This package is free software;  you can redistribute it and/or
5  * modify it under the terms of the license found in the file
6  * named COPYING that should have accompanied this file.
7  *
8  * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
9  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11  */
12 
13 
14 #include "common.h"
15 
16 // interface header
17 #include "DropGeometry.h"
18 
19 // system headers
20 #include <stdlib.h>
21 
22 // common headers
23 #include "Intersect.h"
24 #include "Obstacle.h"
25 #include "MeshFace.h"
26 #include "BaseBuilding.h"
27 #include "ObstacleMgr.h"
28 #include "CollisionManager.h"
29 #include "PhysicsDriver.h"
30 #include "WorldInfo.h"
31 #include "BZDBCache.h"
32 
33 
34 //
35 // Datatype Definitions
36 //
37 class HoldingList
38 {
39 public:
40     HoldingList();
41     ~HoldingList();
42     void copy(const ObsList* list);
43 public:
44     int size;
45     int count;
46     Obstacle** list;
47 };
48 
49 static HoldingList rayList; // ray intersection list
50 
51 //
52 // Function Prototypes
53 //
54 static int compareAscending(const void* a, const void* b);
55 static int compareDescending(const void* a, const void* b);
56 static bool isDeathLanding(const Obstacle* landing);
57 static bool isOpposingTeam(const Obstacle* obs, int team);
58 static bool isValidLanding(const Obstacle* obs);
59 static bool isValidClearance(const float pos[3], float radius,
60                              float height, int team);
61 static bool dropIt(float pos[3], float minZ, float maxZ,
62                    float radius, float height, int team);
63 
64 
65 /******************************************************************************/
66 
dropPlayer(float pos[3],float minZ,float maxZ)67 bool DropGeometry::dropPlayer(float pos[3], float minZ, float maxZ)
68 {
69     // fudge-it to avoid spawn stickiness on obstacles
70     const float fudge = 0.001f;
71     const float tankHeight = BZDBCache::tankHeight + fudge;
72     bool value = dropIt(pos, minZ, maxZ, BZDBCache::tankRadius, tankHeight, -1);
73     pos[2] += fudge;
74     return value;
75 }
76 
77 
dropFlag(float pos[3],float minZ,float maxZ)78 bool DropGeometry::dropFlag(float pos[3], float minZ, float maxZ)
79 {
80     const float flagHeight = BZDB.eval(StateDatabase::BZDB_FLAGHEIGHT);
81     return dropIt(pos, minZ, maxZ, BZDBCache::tankRadius, flagHeight, -1);
82 }
83 
84 
dropTeamFlag(float pos[3],float minZ,float maxZ,int team)85 bool DropGeometry::dropTeamFlag(float pos[3], float minZ, float maxZ,
86                                 int team)
87 {
88     // team flags do not get real clearance checks (radius = 0)
89     // if you want to put some smarts in to check for wedging
90     // (flag is stuck amongst obstacles), then add the code into
91     // isValidClearance().
92     const float flagHeight = BZDB.eval(StateDatabase::BZDB_FLAGHEIGHT);
93     return dropIt(pos, minZ, maxZ, 0.0f, flagHeight, team);
94 }
95 
96 
97 /******************************************************************************/
98 
isDeathLanding(const Obstacle * obs)99 static inline bool isDeathLanding(const Obstacle* obs)
100 {
101     if (obs->getType() == MeshFace::getClassName())
102     {
103         const MeshFace* face = (const MeshFace*) obs;
104         int driver = face->getPhysicsDriver();
105         const PhysicsDriver* phydrv = PHYDRVMGR.getDriver(driver);
106         if ((phydrv != NULL) && (phydrv->getIsDeath()))
107             return true;
108     }
109     return false;
110 }
111 
112 
isOpposingTeam(const Obstacle * obs,int team)113 static inline bool isOpposingTeam(const Obstacle* obs, int team)
114 {
115     if (team < 0)
116         return false;
117 
118     if (obs->getType() != BaseBuilding::getClassName())
119         return false;
120 
121     const BaseBuilding* base = (const BaseBuilding*) obs;
122     if (base->getTeam() == team)
123         return false;
124 
125     return true;
126 }
127 
128 
isValidLanding(const Obstacle * obs)129 static inline bool isValidLanding(const Obstacle* obs)
130 {
131     // must be a flattop buildings
132     if (!obs->isFlatTop())
133         return false;
134     // drivethrough buildings are not potential landings
135     if (obs->isDriveThrough())
136         return false;
137 
138     // death buildings are not potential landings
139     if (isDeathLanding(obs))
140         return false;
141 
142     return true;
143 }
144 
145 
isValidClearance(const float pos[3],float radius,float height,int team)146 static bool isValidClearance(const float pos[3], float radius,
147                              float height, int team)
148 {
149     const ObsList* olist = COLLISIONMGR.cylinderTest(pos, radius, height);
150 
151     // invalid if it touches a building
152     for (int i = 0; i < olist->count; i++)
153     {
154         const Obstacle* obs = olist->list[i];
155         const float zTop = obs->getExtents().maxs[2];
156         if (zTop > pos[2])
157         {
158             if (obs->inCylinder(pos, radius, height))
159                 return false;
160         }
161         else
162         {
163             // do not check coplanars unless they are fatal
164             if (isDeathLanding(obs) || isOpposingTeam(obs, team))
165             {
166                 const float fudge = 0.001f; // dig in a little to make sure
167                 const float testPos[3] = {pos[0], pos[1], pos[2] - fudge};
168                 if (obs->inCylinder(testPos, radius, height + fudge))
169                     return false;
170             }
171         }
172     }
173 
174     return true;
175 }
176 
177 
compareAscending(const void * a,const void * b)178 static int compareAscending(const void* a, const void* b)
179 {
180     const Obstacle* obsA = *((const Obstacle* const *)a);
181     const Obstacle* obsB = *((const Obstacle* const *)b);
182     const float topA = obsA->getExtents().maxs[2];
183     const float topB = obsB->getExtents().maxs[2];
184     if (topA < topB)
185         return -1;
186     else if (topA > topB)
187         return +1;
188     else
189         return 0;
190 }
191 
192 
compareDescending(const void * a,const void * b)193 static int compareDescending(const void* a, const void* b)
194 {
195     const Obstacle* obsA = *((const Obstacle* const *)a);
196     const Obstacle* obsB = *((const Obstacle* const *)b);
197     const float topA = obsA->getExtents().maxs[2];
198     const float topB = obsB->getExtents().maxs[2];
199     if (topA < topB)
200         return +1;
201     else if (topA > topB)
202         return -1;
203     else
204         return 0;
205 }
206 
207 
208 /******************************************************************************/
209 
dropIt(float pos[3],float minZ,float maxZ,float radius,float height,int team)210 static bool dropIt(float pos[3], float minZ, float maxZ,
211                    float radius, float height, int team)
212 {
213     int i;
214 
215     // special case, just check the ground
216     if (maxZ <= 0.0f)
217     {
218         pos[2] = 0.0f;
219         if (isValidClearance(pos, radius, height, team))
220             return true;
221         else
222             return false;
223     }
224 
225     // adjust the position to the minimum level
226     if (pos[2] < minZ)
227         pos[2] = minZ;
228 
229     // use a downwards ray to hit the onFlatTop() buildings
230     const float maxHeight = COLLISIONMGR.getWorldExtents().maxs[2];
231     const float dir[3] = {0.0f, 0.0f, -1.0f};
232     const float org[3] = {pos[0], pos[1], maxHeight + 1.0f};
233     Ray ray(org, dir);
234 
235     // list of  possible landings
236     const ObsList* olist = COLLISIONMGR.rayTest(&ray, MAXFLOAT);
237     rayList.copy(olist); // copy the list, so that COLLISIONMGR can be re-used
238 
239     const float startZ = pos[2];
240 
241     // are we in the clear?
242     if (isValidClearance(pos, radius, height, team))
243     {
244         // sort from highest to lowest
245         qsort(rayList.list, rayList.count, sizeof(Obstacle*), compareDescending);
246         // no interference, try dropping
247         for (i = 0; i < rayList.count; i++)
248         {
249             const Obstacle* obs = rayList.list[i];
250             const float zTop = obs->getExtents().maxs[2];
251             // make sure that it's within the limits
252             if ((zTop > startZ) || (zTop > maxZ))
253                 continue;
254             if (zTop < minZ)
255                 break;
256             pos[2] = zTop;
257 
258             if (obs->intersect(ray) >= 0.0f)
259             {
260                 if (isValidLanding(obs) &&
261                         isValidClearance(pos, radius, height, team))
262                     return true;
263                 else
264                 {
265                     // a potential hit surface was tested and failed, unless
266                     // we want to pass through it, we have to return false.
267                     return false;
268                 }
269             }
270         }
271         // check the ground
272         if (minZ <= 0.0f)
273         {
274             pos[2] = 0.0f;
275             if (isValidClearance(pos, radius, height, team))
276                 return true;
277         }
278     }
279     else
280     {
281         // sort from lowest to highest
282         qsort(rayList.list, rayList.count, sizeof(Obstacle*), compareAscending);
283         // we're blocked, try climbing
284         for (i = 0; i < rayList.count; i++)
285         {
286             const Obstacle* obs = rayList.list[i];
287             const float zTop = obs->getExtents().maxs[2];
288             // make sure that it's within the limits
289             if ((zTop < startZ) || (zTop < minZ))
290                 continue;
291             if (zTop > maxZ)
292                 return false;
293             pos[2] = zTop;
294 
295             if (isValidLanding(obs) &&
296                     (obs->intersect(ray) >= 0.0f) &&
297                     isValidClearance(pos, radius, height, team))
298                 return true;
299         }
300     }
301 
302     return false;
303 }
304 
305 
306 /******************************************************************************/
307 
HoldingList()308 HoldingList::HoldingList()
309 {
310     size = count = 0;
311     list = NULL;
312     return;
313 }
314 
~HoldingList()315 HoldingList::~HoldingList()
316 {
317     delete[] list;
318     return;
319 }
320 
copy(const ObsList * olist)321 void HoldingList::copy(const ObsList* olist)
322 {
323     if (olist->count > size)
324     {
325         // increase the list size
326         delete[] list;
327         size = olist->count;
328         list = new Obstacle*[size];
329     }
330     count = olist->count;
331     memcpy(list, olist->list, count * sizeof(Obstacle*));
332     return;
333 }
334 
335 
336 // Local Variables: ***
337 // mode: C++ ***
338 // tab-width: 4 ***
339 // c-basic-offset: 4 ***
340 // indent-tabs-mode: nil ***
341 // End: ***
342 // ex: shiftwidth=4 tabstop=4
343