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 // interface headers
14 #include "WallSceneNode.h"
15 #include "PolyWallSceneNode.h"
16 
17 // system headers
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <math.h>
21 #include <string.h>
22 
23 // common implementation headers
24 #include "StateDatabase.h"
25 #include "BZDBCache.h"
26 
27 // local implementation headers
28 #include "ViewFrustum.h"
29 
30 // FIXME (SceneRenderer.cxx is in src/bzflag)
31 #include "SceneRenderer.h"
32 
WallSceneNode()33 WallSceneNode::WallSceneNode() : numLODs(0),
34     elementAreas(NULL),
35     style(0)
36 {
37     dynamicColor = NULL;
38     color[3] = 1.0f;
39     modulateColor[3] = 1.0f;
40     lightedColor[3] = 1.0f;
41     lightedModulateColor[3] = 1.0f;
42     color[3] = 1.0f;
43     color[3] = 1.0f;
44     color[3] = 1.0f;
45     setColor(1.0f, 1.0f, 1.0f);
46     setModulateColor(1.0f, 1.0f, 1.0f);
47     setLightedColor(1.0f, 1.0f, 1.0f);
48     setLightedModulateColor(1.0f, 1.0f, 1.0f);
49     useColorTexture = false;
50     noCulling = false;
51     noSorting = false;
52     isBlended = false;
53     wantBlending = false;
54     wantSphereMap = false;
55     alphaThreshold = 0.0f;
56     return;
57 }
58 
~WallSceneNode()59 WallSceneNode::~WallSceneNode()
60 {
61     // free element area table
62     delete[] elementAreas;
63 }
64 
getPlane() const65 const GLfloat* WallSceneNode::getPlane() const
66 {
67     return plane;
68 }
69 
setNumLODs(int num,float * areas)70 void            WallSceneNode::setNumLODs(int num, float* areas)
71 {
72     numLODs = num;
73     elementAreas = areas;
74 }
75 
setPlane(const GLfloat _plane[4])76 void            WallSceneNode::setPlane(const GLfloat _plane[4])
77 {
78     // get normalization factor
79     const float n = 1.0f / sqrtf((_plane[0] * _plane[0]) +
80                                  (_plane[1] * _plane[1]) +
81                                  (_plane[2] * _plane[2]));
82 
83     // store normalized plane equation
84     plane[0] = n * _plane[0];
85     plane[1] = n * _plane[1];
86     plane[2] = n * _plane[2];
87     plane[3] = n * _plane[3];
88 }
89 
cull(const ViewFrustum & frustum) const90 bool            WallSceneNode::cull(const ViewFrustum& frustum) const
91 {
92     // cull if eye is behind (or on) plane
93     const GLfloat* eye = frustum.getEye();
94     const float eyedot = (eye[0] * plane[0]) +
95                          (eye[1] * plane[1]) +
96                          (eye[2] * plane[2]) + plane[3];
97     if (eyedot <= 0.0f)
98         return true;
99 
100     // if the Visibility culler tells us that we're
101     // fully visible, then skip the rest of these tests
102     if (octreeState == OctreeVisible)
103         return false;
104 
105     // get signed distance of wall center to each frustum side.
106     // if more than radius outside then cull
107     const int planeCount = frustum.getPlaneCount();
108     int i;
109     float d[6], d2[6];
110     const GLfloat* mySphere = getSphere();
111     bool inside = true;
112     for (i = 0; i < planeCount; i++)
113     {
114         const GLfloat* norm = frustum.getSide(i);
115         d[i] = (mySphere[0] * norm[0]) +
116                (mySphere[1] * norm[1]) +
117                (mySphere[2] * norm[2]) + norm[3];
118         if (d[i] < 0.0f)
119         {
120             d2[i] = d[i] * d[i];
121             if (d2[i] > mySphere[3])
122                 return true;
123             inside = false;
124         }
125     }
126 
127     // see if center of wall is inside each frustum side
128     if (inside)
129         return false;
130 
131     // most complicated test:  for sides sphere is behind, see if
132     // center is beyond radius times the sine of the angle between
133     // the normals, or equivalently:
134     //    distance^2 > radius^2 * (1 - cos^2)
135     // if so the wall is outside the view frustum
136     for (i = 0; i < planeCount; i++)
137     {
138         if (d[i] >= 0.0f)
139             continue;
140         const GLfloat* norm = frustum.getSide(i);
141         const GLfloat c = norm[0]*plane[0] + norm[1]*plane[1] + norm[2]*plane[2];
142         if (d2[i] > mySphere[3] * (1.0f - c*c))
143             return true;
144     }
145 
146     // probably visible
147     return false;
148 }
149 
pickLevelOfDetail(const SceneRenderer & renderer) const150 int         WallSceneNode::pickLevelOfDetail(
151     const SceneRenderer& renderer) const
152 {
153     if (!BZDBCache::tesselation)
154         return 0;
155 
156     int bestLOD = 0;
157 
158     const GLfloat* mySphere = getSphere();
159     const int numLights = renderer.getNumLights();
160     for (int i = 0; i < numLights; i++)
161     {
162         const GLfloat* pos = renderer.getLight(i).getPosition();
163 
164         // get signed distance from plane
165         GLfloat pd = pos[0] * plane[0] + pos[1] * plane[1] +
166                      pos[2] * plane[2] + plane[3];
167 
168         // ignore if behind wall
169         if (pd < 0.0f) continue;
170 
171         // get squared distance from center of wall
172         GLfloat ld = (pos[0] - mySphere[0]) * (pos[0] - mySphere[0]) +
173                      (pos[1] - mySphere[1]) * (pos[1] - mySphere[1]) +
174                      (pos[2] - mySphere[2]) * (pos[2] - mySphere[2]);
175 
176         // pick representative distance
177         GLfloat d = (ld > 1.5f * mySphere[3]) ? ld : pd * pd;
178 
179         // choose lod based on distance and element areas;
180         int j;
181         for (j = 0; j < numLODs - 1; j++)
182             if (elementAreas[j] < d)
183                 break;
184 
185         // use new lod if more detailed
186         if (j > bestLOD) bestLOD = j;
187     }
188 
189     // FIXME -- if transient texture warper is active then possibly
190     // bump up LOD if view point is close to wall.
191 
192     // limit lod to maximum allowed
193     if (bestLOD > BZDBCache::maxLOD) bestLOD = (int)BZDBCache::maxLOD;
194 
195     // return highest level required -- note that we don't care about
196     // the view point because, being flat, the wall would always
197     // choose the lowest LOD for any view.
198     return bestLOD;
199 }
200 
getDistance(const GLfloat * eye) const201 GLfloat         WallSceneNode::getDistance(const GLfloat* eye) const
202 {
203     const GLfloat d = plane[0] * eye[0] + plane[1] * eye[1] +
204                       plane[2] * eye[2] + plane[3];
205     return d * d;
206 }
207 
setColor(GLfloat r,GLfloat g,GLfloat b,GLfloat a)208 void            WallSceneNode::setColor(
209     GLfloat r, GLfloat g, GLfloat b, GLfloat a)
210 {
211     color[0] = r;
212     color[1] = g;
213     color[2] = b;
214     color[3] = a;
215 }
216 
setDynamicColor(const GLfloat * rgba)217 void            WallSceneNode::setDynamicColor(const GLfloat* rgba)
218 {
219     dynamicColor = rgba;
220     return;
221 }
222 
setBlending(bool blend)223 void            WallSceneNode::setBlending(bool blend)
224 {
225     wantBlending = blend;
226     return;
227 }
228 
setSphereMap(bool sphereMapping)229 void            WallSceneNode::setSphereMap(bool sphereMapping)
230 {
231     wantSphereMap = sphereMapping;
232     return;
233 }
234 
setColor(const GLfloat * rgba)235 void            WallSceneNode::setColor(const GLfloat* rgba)
236 {
237     color[0] = rgba[0];
238     color[1] = rgba[1];
239     color[2] = rgba[2];
240     color[3] = rgba[3];
241 }
242 
setModulateColor(GLfloat r,GLfloat g,GLfloat b,GLfloat a)243 void            WallSceneNode::setModulateColor(
244     GLfloat r, GLfloat g, GLfloat b, GLfloat a)
245 {
246     modulateColor[0] = r;
247     modulateColor[1] = g;
248     modulateColor[2] = b;
249     modulateColor[3] = a;
250 }
251 
setModulateColor(const GLfloat * rgba)252 void            WallSceneNode::setModulateColor(const GLfloat* rgba)
253 {
254     modulateColor[0] = rgba[0];
255     modulateColor[1] = rgba[1];
256     modulateColor[2] = rgba[2];
257     modulateColor[3] = rgba[3];
258 }
259 
setLightedColor(GLfloat r,GLfloat g,GLfloat b,GLfloat a)260 void            WallSceneNode::setLightedColor(
261     GLfloat r, GLfloat g, GLfloat b, GLfloat a)
262 {
263     lightedColor[0] = r;
264     lightedColor[1] = g;
265     lightedColor[2] = b;
266     lightedColor[3] = a;
267 }
268 
setLightedColor(const GLfloat * rgba)269 void            WallSceneNode::setLightedColor(const GLfloat* rgba)
270 {
271     lightedColor[0] = rgba[0];
272     lightedColor[1] = rgba[1];
273     lightedColor[2] = rgba[2];
274     lightedColor[3] = rgba[3];
275 }
276 
setLightedModulateColor(GLfloat r,GLfloat g,GLfloat b,GLfloat a)277 void            WallSceneNode::setLightedModulateColor(
278     GLfloat r, GLfloat g, GLfloat b, GLfloat a)
279 {
280     lightedModulateColor[0] = r;
281     lightedModulateColor[1] = g;
282     lightedModulateColor[2] = b;
283     lightedModulateColor[3] = a;
284 }
285 
setLightedModulateColor(const GLfloat * rgba)286 void            WallSceneNode::setLightedModulateColor(
287     const GLfloat* rgba)
288 {
289     lightedModulateColor[0] = rgba[0];
290     lightedModulateColor[1] = rgba[1];
291     lightedModulateColor[2] = rgba[2];
292     lightedModulateColor[3] = rgba[3];
293 }
294 
setAlphaThreshold(float thresh)295 void            WallSceneNode::setAlphaThreshold(float thresh)
296 {
297     alphaThreshold = thresh;
298 }
299 
setNoCulling(bool value)300 void            WallSceneNode::setNoCulling(bool value)
301 {
302     noCulling = value;
303 }
304 
setNoSorting(bool value)305 void            WallSceneNode::setNoSorting(bool value)
306 {
307     noSorting = value;
308 }
309 
setMaterial(const OpenGLMaterial & mat)310 void            WallSceneNode::setMaterial(const OpenGLMaterial& mat)
311 {
312     OpenGLGStateBuilder builder(gstate);
313     builder.setMaterial(mat);
314     gstate = builder.getState();
315 }
316 
setTexture(const int tex)317 void            WallSceneNode::setTexture(const int tex)
318 {
319     OpenGLGStateBuilder builder(gstate);
320     builder.setTexture(tex);
321     gstate = builder.getState();
322 }
323 
setTextureMatrix(const GLfloat * texmat)324 void            WallSceneNode::setTextureMatrix(const GLfloat* texmat)
325 {
326     OpenGLGStateBuilder builder(gstate);
327     builder.setTextureMatrix(texmat);
328     gstate = builder.getState();
329 }
330 
notifyStyleChange()331 void            WallSceneNode::notifyStyleChange()
332 {
333     float alpha;
334     bool lighted = (BZDBCache::lighting && gstate.isLighted());
335     OpenGLGStateBuilder builder(gstate);
336     style = 0;
337     if (lighted)
338     {
339         style += 1;
340         builder.setShading();
341     }
342     else
343         builder.setShading(GL_FLAT);
344     if (BZDBCache::texture && gstate.isTextured())
345     {
346         style += 2;
347         builder.enableTexture(true);
348         builder.enableTextureMatrix(true);
349         alpha = lighted ? lightedModulateColor[3] : modulateColor[3];
350     }
351     else
352     {
353         builder.enableTexture(false);
354         builder.enableTextureMatrix(false);
355         alpha = lighted ? lightedColor[3] : color[3];
356     }
357     if (BZDB.isTrue("texturereplace"))
358         builder.setTextureEnvMode(GL_REPLACE);
359     else
360         builder.setTextureEnvMode(GL_MODULATE);
361     builder.enableMaterial(lighted);
362     if (BZDBCache::blend && (wantBlending || (alpha != 1.0f)))
363     {
364         builder.setBlending(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
365         builder.setStipple(1.0f);
366     }
367     else
368     {
369         builder.resetBlending();
370         builder.setStipple(alpha);
371     }
372     isBlended = wantBlending || (alpha != 1.0f);
373     if (alphaThreshold != 0.0f)
374         builder.setAlphaFunc(GL_GEQUAL, alphaThreshold);
375     if (noCulling)
376         builder.disableCulling();
377     if (noSorting)
378         builder.setNeedsSorting(false);
379     if (wantSphereMap)
380         builder.enableSphereMap(true);
381     gstate = builder.getState();
382 }
383 
copyStyle(WallSceneNode * node)384 void            WallSceneNode::copyStyle(WallSceneNode* node)
385 {
386     gstate = node->gstate;
387     useColorTexture = node->useColorTexture;
388     dynamicColor = node->dynamicColor;
389     setColor(node->color);
390     setModulateColor(node->modulateColor);
391     setLightedColor(node->lightedColor);
392     setLightedModulateColor(node->lightedModulateColor);
393     isBlended = node->isBlended;
394     wantBlending = node->wantBlending;
395     wantSphereMap = node->wantSphereMap;
396 }
397 
setColor()398 void            WallSceneNode::setColor()
399 {
400     if (BZDBCache::texture && useColorTexture)
401         myColor4f(1,1,1,1);
402     else if (dynamicColor != NULL)
403         myColor4fv(dynamicColor);
404     else
405     {
406         switch (style)
407         {
408         case 0:
409             myColor4fv(color);
410             break;
411         case 1:
412             myColor4fv(lightedColor);
413             break;
414         case 2:
415             myColor4fv(modulateColor);
416             break;
417         case 3:
418             myColor4fv(lightedModulateColor);
419             break;
420         }
421     }
422 }
423 
splitWall(const GLfloat * splitPlane,const GLfloat3Array & vertices,const GLfloat2Array & texcoords,SceneNode * & front,SceneNode * & back)424 int WallSceneNode::splitWall(const GLfloat* splitPlane,
425                              const GLfloat3Array& vertices,
426                              const GLfloat2Array& texcoords,
427                              SceneNode*& front, SceneNode*& back) // const
428 {
429     int i;
430     const int count = vertices.getSize();
431     const float fudgeFactor = 0.001f;
432     const unsigned char BACK_SIDE = (1 << 0);
433     const unsigned char FRONT_SIDE = (1 << 1);
434 
435     // arrays for tracking each vertex's side
436     // and distance from the splitting plane
437     // (assuming stack allocation with be faster then heap, might be wrong)
438     // wonder how that compares to static vs. stack access speeds
439     const int staticSize = 64;
440     float* dists;
441     unsigned char* array;
442     float staticDists[staticSize];
443     unsigned char staticArray[staticSize];
444     if (count > staticSize)
445     {
446         array = new unsigned char[count];
447         dists = new float[count];
448     }
449     else
450     {
451         array = staticArray;
452         dists = staticDists;
453     }
454 
455     // determine on which side of the plane each point lies
456     int bothCount = 0;
457     int backCount = 0;
458     int frontCount = 0;
459     for (i = 0; i < count; i++)
460     {
461         const GLfloat d = (vertices[i][0] * splitPlane[0]) +
462                           (vertices[i][1] * splitPlane[1]) +
463                           (vertices[i][2] * splitPlane[2]) + splitPlane[3];
464         if (d < -fudgeFactor)
465         {
466             array[i] = BACK_SIDE;
467             backCount++;
468         }
469         else if (d > fudgeFactor)
470         {
471             array[i] = FRONT_SIDE;
472             frontCount++;
473         }
474         else
475         {
476             array[i] = (BACK_SIDE | FRONT_SIDE);
477             bothCount++;
478             backCount++;
479             frontCount++;
480         }
481         dists[i] = d; // save for later
482     }
483 
484     // see if we need to split
485     if ((frontCount == 0) || (frontCount == bothCount))
486     {
487         if (count > staticSize)
488         {
489             delete[] array;
490             delete[] dists;
491         }
492         return -1; // node is on the back side
493     }
494 
495     if ((backCount == 0) || (backCount == bothCount))
496     {
497         if (count > staticSize)
498         {
499             delete[] array;
500             delete[] dists;
501         }
502         return +1; // node is on the front side
503     }
504 
505     // get the first old front and back points
506     int firstFront = -1, firstBack = -1;
507 
508     for (i = 0; i < count; i++)
509     {
510 
511         const int next = (i + 1) % count; // the next index
512 
513         if (array[next] & FRONT_SIDE)
514         {
515             if (!(array[i] & FRONT_SIDE))
516                 firstFront = next;
517         }
518         if (array[next] & BACK_SIDE)
519         {
520             if (!(array[i] & BACK_SIDE))
521                 firstBack = next;
522         }
523     }
524 
525     // get the last old front and back points
526     int lastFront = (firstFront + frontCount - 1) % count;
527     int lastBack = (firstBack + backCount - 1) % count;
528 
529     // add in extra counts for the splitting vertices
530     if (firstFront != lastBack)
531     {
532         frontCount++;
533         backCount++;
534     }
535     if (firstBack != lastFront)
536     {
537         frontCount++;
538         backCount++;
539     }
540 
541     // make space for new polygons
542     GLfloat3Array vertexFront(frontCount);
543     GLfloat2Array uvFront(frontCount);
544     GLfloat3Array vertexBack(backCount);
545     GLfloat2Array uvBack(backCount);
546 
547     // fill in the splitting vertices
548     int frontIndex = 0;
549     int backIndex = 0;
550     if (firstFront != lastBack)
551     {
552         GLfloat splitVertex[3], splitUV[2];
553         splitEdge(dists[firstFront], dists[lastBack],
554                   vertices[firstFront], vertices[lastBack],
555                   texcoords[firstFront], texcoords[lastBack],
556                   splitVertex, splitUV);
557         memcpy(vertexFront[0], splitVertex, sizeof(GLfloat[3]));
558         memcpy(uvFront[0], splitUV, sizeof(GLfloat[2]));
559         frontIndex++; // bump up the head
560         const int last = backCount - 1;
561         memcpy(vertexBack[last], splitVertex, sizeof(GLfloat[3]));
562         memcpy(uvBack[last], splitUV, sizeof(GLfloat[2]));
563     }
564     if (firstBack != lastFront)
565     {
566         GLfloat splitVertex[3], splitUV[2];
567         splitEdge(dists[firstBack], dists[lastFront],
568                   vertices[firstBack], vertices[lastFront],
569                   texcoords[firstBack], texcoords[lastFront],
570                   splitVertex, splitUV);
571         memcpy(vertexBack[0], splitVertex, sizeof(GLfloat[3]));
572         memcpy(uvBack[0], splitUV, sizeof(GLfloat[2]));
573         backIndex++; // bump up the head
574         const int last = frontCount - 1;
575         memcpy(vertexFront[last], splitVertex, sizeof(GLfloat[3]));
576         memcpy(uvFront[last], splitUV, sizeof(GLfloat[2]));
577     }
578 
579     // fill in the old front side vertices
580     const int endFront = (lastFront + 1) % count;
581     for (i = firstFront; i != endFront; i = (i + 1) % count)
582     {
583         memcpy(vertexFront[frontIndex], vertices[i], sizeof(GLfloat[3]));
584         memcpy(uvFront[frontIndex], texcoords[i], sizeof(GLfloat[2]));
585         frontIndex++;
586     }
587 
588     // fill in the old back side vertices
589     const int endBack = (lastBack + 1) % count;
590     for (i = firstBack; i != endBack; i = (i + 1) % count)
591     {
592         memcpy(vertexBack[backIndex], vertices[i], sizeof(GLfloat[3]));
593         memcpy(uvBack[backIndex], texcoords[i], sizeof(GLfloat[2]));
594         backIndex++;
595     }
596 
597     // make new nodes
598     front = new PolyWallSceneNode(vertexFront, uvFront);
599     back = new PolyWallSceneNode(vertexBack, uvBack);
600 
601     // free the arrays, if required
602     if (count > staticSize)
603     {
604         delete[] array;
605         delete[] dists;
606     }
607 
608     return 0; // generated new front and back nodes
609 }
610 
611 
splitEdge(float d1,float d2,const GLfloat * p1,const GLfloat * p2,const GLfloat * uv1,const GLfloat * uv2,GLfloat * p,GLfloat * uv)612 void WallSceneNode::splitEdge(float d1, float d2,
613                               const GLfloat* p1, const GLfloat* p2,
614                               const GLfloat* uv1, const GLfloat* uv2,
615                               GLfloat* p, GLfloat* uv) // const
616 {
617     // compute fraction along edge where split occurs
618     float t1 = (d2 - d1);
619     if (t1 != 0.0f)   // shouldn't happen
620         t1 = -(d1 / t1);
621 
622     // compute vertex
623     p[0] = p1[0] + (t1 * (p2[0] - p1[0]));
624     p[1] = p1[1] + (t1 * (p2[1] - p1[1]));
625     p[2] = p1[2] + (t1 * (p2[2] - p1[2]));
626 
627     // compute texture coordinate
628     uv[0] = uv1[0] + (t1 * (uv2[0] - uv1[0]));
629     uv[1] = uv1[1] + (t1 * (uv2[1] - uv1[1]));
630 }
631 
632 
inAxisBox(const Extents & UNUSED (exts)) const633 bool WallSceneNode::inAxisBox (const Extents& UNUSED(exts)) const
634 {
635     // this should never happen, only the TriWallSceneNode
636     // and QuadWallSceneNode version of this function will
637     // be called
638     printf ("WallSceneNode::inAxisBox() was called!\n");
639     exit (1);
640     return false;
641 }
642 
643 // Local Variables: ***
644 // mode: C++ ***
645 // tab-width: 4 ***
646 // c-basic-offset: 4 ***
647 // indent-tabs-mode: nil ***
648 // End: ***
649 // ex: shiftwidth=4 tabstop=4
650