1 // obj.cxx -- routines to handle loading scenery and building the plib
2 //            scene graph.
3 //
4 // Written by Curtis Olson, started October 1997.
5 //
6 // Copyright (C) 1997  Curtis L. Olson  - http://www.flightgear.org/~curt
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 //
22 // $Id$
23 
24 
25 #ifdef HAVE_CONFIG_H
26 #  include <simgear_config.h>
27 #endif
28 
29 #include <osg/LOD>
30 #include <osgUtil/Simplifier>
31 
32 #include <boost/foreach.hpp>
33 
34 #include <simgear/scene/material/matmodel.hxx>
35 #include <simgear/scene/model/SGOffsetTransform.hxx>
36 #include <simgear/scene/util/QuadTreeBuilder.hxx>
37 #include <simgear/scene/util/SGReaderWriterOptions.hxx>
38 #include <simgear/scene/util/OptionsReadFileCallback.hxx>
39 #include <simgear/scene/util/SGNodeMasks.hxx>
40 #include <simgear/debug/ErrorReportingCallback.hxx>
41 
42 #include "SGNodeTriangles.hxx"
43 #include "GroundLightManager.hxx"
44 #include "SGLightBin.hxx"
45 #include "SGDirectionalLightBin.hxx"
46 #include "SGModelBin.hxx"
47 #include "SGBuildingBin.hxx"
48 #include "TreeBin.hxx"
49 
50 #include "pt_lights.hxx"
51 
52 
53 typedef std::list<SGLightBin> SGLightListBin;
54 typedef std::list<SGDirectionalLightBin> SGDirectionalLightListBin;
55 
56 #define SG_SIMPLIFIER_RATIO         (0.001)
57 #define SG_SIMPLIFIER_MAX_LENGTH    (1000.0)
58 #define SG_SIMPLIFIER_MAX_ERROR     (2000.0)
59 
60 using namespace simgear;
61 
62 using ReadResult = osgDB::ReaderWriter::ReadResult;
63 
64 // QuadTreeBuilder is used by Random Objects Generator
65 typedef std::pair<osg::Node*, int> ModelLOD;
66 struct MakeQuadLeaf {
operator ()MakeQuadLeaf67     osg::LOD* operator() () const { return new osg::LOD; }
68 };
69 struct AddModelLOD {
operator ()AddModelLOD70     void operator() (osg::LOD* leaf, ModelLOD& mlod) const
71     {
72         leaf->addChild(mlod.first, 0, mlod.second);
73     }
74 };
75 struct GetModelLODCoord {
GetModelLODCoordGetModelLODCoord76     GetModelLODCoord() {}
GetModelLODCoordGetModelLODCoord77     GetModelLODCoord(const GetModelLODCoord& rhs)
78     {}
operator ()GetModelLODCoord79     osg::Vec3 operator() (const ModelLOD& mlod) const
80     {
81         return mlod.first->getBound().center();
82     }
83 };
84 typedef QuadTreeBuilder<osg::LOD*, ModelLOD, MakeQuadLeaf, AddModelLOD,
85                         GetModelLODCoord>  RandomObjectsQuadtree;
86 
87 
88 // needs constructor
89 static unsigned int num_tdcb = 0;
90 class SGTileDetailsCallback : public OptionsReadFileCallback {
91 public:
SGTileDetailsCallback()92     SGTileDetailsCallback()
93     {
94         num_tdcb++;
95     }
96 
~SGTileDetailsCallback()97     virtual ~SGTileDetailsCallback()
98     {
99         num_tdcb--;
100         SG_LOG( SG_TERRAIN, SG_DEBUG, "SGTileDetailsCallback::~SGTileDetailsCallback() num cbs left " << num_tdcb  );
101     }
102 
readNode(const std::string &,const osgDB::Options *)103     ReadResult readNode(const std::string&, const osgDB::Options*) override
104     {
105         osg::ref_ptr<osg::Group> group;
106         simgear::ErrorReportContext ec{"btg", _path};
107 
108         try {
109             group = new osg::Group;
110             SGMaterialLibPtr matlib;
111             osg::ref_ptr<SGMaterialCache> matcache;
112             group->setDataVariance(osg::Object::STATIC);
113 
114             // generate textured triangle list
115             std::vector<SGTriangleInfo> matTris;
116             GetNodeTriangles nodeTris(_gbs_center, &matTris);
117             _rootNode->accept( nodeTris );
118 
119             // build matcache
120             matlib = _options->getMaterialLib();
121             if (matlib) {
122                 SGGeod geodPos = SGGeod::fromCart(_gbs_center);
123                 matcache = matlib->generateMatCache(geodPos);
124             }
125 
126     #if 0
127             // TEST : See if we can regenerate landclass shapes from node
128             for ( unsigned int i=0; i<matTris.size(); i++ ) {
129                 matTris[i].dumpBorder(_gbs_center);
130             }
131     #endif
132 
133             osg::Node* node = loadTerrain();
134             if (node) {
135                 group->addChild(node);
136             }
137 
138             osg::LOD* lightLOD = generateLightingTileObjects(matTris, matcache);
139             if (lightLOD) {
140                 group->addChild(lightLOD);
141             }
142 
143             osg::LOD* objectLOD = generateRandomTileObjects(matTris, matcache);
144             if (objectLOD) {
145                 group->addChild(objectLOD);
146             }
147         } catch (sg_exception& sge) {
148             simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::BTGLoad,
149                                 "Failed to load BTG file:" + sge.getFormattedMessage(),
150                                 sge.getLocation());
151             return ReadResult::ERROR_IN_READING_FILE;
152         } catch (std::bad_alloc &e) {
153             simgear::reportFailure(simgear::LoadFailure::OutOfMemory, simgear::ErrorCode::BTGLoad,
154                                 "Out of memory loading tile details", sg_location{_path});
155             return ReadResult::INSUFFICIENT_MEMORY_TO_LOAD;
156         }
157 
158         return group.release();
159     }
160 
getMaterialLightColor(const SGMaterial * material)161     static SGVec4f getMaterialLightColor(const SGMaterial* material)
162     {
163         if (!material) {
164             return SGVec4f(1, 1, 1, 0.8);
165         }
166 
167         return material->get_light_color();
168     }
169 
170     static void
addPointGeometry(SGLightBin & lights,const std::vector<SGVec3d> & vertices,const SGVec4f & color,const int_list & pts_v)171     addPointGeometry(SGLightBin& lights,
172                      const std::vector<SGVec3d>& vertices,
173                      const SGVec4f& color,
174                      const int_list& pts_v)
175     {
176         for (unsigned i = 0; i < pts_v.size(); ++i)
177             lights.insert(toVec3f(vertices[pts_v[i]]), color);
178     }
179 
180     static void
addPointGeometry(SGDirectionalLightBin & lights,const std::vector<SGVec3d> & vertices,const std::vector<SGVec3f> & normals,const SGVec4f & color,const int_list & pts_v,const int_list & pts_n)181     addPointGeometry(SGDirectionalLightBin& lights,
182                      const std::vector<SGVec3d>& vertices,
183                      const std::vector<SGVec3f>& normals,
184                      const SGVec4f& color,
185                      const int_list& pts_v,
186                      const int_list& pts_n)
187     {
188         // If the normal indices match the vertex indices, use seperate
189         // normal indices. Else reuse the vertex indices for the normals.
190         if (pts_v.size() == pts_n.size()) {
191             for (unsigned i = 0; i < pts_v.size(); ++i)
192                 lights.insert(toVec3f(vertices[pts_v[i]]), normals[pts_n[i]], color);
193         } else {
194             for (unsigned i = 0; i < pts_v.size(); ++i)
195                 lights.insert(toVec3f(vertices[pts_v[i]]), normals[pts_v[i]], color);
196         }
197     }
198 
insertPtGeometry(const SGBinObject & obj,SGMaterialCache * matcache)199     bool insertPtGeometry(const SGBinObject& obj, SGMaterialCache* matcache)
200     {
201         if (obj.get_pts_v().size() != obj.get_pts_n().size()) {
202             SG_LOG(SG_TERRAIN, SG_ALERT,
203                    "Group list sizes for points do not match!");
204             return false;
205         }
206 
207         for (unsigned grp = 0; grp < obj.get_pts_v().size(); ++grp) {
208             std::string materialName = obj.get_pt_materials()[grp];
209             SGMaterial* material = matcache->find(materialName);
210             SGVec4f color = getMaterialLightColor(material);
211 
212             if (3 <= materialName.size() && materialName.substr(0, 3) != "RWY") {
213                 // Just plain lights. Not something for the runway.
214                 addPointGeometry(tileLights, obj.get_wgs84_nodes(), color,
215                                  obj.get_pts_v()[grp]);
216             } else if (materialName == "RWY_BLUE_TAXIWAY_LIGHTS"
217                 || materialName == "RWY_GREEN_TAXIWAY_LIGHTS") {
218                 addPointGeometry(taxiLights, obj.get_wgs84_nodes(), obj.get_normals(),
219                                  color, obj.get_pts_v()[grp], obj.get_pts_n()[grp]);
220                 } else if (materialName == "RWY_VASI_LIGHTS") {
221                     vasiLights.push_back(SGDirectionalLightBin());
222                     addPointGeometry(vasiLights.back(), obj.get_wgs84_nodes(),
223                                      obj.get_normals(), color, obj.get_pts_v()[grp],
224                                      obj.get_pts_n()[grp]);
225                 } else if (materialName == "RWY_SEQUENCED_LIGHTS") {
226                     rabitLights.push_back(SGDirectionalLightBin());
227                     addPointGeometry(rabitLights.back(), obj.get_wgs84_nodes(),
228                                      obj.get_normals(), color, obj.get_pts_v()[grp],
229                                      obj.get_pts_n()[grp]);
230                 } else if (materialName == "RWY_ODALS_LIGHTS") {
231                     odalLights.push_back(SGLightBin());
232                     addPointGeometry(odalLights.back(), obj.get_wgs84_nodes(),
233                                      color, obj.get_pts_v()[grp]);
234                 } else if (materialName == "RWY_YELLOW_PULSE_LIGHTS") {
235                     holdshortLights.push_back(SGDirectionalLightBin());
236                     addPointGeometry(holdshortLights.back(), obj.get_wgs84_nodes(),
237                                      obj.get_normals(), color, obj.get_pts_v()[grp],
238                                      obj.get_pts_n()[grp]);
239                 } else if (materialName == "RWY_GUARD_LIGHTS") {
240                     guardLights.push_back(SGDirectionalLightBin());
241                     addPointGeometry(guardLights.back(), obj.get_wgs84_nodes(),
242                                      obj.get_normals(), color, obj.get_pts_v()[grp],
243                                      obj.get_pts_n()[grp]);
244                 } else if (materialName == "RWY_REIL_LIGHTS") {
245                     reilLights.push_back(SGDirectionalLightBin());
246                     addPointGeometry(reilLights.back(), obj.get_wgs84_nodes(),
247                                      obj.get_normals(), color, obj.get_pts_v()[grp],
248                                      obj.get_pts_n()[grp]);
249                 } else {
250                     // what is left must be runway lights
251                     addPointGeometry(runwayLights, obj.get_wgs84_nodes(),
252                                      obj.get_normals(), color, obj.get_pts_v()[grp],
253                                      obj.get_pts_n()[grp]);
254                 }
255         }
256 
257         return true;
258     }
259 
260 
261 
262     // Load terrain if required
263     // todo - this is the same code as when we load a btg from the .STG - can we combine?
loadTerrain()264     osg::Node* loadTerrain()
265     {
266       if (! _loadterrain)
267         return NULL;
268 
269       SGBinObject tile;
270       if (!tile.read_bin(_path))
271         return NULL;
272 
273       SGMaterialLibPtr matlib;
274       SGMaterialCache* matcache = 0;
275       bool useVBOs = false;
276       bool simplifyNear    = false;
277       double ratio       = SG_SIMPLIFIER_RATIO;
278       double maxLength   = SG_SIMPLIFIER_MAX_LENGTH;
279       double maxError    = SG_SIMPLIFIER_MAX_ERROR;
280 
281       if (_options) {
282         matlib = _options->getMaterialLib();
283         useVBOs = (_options->getPluginStringData("SimGear::USE_VBOS") == "ON");
284         SGPropertyNode* propertyNode = _options->getPropertyNode().get();
285         simplifyNear = propertyNode->getBoolValue("/sim/rendering/terrain/simplifier/enabled-near", simplifyNear);
286         ratio = propertyNode->getDoubleValue("/sim/rendering/terrain/simplifier/ratio", ratio);
287         maxLength = propertyNode->getDoubleValue("/sim/rendering/terrain/simplifier/max-length", maxLength);
288         maxError = propertyNode->getDoubleValue("/sim/rendering/terrain/simplifier/max-error", maxError);
289       }
290 
291       // PSADRO TODO : we can do this in terragear
292       // - why not add a bitmask of flags to the btg so we can precompute this?
293       // and only do it if it hasn't been done already
294       SGVec3d center = tile.get_gbs_center();
295       SGGeod geodPos = SGGeod::fromCart(center);
296       SGQuatd hlOr = SGQuatd::fromLonLat(geodPos)*SGQuatd::fromEulerDeg(0, 0, 180);
297 
298       // Generate a materials cache
299       if (matlib) {
300           matcache = matlib->generateMatCache(geodPos);
301       }
302 
303       // rotate the tiles so that the bounding boxes get nearly axis aligned.
304       // this will help the collision tree's bounding boxes a bit ...
305       std::vector<SGVec3d> nodes = tile.get_wgs84_nodes();
306       for (unsigned i = 0; i < nodes.size(); ++i) {
307         nodes[i] = hlOr.transform(nodes[i]);
308       }
309       tile.set_wgs84_nodes(nodes);
310 
311       SGQuatf hlOrf(hlOr[0], hlOr[1], hlOr[2], hlOr[3]);
312       std::vector<SGVec3f> normals = tile.get_normals();
313       for (unsigned i = 0; i < normals.size(); ++i) {
314         normals[i] = hlOrf.transform(normals[i]);
315       }
316       tile.set_normals(normals);
317 
318       osg::ref_ptr<SGTileGeometryBin> tileGeometryBin = new SGTileGeometryBin;
319 
320       if (!tileGeometryBin->insertSurfaceGeometry(tile, matcache)) {
321         return NULL;
322       }
323 
324       osg::Node* node = tileGeometryBin->getSurfaceGeometry(matcache, useVBOs);
325       if (node && simplifyNear) {
326         osgUtil::Simplifier simplifier(ratio, maxError, maxLength);
327         node->accept(simplifier);
328       }
329 
330       return node;
331     }
332 
min_dist_to_seg_squared(const SGVec3f p,const SGVec3d & a,const SGVec3d & b)333     float min_dist_to_seg_squared( const SGVec3f p, const SGVec3d& a, const SGVec3d& b )
334     {
335         const float l2 = distSqr(a, b);
336         SGVec3d pd = toVec3d( p );
337         if (l2 == 0.0) {
338             return distSqr(pd, a); // if a == b, just return distance to A
339         }
340 
341         // Consider the line extending the segment, parameterized as a + t (b - a).
342         // We find projection of pt onto the line.
343         // It falls where t = [(p-a) . (b-a)] / |b-a|^2
344         const float t = dot(pd-a, b-a) / l2;
345 
346         if (t < 0.0) {
347             return distSqr(pd, a);
348         } else if (t > 1.0) {
349             return distSqr(pd, b);
350         } else {
351             const SGVec3d proj = a + t * (b-a);
352             return distSqr(pd, proj);
353         }
354     }
355 
min_dist_from_borders(SGVec3f p,const std::vector<SGBorderContour> & bsegs)356     float min_dist_from_borders( SGVec3f p, const std::vector<SGBorderContour>& bsegs )
357     {
358         // calc min dist to each line
359         // calc distance squared to keep this as fast as we can
360         // first, we must be able to project the point onto the segment
361         std::vector<float> distances;
362         for ( unsigned int b=0; b<bsegs.size(); b++ )
363         {
364             distances.push_back( min_dist_to_seg_squared( p, bsegs[b].start, bsegs[b].end ) );
365         }
366 
367         float min_dist_sq = *std::min_element( distances.begin(), distances.end() );
368         return sqrt( min_dist_sq );
369     }
370 
371     // let's break random objects from randomBuildings
computeRandomObjectsAndBuildings(std::vector<SGTriangleInfo> & matTris,float building_density,bool use_random_objects,bool use_random_buildings,bool useVBOs,SGMatModelBin & randomModels,SGBuildingBinList & randomBuildings)372     void computeRandomObjectsAndBuildings(
373         std::vector<SGTriangleInfo>& matTris,
374         float building_density,
375         bool use_random_objects,
376         bool use_random_buildings,
377         bool useVBOs,
378         SGMatModelBin&     randomModels,
379         SGBuildingBinList& randomBuildings )
380     {
381         unsigned int m;
382 
383         // Only compute the random objects if we haven't already done so
384         if (_tileRandomObjectsComputed) {
385             return;
386         }
387         _tileRandomObjectsComputed = true;
388 
389         // generate a repeatable random seed
390         mt seed;
391         mt_init(&seed, unsigned(123));
392 
393         for ( m=0; m<matTris.size(); m++ ) {
394             SGMaterial *mat = matTris[m].getMaterial();
395             if (!mat)
396                 continue;
397 
398             osg::Texture2D* object_mask  = mat->get_one_object_mask(matTris[m].getTextureIndex());
399 
400             int   group_count            = mat->get_object_group_count();
401             float building_coverage      = mat->get_building_coverage();
402             float cos_zero_density_angle = mat->get_cos_object_zero_density_slope_angle();
403             float cos_max_density_angle  = mat->get_cos_object_max_density_slope_angle();
404 
405             if ((building_coverage == 0) && (group_count ==0))
406                 continue;
407 
408             SGBuildingBin* bin = NULL;
409 
410             if (building_coverage > 0) {
411                 bin = new SGBuildingBin(mat, useVBOs);
412                 randomBuildings.push_back(bin);
413             }
414 
415             unsigned num = matTris[m].getNumTriangles();
416             int random_dropped = 0;
417             int mask_dropped = 0;
418             int building_dropped = 0;
419             int triangle_dropped = 0;
420 
421             // get the polygon border segments
422 //            std::vector<SGBorderContour> borderSegs;
423 //            matTris[m].getBorderContours( borderSegs );
424 
425             for (unsigned i = 0; i < num; ++i) {
426                 std::vector<SGVec3f> triVerts;
427                 std::vector<SGVec2f> triTCs;
428                 matTris[m].getTriangle(i, triVerts, triTCs);
429 
430                 SGVec3f vorigin = triVerts[0];
431                 SGVec3f v0 = triVerts[1] - vorigin;
432                 SGVec3f v1 = triVerts[2] - vorigin;
433                 SGVec2f torigin = triTCs[0];
434                 SGVec2f t0 = triTCs[1] - torigin;
435                 SGVec2f t1 = triTCs[2] - torigin;
436                 SGVec3f normal = cross(v0, v1);
437 
438                 // Ensure the slope isn't too steep by checking the
439                 // cos of the angle between the slope normal and the
440                 // vertical (conveniently the z-component of the normalized
441                 // normal) and values passed in.
442                 float cos = normalize(normal).z();
443                 float slope_density = 1.0;
444                 if (cos < cos_zero_density_angle) continue; // Too steep for any objects
445                 if (cos < cos_max_density_angle) {
446                     slope_density =
447                     (cos - cos_zero_density_angle) /
448                     (cos_max_density_angle - cos_zero_density_angle);
449                 }
450 
451                 // Containers to hold the random buildings and objects generated
452                 // for this triangle for collision detection purposes.
453                 std::vector< std::pair< SGVec3f, float> > triangleObjectsList;
454                 std::vector< std::pair< SGVec3f, float> > triangleBuildingList;
455 
456                 // Compute the area : todo - we only want to stop if the area of the POLY
457                 // is too small
458                 // so we need to know area of each poly....
459                 float area = 0.5f*length(normal);
460                 if (area <= SGLimitsf::min())
461                     continue;
462 
463                 // Generate any random objects
464                 if (use_random_objects && (group_count > 0))
465                 {
466                     for (int j = 0; j < group_count; j++)
467                     {
468                         SGMatModelGroup *object_group =  mat->get_object_group(j);
469                         int nObjects = object_group->get_object_count();
470 
471                         if (nObjects == 0) continue;
472 
473                         // For each of the random models in the group, determine an appropriate
474                         // number of random placements and insert them.
475                         for (int k = 0; k < nObjects; k++) {
476                             SGMatModel * object = object_group->get_object(k);
477 
478                             // Determine the number of objecst to place, taking into account
479                             // the slope density factor.
480                             double n = slope_density * area / object->get_coverage_m2();
481 
482                             // Use the zombie door method to determine fractional object placement.
483                             n = n + mt_rand(&seed);
484 
485                             // place an object each unit of area
486                             while ( n > 1.0 ) {
487                                 n -= 1.0;
488 
489                                 float a = mt_rand(&seed);
490                                 float b = mt_rand(&seed);
491                                 if ( a + b > 1 ) {
492                                     a = 1 - a;
493                                     b = 1 - b;
494                                 }
495 
496                                 SGVec3f randomPoint = vorigin + a*v0 + b*v1;
497                                 float rotation = static_cast<float>(mt_rand(&seed));
498 
499                                 // Check that the point is sufficiently far from
500                                 // the edge of the triangle by measuring the distance
501                                 // from the three lines that make up the triangle.
502                                 float spacing = object->get_spacing_m();
503 
504                                 SGVec3f p = randomPoint - vorigin;
505 #if 1
506                                 float edges[] = {
507                                     length(cross(p     , p - v0)) / length(v0),
508                                     length(cross(p - v0, p - v1)) / length(v1 - v0),
509                                     length(cross(p - v1, p     )) / length(v1)      };
510                                     float edge_dist = *std::min_element(edges, edges + 3);
511 #else
512                                     float edge_dist = min_dist_from_borders( randomPoint, borderSegs );
513 #endif
514                                     if (edge_dist < spacing) {
515                                         continue;
516                                     }
517 
518                                     if (object_mask != NULL) {
519                                         SGVec2f texCoord = torigin + a*t0 + b*t1;
520 
521                                         // Check this random point against the object mask
522                                         // blue (for buildings) channel.
523                                         osg::Image* img = object_mask->getImage();
524                                         unsigned int x = (int) (img->s() * texCoord.x()) % img->s();
525                                         unsigned int y = (int) (img->t() * texCoord.y()) % img->t();
526 
527                                         if (mt_rand(&seed) > img->getColor(x, y).b()) {
528                                             // Failed object mask check
529                                             continue;
530                                         }
531 
532                                         rotation = img->getColor(x,y).r();
533                                     }
534 
535                                     bool close = false;
536 
537                                     // Check it isn't too close to any other random objects in the triangle
538                                     std::vector<std::pair<SGVec3f, float> >::iterator l;
539                                     for (l = triangleObjectsList.begin(); l != triangleObjectsList.end(); ++l) {
540                                         float min_dist2 = (l->second + object->get_spacing_m()) *
541                                         (l->second + object->get_spacing_m());
542 
543                                         if (distSqr(l->first, randomPoint) < min_dist2) {
544                                             close = true;
545                                             continue;
546                                         }
547                                     }
548 
549                                     if (!close) {
550                                         triangleObjectsList.push_back(std::make_pair(randomPoint, object->get_spacing_m()));
551                                         randomModels.insert(randomPoint,
552                                                             object,
553                                                             (int)object->get_randomized_range_m(&seed),
554                                                             rotation);
555                                     }
556                             }
557                         }
558                     }
559                 }
560 
561                 // Random objects now generated.  Now generate the random buildings (if any);
562                 if (use_random_buildings && (building_coverage > 0) && (building_density > 0)) {
563 
564                     // Calculate the number of buildings, taking into account building density (which is linear)
565                     // and the slope density factor.
566                     double num = building_density * building_density * slope_density * area / building_coverage;
567 
568                     // For partial units of area, use a zombie door method to
569                     // create the proper random chance of an object being created
570                     // for this triangle.
571                     num = num + mt_rand(&seed);
572 
573                     if (num < 1.0f) {
574                         continue;
575                     }
576 
577                     // Cosine of the angle between the two vectors.
578                     float cosine = (dot(v0, v1) / (length(v0) * length(v1)));
579 
580                     // Determine a grid spacing in each vector such that the correct
581                     // coverage will result.
582                     float stepv0 = (sqrtf(building_coverage) / building_density) / length(v0) / sqrtf(1 - cosine * cosine);
583                     float stepv1 = (sqrtf(building_coverage) / building_density) / length(v1);
584 
585                     stepv0 = std::min(stepv0, 1.0f);
586                     stepv1 = std::min(stepv1, 1.0f);
587 
588                     // Start at a random point. a will be immediately incremented below.
589                     float a = -mt_rand(&seed) * stepv0;
590                     float b = mt_rand(&seed) * stepv1;
591 
592                     // Place an object each unit of area
593                     while (num > 1.0) {
594                         num -= 1.0;
595 
596                         // Set the next location to place a building
597                         a += stepv0;
598 
599                         if ((a + b) > 1.0f) {
600                             // Reached the end of the scan-line on v0. Reset and increment
601                             // scan-line on v1
602                             a = mt_rand(&seed) * stepv0;
603                             b += stepv1;
604                         }
605 
606                         if (b > 1.0f) {
607                             // In a degenerate case of a single point, we might be outside the
608                             // scanline.  Note that we need to still ensure that a+b < 1.
609                             b = mt_rand(&seed) * stepv1 * (1.0f - a);
610                         }
611 
612                         if ((a + b) > 1.0f ) {
613                             // Truly degenerate case - simply choose a random point guaranteed
614                             // to fulfil the constraing of a+b < 1.
615                             a = mt_rand(&seed);
616                             b = mt_rand(&seed) * (1.0f - a);
617                         }
618 
619                         SGVec3f randomPoint = vorigin + a*v0 + b*v1;
620                         float rotation = mt_rand(&seed);
621 
622                         if (object_mask != NULL) {
623                             SGVec2f texCoord = torigin + a*t0 + b*t1;
624                             osg::Image* img = object_mask->getImage();
625                             int x = (int) (img->s() * texCoord.x()) % img->s();
626                             int y = (int) (img->t() * texCoord.y()) % img->t();
627 
628                             // In some degenerate cases x or y can be < 1, in which case the mod operand fails
629                             while (x < 0) x += img->s();
630                             while (y < 0) y += img->t();
631 
632                             if (mt_rand(&seed) < img->getColor(x, y).b()) {
633                                 // Object passes mask. Rotation is taken from the red channel
634                                 rotation = img->getColor(x,y).r();
635                             } else {
636                                 // Fails mask test - try again.
637                                 mask_dropped++;
638                                 continue;
639                             }
640                         }
641 
642                         // Check building isn't too close to the triangle edge.
643                         float type_roll = mt_rand(&seed);
644                         SGBuildingBin::BuildingType buildingtype = bin->getBuildingType(type_roll);
645                         float radius = bin->getBuildingMaxRadius(buildingtype);
646 
647                         // Determine the actual center of the building, by shifting from the
648                         // center of the front face to the true center.
649                         osg::Matrix rotationMat = osg::Matrix::rotate(- rotation * M_PI * 2,
650                                                                       osg::Vec3f(0.0, 0.0, 1.0));
651                         SGVec3f buildingCenter = randomPoint + toSG(osg::Vec3f(-0.5 * bin->getBuildingMaxDepth(buildingtype), 0.0, 0.0) * rotationMat);
652 
653                         SGVec3f p = buildingCenter - vorigin;
654 #if 1
655                         float edges[] = { length(cross(p     , p - v0)) / length(v0),
656                             length(cross(p - v0, p - v1)) / length(v1 - v0),
657                             length(cross(p - v1, p     )) / length(v1)      };
658                             float edge_dist = *std::min_element(edges, edges + 3);
659 #else
660                             float edge_dist = min_dist_from_borders(randomPoint, borderSegs);
661 #endif
662                             if (edge_dist < radius) {
663                                 triangle_dropped++;
664                                 continue;
665                             }
666 
667                             // Check building isn't too close to random objects and other buildings.
668                             bool close = false;
669                             std::vector<std::pair<SGVec3f, float> >::iterator iter;
670 
671                             for (iter = triangleBuildingList.begin(); iter != triangleBuildingList.end(); ++iter) {
672                                 float min_dist = iter->second + radius;
673                                 if (distSqr(iter->first, buildingCenter) < min_dist * min_dist) {
674                                     close = true;
675                                     continue;
676                                 }
677                             }
678 
679                             if (close) {
680                                 building_dropped++;
681                                 continue;
682                             }
683 
684                             for (iter = triangleObjectsList.begin(); iter != triangleObjectsList.end(); ++iter) {
685                                 float min_dist = iter->second + radius;
686                                 if (distSqr(iter->first, buildingCenter) < min_dist * min_dist) {
687                                     close = true;
688                                     continue;
689                                 }
690                             }
691 
692                             if (close) {
693                                 random_dropped++;
694                                 continue;
695                             }
696 
697                             std::pair<SGVec3f, float> pt = std::make_pair(buildingCenter, radius);
698                             triangleBuildingList.push_back(pt);
699                             bin->insert(randomPoint, rotation, buildingtype);
700                     }
701                 }
702 
703                 triangleObjectsList.clear();
704                 triangleBuildingList.clear();
705             }
706 
707             const int numBuildings = (bin) ? bin->getNumBuildings() : 0;
708             if (numBuildings > 0) {
709                 SG_LOG(SG_TERRAIN, SG_DEBUG, "computed Random Buildings: " << numBuildings);
710                 SG_LOG(SG_TERRAIN, SG_DEBUG, "  Dropped due to mask: " << mask_dropped);
711                 SG_LOG(SG_TERRAIN, SG_DEBUG, "  Dropped due to random object: " << random_dropped);
712                 SG_LOG(SG_TERRAIN, SG_DEBUG, "  Dropped due to other buildings: " << building_dropped);
713             }
714         }
715     }
716 
computeRandomForest(std::vector<SGTriangleInfo> & matTris,float vegetation_density,SGTreeBinList & randomForest)717     void computeRandomForest(std::vector<SGTriangleInfo>& matTris, float vegetation_density, SGTreeBinList& randomForest)
718     {
719         unsigned int i;
720 
721         // generate a repeatable random seed
722         mt seed;
723         mt_init(&seed, unsigned(586));
724 
725         for ( i=0; i<matTris.size(); i++ ) {
726             SGMaterial *mat = matTris[i].getMaterial();
727             if (!mat)
728                 continue;
729 
730             float wood_coverage = mat->get_wood_coverage();
731             if ((wood_coverage <= 0) || (vegetation_density <= 0))
732                 continue;
733 
734             // Attributes that don't vary by tree but do vary by material
735             bool found = false;
736             TreeBin* bin = NULL;
737 
738             BOOST_FOREACH(bin, randomForest)
739             {
740                 if ((bin->texture           == mat->get_tree_texture()  ) &&
741                     (bin->teffect           == mat->get_tree_effect()   ) &&
742                     (bin->texture_varieties == mat->get_tree_varieties()) &&
743                     (bin->range             == mat->get_tree_range()    ) &&
744                     (bin->width             == mat->get_tree_width()    ) &&
745                     (bin->height            == mat->get_tree_height()   )   ) {
746                     found = true;
747                 break;
748                     }
749             }
750 
751             if (!found) {
752                 bin = new TreeBin();
753                 bin->texture = mat->get_tree_texture();
754                 SG_LOG(SG_TERRAIN, SG_DEBUG, "Tree texture " << bin->texture);
755                 bin->teffect = mat->get_tree_effect();
756                 SG_LOG(SG_TERRAIN, SG_DEBUG, "Tree effect " << bin->teffect);
757                 bin->range   = mat->get_tree_range();
758                 bin->width   = mat->get_tree_width();
759                 bin->height  = mat->get_tree_height();
760                 bin->texture_varieties = mat->get_tree_varieties();
761                 randomForest.push_back(bin);
762             }
763 
764             std::vector<SGVec3f> randomPoints;
765             std::vector<SGVec3f> randomPointNormals;
766             matTris[i].addRandomTreePoints(wood_coverage,
767                                            mat->get_one_object_mask(matTris[i].getTextureIndex()),
768                                            vegetation_density,
769                                            mat->get_cos_tree_max_density_slope_angle(),
770                                            mat->get_cos_tree_zero_density_slope_angle(),
771                                            mat->get_is_plantation(),
772                                            randomPoints,
773                                            randomPointNormals);
774 
775             std::vector<SGVec3f>::iterator k;
776             std::vector<SGVec3f>::iterator j;
777             for (k = randomPoints.begin(), j = randomPointNormals.begin(); k != randomPoints.end(); ++k, ++j) {
778 	              bin->insert(*k, *j);
779             }
780         }
781     }
782 
computeRandomSurfaceLights(std::vector<SGTriangleInfo> & matTris,SGLightBin & randomTileLights)783     void computeRandomSurfaceLights(std::vector<SGTriangleInfo>& matTris, SGLightBin& randomTileLights )
784     {
785         unsigned int i;
786 
787         // Only compute the lights if we haven't already done so.
788         // For example, the light data will still exist if the
789         // PagedLOD expires.
790         if ( _randomSurfaceLightsComputed )
791         {
792             return;
793         }
794         _randomSurfaceLightsComputed = true;
795 
796         // generate a repeatable random seed
797         mt seed;
798         mt_init(&seed, unsigned(123));
799 
800         for ( i=0; i<matTris.size(); i++ ) {
801             SGMaterial *mat = matTris[i].getMaterial();
802             if (!mat)
803                 continue;
804 
805             float coverage = mat->get_light_coverage();
806             if (coverage <= 0)
807                 continue;
808 
809             int texIndex = matTris[i].getTextureIndex();
810 
811             std::vector<SGVec3f> randomPoints;
812             matTris[i].addRandomSurfacePoints(coverage, 3, mat->get_one_object_mask(texIndex), randomPoints);
813             std::vector<SGVec3f>::iterator j;
814             for (j = randomPoints.begin(); j != randomPoints.end(); ++j) {
815                 float zombie = mt_rand(&seed);
816                 // factor = sg_random() ^ 2, range = 0 .. 1 concentrated towards 0
817                 float factor = mt_rand(&seed);
818                 factor *= factor;
819 
820                 float bright = 1;
821                 SGVec4f color;
822                 if ( zombie > 0.5 ) {
823                     // 50% chance of yellowish
824                     color = SGVec4f(0.9f, 0.9f, 0.3f, bright - factor * 0.2f);
825                 } else if (zombie > 0.15f) {
826                     // 35% chance of whitish
827                     color = SGVec4f(0.9, 0.9f, 0.8f, bright - factor * 0.2f);
828                 } else if (zombie > 0.05f) {
829                     // 10% chance of orangish
830                     color = SGVec4f(0.9f, 0.6f, 0.2f, bright - factor * 0.2f);
831                 } else {
832                     // 5% chance of redish
833                     color = SGVec4f(0.9f, 0.2f, 0.2f, bright - factor * 0.2f);
834                 }
835                 randomTileLights.insert(*j, color);
836             }
837         }
838     }
839 
840     // Generate all the lighting objects for the tile.
generateLightingTileObjects(std::vector<SGTriangleInfo> & matTris,const SGMaterialCache * matcache)841     osg::LOD* generateLightingTileObjects(std::vector<SGTriangleInfo>& matTris, const SGMaterialCache* matcache)
842     {
843       SGLightBin randomTileLights;
844       computeRandomSurfaceLights(matTris, randomTileLights);
845 
846       GroundLightManager* lightManager = GroundLightManager::instance();
847       osg::ref_ptr<osg::Group> lightGroup = new SGOffsetTransform(0.94);
848       SGVec3f up(0, 0, 1);
849 
850       if (tileLights.getNumLights() > 0 || randomTileLights.getNumLights() > 0) {
851         osg::ref_ptr<osg::Group> groundLights0 = new osg::Group;
852 
853         groundLights0->setStateSet(lightManager->getGroundLightStateSet());
854         groundLights0->setNodeMask(GROUNDLIGHTS0_BIT);
855 
856         osg::ref_ptr<EffectGeode> geode = new EffectGeode;
857         osg::ref_ptr<Effect> lightEffect = getLightEffect(24, osg::Vec3(1, 0.001, 0.00001), 1, 8, false, _options);
858 
859         geode->setEffect(lightEffect);
860         geode->addDrawable(SGLightFactory::getLights(tileLights));
861         geode->addDrawable(SGLightFactory::getLights(randomTileLights, 4, -0.3f));
862         groundLights0->addChild(geode);
863         lightGroup->addChild(groundLights0);
864       }
865 
866       if (randomTileLights.getNumLights() > 0) {
867         osg::ref_ptr<osg::Group> groundLights1 = new osg::Group;
868         groundLights1->setStateSet(lightManager->getGroundLightStateSet());
869         groundLights1->setNodeMask(GROUNDLIGHTS1_BIT);
870 
871         osg::ref_ptr<osg::Group> groundLights2 = new osg::Group;
872         groundLights2->setStateSet(lightManager->getGroundLightStateSet());
873         groundLights2->setNodeMask(GROUNDLIGHTS2_BIT);
874 
875         osg::ref_ptr<EffectGeode> geode1 = new EffectGeode;
876 
877         osg::ref_ptr<Effect> lightEffect = getLightEffect(24, osg::Vec3(1, 0.001, 0.00001), 1, 8, false, _options);
878         geode1->setEffect(lightEffect);
879         geode1->addDrawable(SGLightFactory::getLights(randomTileLights, 2, -0.15f));
880         groundLights1->addChild(geode1);
881         lightGroup->addChild(groundLights1);
882 
883         osg::ref_ptr<EffectGeode> geode2 = new EffectGeode;
884 
885         geode2->setEffect(lightEffect);
886         geode2->addDrawable(SGLightFactory::getLights(randomTileLights));
887         groundLights2->addChild(geode2);
888         lightGroup->addChild(groundLights2);
889       }
890 
891       if (! vasiLights.empty()) {
892         EffectGeode* vasiGeode = new EffectGeode;
893         Effect* vasiEffect = getLightEffect(24, osg::Vec3(1, 0.0001, 0.000001), 1, 24, true, _options);
894         vasiGeode->setEffect(vasiEffect);
895         SGVec4f red(1, 0, 0, 1);
896         SGMaterial* mat = 0;
897         if (matcache)
898           mat = matcache->find("RWY_RED_LIGHTS");
899         if (mat) {
900           red = mat->get_light_color();
901         }
902 
903         SGVec4f white(1, 1, 1, 1);
904         mat = 0;
905         if (matcache)
906           mat = matcache->find("RWY_WHITE_LIGHTS");
907         if (mat) {
908           white = mat->get_light_color();
909         }
910         SGDirectionalLightListBin::const_iterator i;
911         for (i = vasiLights.begin();
912              i != vasiLights.end(); ++i) {
913             osg::Drawable* vasiDraw = SGLightFactory::getVasi(up, *i, red, white);
914             vasiGeode->addDrawable( vasiDraw );
915         }
916         osg::StateSet* ss = lightManager->getRunwayLightStateSet();
917         vasiGeode->setStateSet( ss );
918         lightGroup->addChild(vasiGeode);
919       }
920 
921       Effect* runwayEffect = 0;
922       if (runwayLights.getNumLights() > 0 || taxiLights.getNumLights() > 0) {
923           runwayEffect = getLightEffect(16, osg::Vec3(1, 0.001, 0.0002), 1, 16, true, _options);
924       }
925 
926       if (runwayLights.getNumLights() > 0
927           || !rabitLights.empty()
928           || !reilLights.empty()
929           || !odalLights.empty()
930           || !holdshortLights.empty()
931           || !guardLights.empty()) {
932 
933         osg::Group* rwyLightsGroup = new osg::Group;
934         rwyLightsGroup->setStateSet(lightManager->getRunwayLightStateSet());
935         rwyLightsGroup->setNodeMask(RUNWAYLIGHTS_BIT);
936 
937         SGDirectionalLightListBin::const_iterator i;
938 
939         for (i = rabitLights.begin() ; i != rabitLights.end() ; ++i) {
940             rwyLightsGroup->addChild(SGLightFactory::getSequenced(*i, _options));
941         }
942         for (i = reilLights.begin() ; i != reilLights.end() ; ++i) {
943             rwyLightsGroup->addChild(SGLightFactory::getReil(*i, _options));
944         }
945         for (i = holdshortLights.begin() ; i != holdshortLights.end() ; ++i) {
946             rwyLightsGroup->addChild(SGLightFactory::getHoldShort(*i, _options));
947         }
948         for (i = guardLights.begin() ; i != guardLights.end() ; ++i) {
949             rwyLightsGroup->addChild(SGLightFactory::getGuard(*i, _options));
950         }
951         SGLightListBin::const_iterator j;
952         for (j = odalLights.begin() ; j != odalLights.end() ; ++j) {
953             rwyLightsGroup->addChild(SGLightFactory::getOdal(*j, _options));
954         }
955 
956         if (runwayLights.getNumLights() > 0) {
957           osg::ref_ptr<EffectGeode> geode = new EffectGeode;
958           geode->setEffect(runwayEffect);
959           geode->addDrawable(SGLightFactory::getLights(runwayLights));
960           rwyLightsGroup->addChild(geode);
961         }
962 
963         lightGroup->addChild(rwyLightsGroup);
964       }
965 
966       if (taxiLights.getNumLights() > 0) {
967         osg::Group* taxiLightsGroup = new osg::Group;
968         taxiLightsGroup->setStateSet(lightManager->getTaxiLightStateSet());
969         taxiLightsGroup->setNodeMask(RUNWAYLIGHTS_BIT);
970         EffectGeode* geode = new EffectGeode;
971         geode->setEffect(runwayEffect);
972         geode->addDrawable(SGLightFactory::getLights(taxiLights));
973         taxiLightsGroup->addChild(geode);
974         lightGroup->addChild(taxiLightsGroup);
975       }
976 
977       osg::LOD* lightLOD = NULL;
978 
979       if (lightGroup->getNumChildren() > 0) {
980         lightLOD = new osg::LOD;
981         lightLOD->addChild(lightGroup.get(), 0, 60000);
982         // VASI is always on, so doesn't use light bits.
983         lightLOD->setNodeMask(LIGHTS_BITS | MODEL_BIT | PERMANENTLIGHT_BIT);
984       }
985 
986       return lightLOD;
987     }
988 
989     // Generate all the random forest, objects and buildings for the tile
generateRandomTileObjects(std::vector<SGTriangleInfo> & matTris,const SGMaterialCache * matcache)990     osg::LOD* generateRandomTileObjects(std::vector<SGTriangleInfo>& matTris, const SGMaterialCache* matcache)
991     {
992       SGMaterialLibPtr matlib;
993       bool use_random_objects = false;
994       bool use_random_vegetation = false;
995       bool use_random_buildings = false;
996       float vegetation_density = 1.0f;
997       float building_density = 1.0f;
998       float object_range = SG_OBJECT_RANGE_ROUGH;
999       bool useVBOs = false;
1000 
1001       osg::ref_ptr<osg::Group> randomObjects;
1002       osg::ref_ptr<osg::Group> forestNode;
1003       osg::ref_ptr<osg::Group> buildingNode;
1004 
1005       if (_options) {
1006         matlib = _options->getMaterialLib();
1007         SGPropertyNode* propertyNode = _options->getPropertyNode().get();
1008         if (propertyNode) {
1009             use_random_objects
1010                 = propertyNode->getBoolValue("/sim/rendering/random-objects",
1011                                              use_random_objects);
1012             use_random_vegetation
1013                 = propertyNode->getBoolValue("/sim/rendering/random-vegetation",
1014                                              use_random_vegetation);
1015             vegetation_density
1016                 = propertyNode->getFloatValue("/sim/rendering/vegetation-density",
1017                                               vegetation_density);
1018             use_random_buildings
1019                 = propertyNode->getBoolValue("/sim/rendering/random-buildings",
1020                                              use_random_buildings);
1021             building_density
1022                 = propertyNode->getFloatValue("/sim/rendering/building-density",
1023                                               building_density);
1024             object_range
1025                 = propertyNode->getFloatValue("/sim/rendering/static-lod/rough",
1026                                               object_range);
1027         }
1028 
1029         useVBOs = (_options->getPluginStringData("SimGear::USE_VBOS") == "ON");
1030       }
1031 
1032       SGMatModelBin     randomModels;
1033 
1034       SGBuildingBinList randomBuildings;
1035 
1036       if (matlib && (use_random_objects || use_random_buildings)) {
1037           computeRandomObjectsAndBuildings( matTris,
1038                                             building_density,
1039                                             use_random_objects,
1040                                             use_random_buildings,
1041                                             useVBOs,
1042                                             randomModels,
1043                                             randomBuildings
1044                                           );
1045       }
1046 
1047       if (randomModels.getNumModels() > 0) {
1048         // Generate a repeatable random seed
1049         mt seed;
1050         mt_init(&seed, unsigned(123));
1051 
1052         std::vector<ModelLOD> models;
1053         for (unsigned int i = 0; i < randomModels.getNumModels(); i++) {
1054           SGMatModelBin::MatModel obj = randomModels.getMatModel(i);
1055 
1056           SGPropertyNode* root = _options->getPropertyNode()->getRootNode();
1057           osg::Node* node = obj.model->get_random_model(root, &seed);
1058 
1059           // Create a matrix to place the object in the correct
1060           // location, and then apply the rotation matrix created
1061           // above, with an additional random (or taken from
1062           // the object mask) heading rotation if appropriate.
1063           osg::Matrix transformMat;
1064           transformMat = osg::Matrix::translate(toOsg(obj.position));
1065           if (obj.model->get_heading_type() == SGMatModel::HEADING_RANDOM) {
1066             // Rotate the object around the z axis.
1067             double hdg = mt_rand(&seed) * M_PI * 2;
1068             transformMat.preMult(osg::Matrix::rotate(hdg,
1069                                                      osg::Vec3d(0.0, 0.0, 1.0)));
1070           }
1071 
1072           if (obj.model->get_heading_type() == SGMatModel::HEADING_MASK) {
1073             // Rotate the object around the z axis.
1074             double hdg =  - obj.rotation * M_PI * 2;
1075             transformMat.preMult(osg::Matrix::rotate(hdg,
1076                                                      osg::Vec3d(0.0, 0.0, 1.0)));
1077           }
1078 
1079           osg::MatrixTransform* position =
1080             new osg::MatrixTransform(transformMat);
1081           position->setName("positionRandomModel");
1082           position->addChild(node);
1083           models.push_back(ModelLOD(position, obj.lod));
1084         }
1085         RandomObjectsQuadtree quadtree((GetModelLODCoord()), (AddModelLOD()));
1086         quadtree.buildQuadTree(models.begin(), models.end());
1087         randomObjects = quadtree.getRoot();
1088         randomObjects->setName("Random objects");
1089       }
1090 
1091       if (!randomBuildings.empty()) {
1092         buildingNode = createRandomBuildings(randomBuildings, osg::Matrix::identity(), _options);
1093         buildingNode->setName("Random buildings");
1094         randomBuildings.clear();
1095       }
1096 
1097       if (use_random_vegetation && matlib) {
1098         // Now add some random forest.
1099         SGTreeBinList randomForest;
1100         computeRandomForest(matTris, vegetation_density, randomForest);
1101 
1102         if (!randomForest.empty()) {
1103           forestNode = createForest(randomForest, osg::Matrix::identity(),_options);
1104           forestNode->setName("Random trees");
1105         }
1106       }
1107 
1108       osg::LOD* objectLOD = NULL;
1109 
1110       if (randomObjects.valid() ||  forestNode.valid() || buildingNode.valid()) {
1111         objectLOD = new osg::LOD;
1112 
1113         // random objects, trees and buildings can be displayed up to 2xrange out.
1114         if (randomObjects.valid()) objectLOD->addChild(randomObjects.get(), 0, 2.0 * object_range + SG_TILE_RADIUS);
1115         if (forestNode.valid())  objectLOD->addChild(forestNode.get(), 0, 2.0 * object_range + SG_TILE_RADIUS);
1116         if (buildingNode.valid()) objectLOD->addChild(buildingNode.get(), 0, 2.0 * object_range + SG_TILE_RADIUS);
1117 
1118         unsigned nodeMask = SG_NODEMASK_RECEIVESHADOW_BIT | SG_NODEMASK_TERRAIN_BIT;
1119         objectLOD->setNodeMask(nodeMask);
1120       }
1121 
1122       return objectLOD;
1123     }
1124 
1125     /// The original options to use for this bunch of models
1126     osg::ref_ptr<SGReaderWriterOptions>     _options;
1127     string                                  _path;
1128     bool                                    _loadterrain;
1129     osg::ref_ptr<osg::Node>                 _rootNode;
1130     SGVec3d                                 _gbs_center;
1131     bool                                    _randomSurfaceLightsComputed;
1132     bool                                    _tileRandomObjectsComputed;
1133 
1134     // most of these are just point and color arrays - extracted from the
1135     // .BTG PointGeometry at tile load time.
1136     // It shouldn't be too much to keep this in memory even if we don't use it.
1137     SGLightBin                              tileLights;
1138     SGDirectionalLightBin                   runwayLights;
1139     SGDirectionalLightBin                   taxiLights;
1140     SGDirectionalLightListBin               vasiLights;
1141     SGDirectionalLightListBin               rabitLights;
1142     SGLightListBin                          odalLights;
1143     SGDirectionalLightListBin               holdshortLights;
1144     SGDirectionalLightListBin               guardLights;
1145     SGDirectionalLightListBin               reilLights;
1146 };
1147