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