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 #include "common.h"
14 #include <math.h>
15 #include "global.h"
16 #include "Pack.h"
17 #include "PyramidBuilding.h"
18 #include "Intersect.h"
19 #include "MeshTransform.h"
20
21 const char* PyramidBuilding::typeName = "PyramidBuilding";
22
PyramidBuilding()23 PyramidBuilding::PyramidBuilding()
24 {
25 // do nothing
26 }
27
PyramidBuilding(const float * p,float a,float w,float b,float h,bool drive,bool shoot,bool rico)28 PyramidBuilding::PyramidBuilding(const float* p, float a,
29 float w, float b, float h, bool drive, bool shoot, bool rico) :
30 Obstacle(p, a, w, b, h,drive,shoot,rico)
31 {
32 finalize();
33 return;
34 }
35
~PyramidBuilding()36 PyramidBuilding::~PyramidBuilding()
37 {
38 // do nothing
39 }
40
finalize()41 void PyramidBuilding::finalize()
42 {
43 Obstacle::setExtents();
44 return;
45 }
46
copyWithTransform(const MeshTransform & xform) const47 Obstacle* PyramidBuilding::copyWithTransform(const MeshTransform& xform) const
48 {
49 float newPos[3], newSize[3], newAngle;
50 memcpy(newPos, pos, sizeof(float[3]));
51 memcpy(newSize, size, sizeof(float[3]));
52 newAngle = angle;
53
54 MeshTransform::Tool tool(xform);
55 bool flipped;
56 tool.modifyOldStyle(newPos, newSize, newAngle, flipped);
57
58 PyramidBuilding* copy =
59 new PyramidBuilding(newPos, newAngle, newSize[0], newSize[1], newSize[2],
60 driveThrough, shootThrough, ricochet);
61
62 copy->ZFlip = !(getZFlip() == flipped);
63
64 return copy;
65 }
66
getType() const67 const char* PyramidBuilding::getType() const
68 {
69 return typeName;
70 }
71
getClassName()72 const char* PyramidBuilding::getClassName() // const
73 {
74 return typeName;
75 }
76
intersect(const Ray & r) const77 float PyramidBuilding::intersect(const Ray& r) const
78 {
79 return timeRayHitsPyramids(r, getPosition(), getRotation(),
80 getWidth(), getBreadth(), getHeight(),
81 getZFlip());
82 }
83
getNormal(const float * p,float * n) const84 void PyramidBuilding::getNormal(const float* p,
85 float* n) const
86 {
87 // get normal in z = const plane
88 const float s = shrinkFactor(p[2]);
89 getNormalRect(p, getPosition(), getRotation(),
90 s * getWidth(), s * getBreadth(), n);
91
92 // make sure we are not way above or way below it
93 // above is good so we can drive on it when it's fliped
94 float top = getPosition()[2] + getHeight();
95 float bottom = getPosition()[2];
96
97 if (s ==0)
98 {
99 if (this->getZFlip())
100 {
101 if (p[2] >= top)
102 {
103 n[0] = n[1] = 0;
104 n[2] = 1;
105 return;
106 }
107 }
108 else
109 {
110 if (p[2] <= bottom)
111 {
112 n[0] = n[1] = 0;
113 n[2] = -1;
114 return;
115 }
116 }
117 }
118
119 // now angle it due to slope of wall
120 // FIXME -- this assumes the pyramid has a square base!
121 const float h = 1.0f / hypotf(getHeight(), getWidth());
122 n[0] *= h * getHeight();
123 n[1] *= h * getHeight();
124 n[2] = h * getWidth();
125
126 if (this->getZFlip())
127 n[2] *= -1;
128 }
129
get3DNormal(const float * p,float * n) const130 void PyramidBuilding::get3DNormal(const float* p,
131 float* n) const
132 {
133 const float epsilon = ZERO_TOLERANCE;
134
135 // get normal in z = const plane
136 const float s = shrinkFactor(p[2]);
137 getNormalRect(p, getPosition(), getRotation(),
138 s * getWidth(), s * getBreadth(), n);
139
140 // make sure we are not way above or way below it
141 // above is good so we can drive on it when it's flipped
142 float top = getPosition()[2]+getHeight();
143 float bottom = getPosition()[2];
144
145 if (s == 0)
146 {
147 if (getZFlip())
148 {
149 if (p[2] >= top)
150 {
151 n[0] = n[1] = 0;
152 n[2] = 1;
153 return;
154 }
155 }
156 else
157 {
158 if (p[2] <= bottom)
159 {
160 n[0] = n[1] = 0;
161 n[2] = -1;
162 return;
163 }
164 }
165 }
166
167 if (s >= 1.0f - epsilon)
168 {
169 n[0] = n[1] = 0;
170 if (getZFlip())
171 n[2] = 1;
172 else
173 n[2] = -1;
174 return;
175 }
176
177 // now angle it due to slope of wall
178 // we figure out if it was an X or Y wall that was hit
179 // by checking the normal returned from getNormalRect()
180 // FIXME -- fugly beyond belief
181 float baseLength = getWidth();
182 const float normalAngle = atan2f(n[1], n[0]);
183 const float rightAngle = fabsf(fmodf(normalAngle - getRotation() + (float)(M_PI/2.0), (float)M_PI));
184 if ((rightAngle < 0.1) || (rightAngle > (M_PI - 0.1)))
185 baseLength = getBreadth();
186 const float h = 1.0f / hypotf(getHeight(), baseLength);
187 n[0] *= h * getHeight();
188 n[1] *= h * getHeight();
189 n[2] = h * baseLength;
190
191 if (this->getZFlip())
192 n[2] *= -1;
193 }
194
inCylinder(const float * p,float radius,float height) const195 bool PyramidBuilding::inCylinder(const float* p,
196 float radius, float height) const
197 {
198 // really rough -- doesn't decrease size with height
199 return (p[2] < (getPosition()[2] + getHeight()))
200 && ((p[2]+height) >= getPosition()[2])
201 && testRectCircle(getPosition(), getRotation(), getWidth(), getBreadth(), p, radius);
202 }
203
inBox(const float * p,float a,float dx,float dy,float height) const204 bool PyramidBuilding::inBox(const float* p, float a,
205 float dx, float dy, float height) const
206 {
207 // Tank is below pyramid ?
208 if (p[2] + height < getPosition()[2])
209 return false;
210 // Tank is above pyramid ?
211 if (p[2] >= getPosition()[2] + getHeight())
212 return false;
213 // Could be inside. Then check collision with the rectangle at object height
214 // This is a rectangle reduced by shrinking but pass the height that we are
215 // not so sure where collision can be
216 const float s = shrinkFactor(p[2], height);
217 return testRectRect(getPosition(), getRotation(),
218 s * getWidth(), s * getBreadth(), p, a, dx, dy);
219 }
220
inMovingBox(const float *,float,const float * p,float _angle,float dx,float dy,float dz) const221 bool PyramidBuilding::inMovingBox(const float*, float,
222 const float* p, float _angle,
223 float dx, float dy, float dz) const
224 {
225 return inBox (p, _angle, dx, dy, dz);
226 }
227
isCrossing(const float * p,float a,float dx,float dy,float height,float * plane) const228 bool PyramidBuilding::isCrossing(const float* p, float a,
229 float dx, float dy, float height, float* plane) const
230 {
231 // if not inside or contained then not crossing
232 if (!inBox(p, a, dx, dy, height) ||
233 testRectInRect(getPosition(), getRotation(),
234 getWidth(), getBreadth(), p, a, dx, dy))
235 return false;
236 if (!plane) return true;
237
238 // it's crossing -- choose which wall is being crossed (this
239 // is a guestimate, should really do a careful test). just
240 // see which wall the point is closest to.
241 const float* p2 = getPosition();
242 const float a2 = getRotation();
243 const float c = cosf(-a2), s = sinf(-a2);
244 const float x = c * (p[0] - p2[0]) - s * (p[1] - p2[1]);
245 const float y = c * (p[1] - p2[1]) + s * (p[0] - p2[0]);
246 float pw[2];
247 if (fabsf(fabsf(x) - getWidth()) < fabsf(fabsf(y) - getBreadth()))
248 {
249 plane[0] = ((x < 0.0) ? -cosf(a2) : cosf(a2));
250 plane[1] = ((x < 0.0) ? -sinf(a2) : sinf(a2));
251 pw[0] = p2[0] + getWidth() * plane[0];
252 pw[1] = p2[1] + getWidth() * plane[1];
253 }
254 else
255 {
256 plane[0] = ((y < 0.0) ? sinf(a2) : -sinf(a2));
257 plane[1] = ((y < 0.0) ? -cosf(a2) : cosf(a2));
258 pw[0] = p2[0] + getBreadth() * plane[0];
259 pw[1] = p2[1] + getBreadth() * plane[1];
260 }
261
262 // now finish off plane equation (FIXME -- assumes a square base)
263 const float h = 1.0f / hypotf(getHeight(), getWidth());
264 plane[0] *= h * getHeight();
265 plane[1] *= h * getHeight();
266 plane[2] = h * getWidth();
267 plane[3] = -(plane[0] * pw[0] + plane[1] * pw[1]);
268 return true;
269 }
270
getHitNormal(const float * pos1,float,const float * pos2,float,float,float,float height,float * normal) const271 bool PyramidBuilding::getHitNormal(
272 const float* pos1, float,
273 const float* pos2, float,
274 float, float, float height,
275 float* normal) const
276 {
277 // pyramids height and flipping
278 // normalize height sign and report that in flip
279 float oHeight = getHeight();
280 bool flip = getZFlip();
281 if (oHeight < 0)
282 {
283 flip = !flip;
284 oHeight = -oHeight;
285 }
286
287 // get Bottom and Top of building
288 float oBottom = getPosition()[2];
289 float oTop = oBottom + oHeight;
290
291 // get higher and lower point of base of colliding object
292 float objHigh = pos1[2];
293 float objLow = pos2[2];
294 if (objHigh < objLow)
295 {
296 float temp = objHigh;
297 objHigh = objLow;
298 objLow = temp;
299 }
300
301 normal[0] = normal[1] = 0;
302 if (flip && objHigh >= oTop)
303 {
304 // base of higher object is over the plateau
305 normal[2] = 1;
306 return true;
307 }
308 else if (!flip && objLow + height < oBottom)
309 {
310 // top of lower object is below the base
311 normal[2] = -1;
312 return true;
313 }
314
315 // get normal in z = const plane
316 const float s = shrinkFactor(pos1[2], height);
317
318 getNormalRect(pos1, getPosition(), getRotation(),
319 s * getWidth(), s * getBreadth(), normal);
320
321 // now angle it due to slope of wall
322 // FIXME -- this assumes the pyramid has a square base!
323 const float h = 1.0f / hypotf(oHeight, getWidth());
324 normal[0] *= h * oHeight;
325 normal[1] *= h * oHeight;
326 normal[2] = h * getWidth();
327
328 if (flip)
329 normal[2] = -normal[2];
330 return true;
331 }
332
getCorner(int index,float * _pos) const333 void PyramidBuilding::getCorner(int index,
334 float* _pos) const
335 {
336 const float* base = getPosition();
337 const float c = cosf(getRotation());
338 const float s = sinf(getRotation());
339 const float w = getWidth();
340 const float h = getBreadth();
341 const float top = getHeight() + base[2];
342 switch (index)
343 {
344 case 0:
345 _pos[0] = base[0] + c * w - s * h;
346 _pos[1] = base[1] + s * w + c * h;
347 if (getZFlip())
348 _pos[2] = top;
349 else
350 _pos[2] = base[2];
351 break;
352 case 1:
353 _pos[0] = base[0] - c * w - s * h;
354 _pos[1] = base[1] - s * w + c * h;
355 if (getZFlip())
356 _pos[2] = top;
357 else
358 _pos[2] = base[2];
359 break;
360 case 2:
361 _pos[0] = base[0] - c * w + s * h;
362 _pos[1] = base[1] - s * w - c * h;
363 if (getZFlip())
364 _pos[2] = top;
365 else
366 _pos[2] = base[2];
367 break;
368 case 3:
369 _pos[0] = base[0] + c * w + s * h;
370 _pos[1] = base[1] + s * w - c * h;
371 if (getZFlip())
372 _pos[2] = top;
373 else
374 _pos[2] = base[2];
375 break;
376 case 4:
377 _pos[0] = base[0];
378 _pos[1] = base[1];
379 if (getZFlip())
380 _pos[2] = base[2];
381 else
382 _pos[2] = top;
383 break;
384 }
385 }
386
shrinkFactor(float z,float height) const387 float PyramidBuilding::shrinkFactor(float z,
388 float height) const
389 {
390 float shrink;
391
392 // Normalize Height and flip to have height > 0
393 float oHeight = getHeight();
394 bool flip = getZFlip();
395 if (oHeight < 0)
396 {
397 flip = !flip;
398 oHeight = - oHeight;
399 }
400
401 // Remove heights bias
402 const float *_pos = getPosition();
403 z -= _pos[2];
404 if (oHeight <= ZERO_TOLERANCE)
405 shrink = 1.0f;
406 else
407 {
408 // Normalize heights
409 z /= oHeight;
410
411 // if flipped the bigger intersection is at top of the object
412 if (flip)
413 {
414 // Normalize the object height, we have not done yet
415 z += height / oHeight;
416 }
417
418 // shrink is that
419 if (flip)
420 shrink = z;
421 else
422 shrink = 1.0f - z;
423 }
424
425 // clamp in 0 .. 1
426 if (shrink < 0.0)
427 shrink = 0.0;
428 else if (shrink > 1.0)
429 shrink = 1.0;
430
431 return shrink;
432 }
433
isFlatTop() const434 bool PyramidBuilding::isFlatTop() const
435 {
436 return getZFlip();
437 }
438
439
440
pack(void * buf) const441 void* PyramidBuilding::pack(void* buf) const
442 {
443 buf = nboPackVector(buf, pos);
444 buf = nboPackFloat(buf, angle);
445 buf = nboPackVector(buf, size);
446
447 unsigned char stateByte = 0;
448 stateByte |= isDriveThrough() ? _DRIVE_THRU : 0;
449 stateByte |= isShootThrough() ? _SHOOT_THRU : 0;
450 stateByte |= getZFlip() ? _FLIP_Z : 0;
451 stateByte |= canRicochet() ? _RICOCHET : 0;
452 buf = nboPackUByte(buf, stateByte);
453
454 return buf;
455 }
456
457
unpack(const void * buf)458 const void* PyramidBuilding::unpack(const void* buf)
459 {
460 buf = nboUnpackVector(buf, pos);
461 buf = nboUnpackFloat(buf, angle);
462 buf = nboUnpackVector(buf, size);
463
464 unsigned char stateByte;
465 buf = nboUnpackUByte(buf, stateByte);
466 driveThrough = (stateByte & _DRIVE_THRU) != 0;
467 shootThrough = (stateByte & _SHOOT_THRU) != 0;
468 ricochet = (stateByte & _RICOCHET) != 0;
469 ZFlip = (stateByte & _FLIP_Z) != 0;
470
471 finalize();
472
473 return buf;
474 }
475
476
packSize() const477 int PyramidBuilding::packSize() const
478 {
479 int fullSize = 0;
480 fullSize += sizeof(float[3]); // pos
481 fullSize += sizeof(float[3]); // size
482 fullSize += sizeof(float); // rotation
483 fullSize += sizeof(uint8_t); // state bits
484 return fullSize;
485 }
486
487
print(std::ostream & out,const std::string & indent) const488 void PyramidBuilding::print(std::ostream& out, const std::string& indent) const
489 {
490 out << indent << "pyramid" << std::endl;
491 const float *_pos = getPosition();
492 out << indent << " position " << _pos[0] << " " << _pos[1] << " "
493 << _pos[2] << std::endl;
494 out << indent << " size " << getWidth() << " " << getBreadth()
495 << " " << getHeight() << std::endl;
496 out << indent << " rotation " << ((getRotation() * 180.0) / M_PI)
497 << std::endl;
498 if (getZFlip())
499 out << indent << " flipz" << std::endl;
500
501 if (isPassable())
502 out << indent << " passable" << std::endl;
503 else
504 {
505 if (isDriveThrough())
506 out << indent << " drivethrough" << std::endl;
507 if (isShootThrough())
508 out << indent << " shootthrough" << std::endl;
509 }
510 if (canRicochet())
511 out << indent << " ricochet" << std::endl;
512 out << indent << "end" << std::endl;
513 return;
514 }
515
516
outputFloat(std::ostream & out,float value)517 static void outputFloat(std::ostream& out, float value)
518 {
519 char buffer[32];
520 snprintf(buffer, 30, " %.8f", value);
521 out << buffer;
522 return;
523 }
524
printOBJ(std::ostream & out,const std::string & UNUSED (indent)) const525 void PyramidBuilding::printOBJ(std::ostream& out, const std::string& UNUSED(indent)) const
526 {
527 int i;
528 float verts[5][3] =
529 {
530 {-1.0f, -1.0f, 0.0f},
531 {+1.0f, -1.0f, 0.0f},
532 {+1.0f, +1.0f, 0.0f},
533 {-1.0f, +1.0f, 0.0f},
534 { 0.0f, 0.0f, 1.0f}
535 };
536 const float sqrt1_2 = (float)M_SQRT1_2;
537 float norms[5][3] =
538 {
539 {0.0f, -sqrt1_2, +sqrt1_2}, {+sqrt1_2, 0.0f, +sqrt1_2},
540 {0.0f, +sqrt1_2, +sqrt1_2}, {-sqrt1_2, 0.0f, +sqrt1_2},
541 {0.0f, 0.0f, -1.0f}
542 };
543 const float* s = getSize();
544 const float k = 1.0f / 8.0f;
545 float txcds[7][2] =
546 {
547 {0.0f, 0.0f}, {k*s[0], 0.0f}, {k*s[0], k*s[1]}, {0.0f, k*s[1]},
548 {0.5f*k*s[0], k*sqrtf(s[0]*s[0]+s[2]*s[2])},
549 {k*s[1], 0.0f}, {0.5f*k*s[1], k*sqrtf(s[1]*s[1]+s[2]*s[2])}
550 };
551 MeshTransform xform;
552 const float degrees = getRotation() * (float)(180.0 / M_PI);
553 const float zAxis[3] = {0.0f, 0.0f, +1.0f};
554 if (getZFlip())
555 {
556 const float xAxis[3] = {1.0f, 0.0f, 0.0f};
557 xform.addSpin(180.0f, xAxis);
558 xform.addShift(zAxis);
559 }
560 xform.addScale(getSize());
561 xform.addSpin(degrees, zAxis);
562 xform.addShift(getPosition());
563 xform.finalize();
564 MeshTransform::Tool xtool(xform);
565 for (i = 0; i < 5; i++)
566 xtool.modifyVertex(verts[i]);
567 for (i = 0; i < 5; i++)
568 xtool.modifyNormal(norms[i]);
569
570 out << "# OBJ - start pyramid" << std::endl;
571 out << "o bzpyr_" << getObjCounter() << std::endl;
572
573 for (i = 0; i < 5; i++)
574 {
575 out << "v";
576 outputFloat(out, verts[i][0]);
577 outputFloat(out, verts[i][1]);
578 outputFloat(out, verts[i][2]);
579 out << std::endl;
580 }
581 for (i = 0; i < 7; i++)
582 {
583 out << "vt";
584 outputFloat(out, txcds[i][0]);
585 outputFloat(out, txcds[i][1]);
586 out << std::endl;
587 }
588 for (i = 0; i < 5; i++)
589 {
590 out << "vn";
591 outputFloat(out, norms[i][0]);
592 outputFloat(out, norms[i][1]);
593 outputFloat(out, norms[i][2]);
594 out << std::endl;
595 }
596 out << "usemtl pyrwall" << std::endl;
597 out << "f -1/-1/-5 -5/-7/-5 -4/-6/-5" << std::endl;
598 out << "f -1/-3/-4 -4/-7/-4 -3/-2/-4" << std::endl;
599 out << "f -1/-1/-3 -3/-7/-3 -2/-6/-3" << std::endl;
600 out << "f -1/-3/-2 -2/-7/-2 -5/-2/-2" << std::endl;
601 out << "f -2/-7/-1 -3/-6/-1 -4/-5/-1 -5/-4/-1" << std::endl;
602
603 out << std::endl;
604
605 incObjCounter();
606
607 return;
608 }
609
610
611 // Local Variables: ***
612 // mode: C++ ***
613 // tab-width: 4 ***
614 // c-basic-offset: 4 ***
615 // indent-tabs-mode: nil ***
616 // End: ***
617 // ex: shiftwidth=4 tabstop=4
618