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