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