]> git.mxchange.org Git - simgear.git/blob - simgear/scene/tgdb/obj.cxx
1f6cfd275fa269003415991cb749c46309d222ac
[simgear.git] / simgear / scene / tgdb / obj.cxx
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 "obj.hxx"
30
31 #include <simgear/compiler.h>
32
33 #include <osg/Fog>
34 #include <osg/Geode>
35 #include <osg/Geometry>
36 #include <osg/Group>
37 #include <osg/LOD>
38 #include <osg/MatrixTransform>
39 #include <osg/Point>
40 #include <osg/StateSet>
41 #include <osg/Switch>
42
43 #include <simgear/debug/logstream.hxx>
44 #include <simgear/io/sg_binobj.hxx>
45 #include <simgear/math/sg_geodesy.hxx>
46 #include <simgear/math/sg_random.h>
47 #include <simgear/scene/material/mat.hxx>
48 #include <simgear/scene/material/matlib.hxx>
49 #include <simgear/scene/model/SGOffsetTransform.hxx>
50 #include <simgear/scene/util/SGUpdateVisitor.hxx>
51 #include <simgear/scene/util/SGNodeMasks.hxx>
52 #include <simgear/threads/SGThread.hxx>
53 #include <simgear/threads/SGGuard.hxx>
54
55 #include "SGTexturedTriangleBin.hxx"
56 #include "SGLightBin.hxx"
57 #include "SGDirectionalLightBin.hxx"
58 #include "GroundLightManager.hxx"
59
60
61 #include "pt_lights.hxx"
62
63 using namespace simgear;
64
65 typedef std::map<std::string,SGTexturedTriangleBin> SGMaterialTriangleMap;
66 typedef std::list<SGLightBin> SGLightListBin;
67 typedef std::list<SGDirectionalLightBin> SGDirectionalLightListBin;
68
69 struct SGTileGeometryBin {
70   SGMaterialTriangleMap materialTriangleMap;
71   SGLightBin tileLights;
72   SGLightBin randomTileLights;
73   SGDirectionalLightBin runwayLights;
74   SGDirectionalLightBin taxiLights;
75   SGDirectionalLightListBin vasiLights;
76   SGDirectionalLightListBin rabitLights;
77   SGLightListBin odalLights;
78   SGDirectionalLightListBin reilLights;
79
80   static SGVec4f
81   getMaterialLightColor(const SGMaterial* material)
82   {
83     if (!material)
84       return SGVec4f(1, 1, 1, 0.8);
85     return material->get_light_color();
86   }
87
88   static void
89   addPointGeometry(SGLightBin& lights,
90                    const std::vector<SGVec3d>& vertices,
91                    const SGVec4f& color,
92                    const int_list& pts_v)
93   {
94     for (unsigned i = 0; i < pts_v.size(); ++i)
95       lights.insert(toVec3f(vertices[pts_v[i]]), color);
96   }
97
98   static void
99   addPointGeometry(SGDirectionalLightBin& lights,
100                    const std::vector<SGVec3d>& vertices,
101                    const std::vector<SGVec3f>& normals,
102                    const SGVec4f& color,
103                    const int_list& pts_v,
104                    const int_list& pts_n)
105   {
106     // If the normal indices match the vertex indices, use seperate
107     // normal indices. Else reuse the vertex indices for the normals.
108     if (pts_v.size() == pts_n.size()) {
109       for (unsigned i = 0; i < pts_v.size(); ++i)
110         lights.insert(toVec3f(vertices[pts_v[i]]), normals[pts_n[i]], color);
111     } else {
112       for (unsigned i = 0; i < pts_v.size(); ++i)
113         lights.insert(toVec3f(vertices[pts_v[i]]), normals[pts_v[i]], color);
114     }
115   }
116
117   bool
118   insertPtGeometry(const SGBinObject& obj, SGMaterialLib* matlib)
119   {
120     if (obj.get_pts_v().size() != obj.get_pts_n().size()) {
121       SG_LOG(SG_TERRAIN, SG_ALERT,
122              "Group list sizes for points do not match!");
123       return false;
124     }
125
126     for (unsigned grp = 0; grp < obj.get_pts_v().size(); ++grp) {
127       std::string materialName = obj.get_pt_materials()[grp];
128       SGMaterial* material = matlib->find(materialName);
129       SGVec4f color = getMaterialLightColor(material);
130       
131       if (3 <= materialName.size() && materialName.substr(0, 3) != "RWY") {
132         // Just plain lights. Not something for the runway.
133         addPointGeometry(tileLights, obj.get_wgs84_nodes(), color,
134                          obj.get_pts_v()[grp]);
135       } else if (materialName == "RWY_BLUE_TAXIWAY_LIGHTS"
136                  || materialName == "RWY_GREEN_TAXIWAY_LIGHTS") {
137         addPointGeometry(taxiLights, obj.get_wgs84_nodes(), obj.get_normals(),
138                          color, obj.get_pts_v()[grp], obj.get_pts_n()[grp]);
139       } else if (materialName == "RWY_VASI_LIGHTS") {
140         vasiLights.push_back(SGDirectionalLightBin());
141         addPointGeometry(vasiLights.back(), obj.get_wgs84_nodes(),
142                          obj.get_normals(), color, obj.get_pts_v()[grp],
143                          obj.get_pts_n()[grp]);
144       } else if (materialName == "RWY_SEQUENCED_LIGHTS") {
145         rabitLights.push_back(SGDirectionalLightBin());
146         addPointGeometry(rabitLights.back(), obj.get_wgs84_nodes(),
147                          obj.get_normals(), color, obj.get_pts_v()[grp],
148                          obj.get_pts_n()[grp]);
149       } else if (materialName == "RWY_ODALS_LIGHTS") {
150         odalLights.push_back(SGLightBin());
151         addPointGeometry(odalLights.back(), obj.get_wgs84_nodes(),
152                          color, obj.get_pts_v()[grp]);
153       } else if (materialName == "RWY_REIL_LIGHTS") {
154         reilLights.push_back(SGDirectionalLightBin());
155         addPointGeometry(reilLights.back(), obj.get_wgs84_nodes(),
156                          obj.get_normals(), color, obj.get_pts_v()[grp],
157                          obj.get_pts_n()[grp]);
158       } else {
159         // what is left must be runway lights
160         addPointGeometry(runwayLights, obj.get_wgs84_nodes(),
161                          obj.get_normals(), color, obj.get_pts_v()[grp],
162                          obj.get_pts_n()[grp]);
163       }
164     }
165
166     return true;
167   }
168
169
170   static SGVec2f
171   getTexCoord(const std::vector<SGVec2f>& texCoords, const int_list& tc,
172               const SGVec2f& tcScale, unsigned i)
173   {
174     if (tc.empty())
175       return tcScale;
176     else if (tc.size() == 1)
177       return mult(texCoords[tc[0]], tcScale);
178     else
179       return mult(texCoords[tc[i]], tcScale);
180   }
181
182   static void
183   addTriangleGeometry(SGTexturedTriangleBin& triangles,
184                       const std::vector<SGVec3d>& vertices,
185                       const std::vector<SGVec3f>& normals,
186                       const std::vector<SGVec2f>& texCoords,
187                       const int_list& tris_v,
188                       const int_list& tris_n,
189                       const int_list& tris_tc,
190                       const SGVec2f& tcScale)
191   {
192     if (tris_v.size() != tris_n.size()) {
193       // If the normal indices do not match, they should be inmplicitly
194       // the same than the vertex indices. So just call ourselves again
195       // with the matching index vector.
196       addTriangleGeometry(triangles, vertices, normals, texCoords,
197                           tris_v, tris_v, tris_tc, tcScale);
198       return;
199     }
200
201     for (unsigned i = 2; i < tris_v.size(); i += 3) {
202       SGVertNormTex v0;
203       v0.vertex = toVec3f(vertices[tris_v[i-2]]);
204       v0.normal = normals[tris_n[i-2]];
205       v0.texCoord = getTexCoord(texCoords, tris_tc, tcScale, i-2);
206       SGVertNormTex v1;
207       v1.vertex = toVec3f(vertices[tris_v[i-1]]);
208       v1.normal = normals[tris_n[i-1]];
209       v1.texCoord = getTexCoord(texCoords, tris_tc, tcScale, i-1);
210       SGVertNormTex v2;
211       v2.vertex = toVec3f(vertices[tris_v[i]]);
212       v2.normal = normals[tris_n[i]];
213       v2.texCoord = getTexCoord(texCoords, tris_tc, tcScale, i);
214       triangles.insert(v0, v1, v2);
215     }
216   }
217
218   static void
219   addStripGeometry(SGTexturedTriangleBin& triangles,
220                    const std::vector<SGVec3d>& vertices,
221                    const std::vector<SGVec3f>& normals,
222                    const std::vector<SGVec2f>& texCoords,
223                    const int_list& strips_v,
224                    const int_list& strips_n,
225                    const int_list& strips_tc,
226                    const SGVec2f& tcScale)
227   {
228     if (strips_v.size() != strips_n.size()) {
229       // If the normal indices do not match, they should be inmplicitly
230       // the same than the vertex indices. So just call ourselves again
231       // with the matching index vector.
232       addStripGeometry(triangles, vertices, normals, texCoords,
233                        strips_v, strips_v, strips_tc, tcScale);
234       return;
235     }
236
237     for (unsigned i = 2; i < strips_v.size(); ++i) {
238       SGVertNormTex v0;
239       v0.vertex = toVec3f(vertices[strips_v[i-2]]);
240       v0.normal = normals[strips_n[i-2]];
241       v0.texCoord = getTexCoord(texCoords, strips_tc, tcScale, i-2);
242       SGVertNormTex v1;
243       v1.vertex = toVec3f(vertices[strips_v[i-1]]);
244       v1.normal = normals[strips_n[i-1]];
245       v1.texCoord = getTexCoord(texCoords, strips_tc, tcScale, i-1);
246       SGVertNormTex v2;
247       v2.vertex = toVec3f(vertices[strips_v[i]]);
248       v2.normal = normals[strips_n[i]];
249       v2.texCoord = getTexCoord(texCoords, strips_tc, tcScale, i);
250       if (i%2)
251         triangles.insert(v1, v0, v2);
252       else
253         triangles.insert(v0, v1, v2);
254     }
255   }
256   
257   static void
258   addFanGeometry(SGTexturedTriangleBin& triangles,
259                  const std::vector<SGVec3d>& vertices,
260                  const std::vector<SGVec3f>& normals,
261                  const std::vector<SGVec2f>& texCoords,
262                  const int_list& fans_v,
263                  const int_list& fans_n,
264                  const int_list& fans_tc,
265                  const SGVec2f& tcScale)
266   {
267     if (fans_v.size() != fans_n.size()) {
268       // If the normal indices do not match, they should be implicitly
269       // the same than the vertex indices. So just call ourselves again
270       // with the matching index vector.
271       addFanGeometry(triangles, vertices, normals, texCoords,
272                      fans_v, fans_v, fans_tc, tcScale);
273       return;
274     }
275
276     SGVertNormTex v0;
277     v0.vertex = toVec3f(vertices[fans_v[0]]);
278     v0.normal = normals[fans_n[0]];
279     v0.texCoord = getTexCoord(texCoords, fans_tc, tcScale, 0);
280     SGVertNormTex v1;
281     v1.vertex = toVec3f(vertices[fans_v[1]]);
282     v1.normal = normals[fans_n[1]];
283     v1.texCoord = getTexCoord(texCoords, fans_tc, tcScale, 1);
284     for (unsigned i = 2; i < fans_v.size(); ++i) {
285       SGVertNormTex v2;
286       v2.vertex = toVec3f(vertices[fans_v[i]]);
287       v2.normal = normals[fans_n[i]];
288       v2.texCoord = getTexCoord(texCoords, fans_tc, tcScale, i);
289       triangles.insert(v0, v1, v2);
290       v1 = v2;
291     }
292   }
293
294   SGVec2f getTexCoordScale(const std::string& name, SGMaterialLib* matlib)
295   {
296     if (!matlib)
297       return SGVec2f(1, 1);
298     SGMaterial* material = matlib->find(name);
299     if (!material)
300       return SGVec2f(1, 1);
301
302     return material->get_tex_coord_scale();
303   }
304
305   bool
306   insertSurfaceGeometry(const SGBinObject& obj, SGMaterialLib* matlib)
307   {
308     if (obj.get_tris_n().size() < obj.get_tris_v().size() ||
309         obj.get_tris_tc().size() < obj.get_tris_v().size()) {
310       SG_LOG(SG_TERRAIN, SG_ALERT,
311              "Group list sizes for triangles do not match!");
312       return false;
313     }
314
315     for (unsigned grp = 0; grp < obj.get_tris_v().size(); ++grp) {
316       std::string materialName = obj.get_tri_materials()[grp];
317       SGVec2f tcScale = getTexCoordScale(materialName, matlib);
318       addTriangleGeometry(materialTriangleMap[materialName],
319                           obj.get_wgs84_nodes(), obj.get_normals(),
320                           obj.get_texcoords(), obj.get_tris_v()[grp],
321                           obj.get_tris_n()[grp], obj.get_tris_tc()[grp],
322                           tcScale);
323     }
324
325     if (obj.get_strips_n().size() < obj.get_strips_v().size() ||
326         obj.get_strips_tc().size() < obj.get_strips_v().size()) {
327       SG_LOG(SG_TERRAIN, SG_ALERT,
328              "Group list sizes for strips do not match!");
329       return false;
330     }
331     for (unsigned grp = 0; grp < obj.get_strips_v().size(); ++grp) {
332       std::string materialName = obj.get_strip_materials()[grp];
333       SGVec2f tcScale = getTexCoordScale(materialName, matlib);
334       addStripGeometry(materialTriangleMap[materialName],
335                        obj.get_wgs84_nodes(), obj.get_normals(),
336                        obj.get_texcoords(), obj.get_strips_v()[grp],
337                        obj.get_strips_n()[grp], obj.get_strips_tc()[grp],
338                        tcScale);
339     }
340
341     if (obj.get_fans_n().size() < obj.get_fans_v().size() ||
342         obj.get_fans_tc().size() < obj.get_fans_v().size()) {
343       SG_LOG(SG_TERRAIN, SG_ALERT,
344              "Group list sizes for fans do not match!");
345       return false;
346     }
347     for (unsigned grp = 0; grp < obj.get_fans_v().size(); ++grp) {
348       std::string materialName = obj.get_fan_materials()[grp];
349       SGVec2f tcScale = getTexCoordScale(materialName, matlib);
350       addFanGeometry(materialTriangleMap[materialName],
351                      obj.get_wgs84_nodes(), obj.get_normals(),
352                      obj.get_texcoords(), obj.get_fans_v()[grp],
353                      obj.get_fans_n()[grp], obj.get_fans_tc()[grp],
354                      tcScale);
355     }
356     return true;
357   }
358
359   osg::Node* getSurfaceGeometry(SGMaterialLib* matlib) const
360   {
361     if (materialTriangleMap.empty())
362       return 0;
363
364     osg::Geode* geode = new osg::Geode;
365     SGMaterialTriangleMap::const_iterator i;
366     for (i = materialTriangleMap.begin(); i != materialTriangleMap.end(); ++i) {
367       // CHUNCKED (sic) here splits up unconnected triangles parts of
368       // the mesh into different Geometry sets, presumably for better
369       // culling. I (timoore) believe it is more performant to build
370       // the biggest indexed sets possible at the expense of tight
371       // culling.
372 //#define CHUNCKED
373 #ifdef CHUNCKED
374       SGMaterial *mat = matlib->find(i->first);
375
376       std::list<SGTexturedTriangleBin::TriangleVector> connectSets;
377       i->second.getConnectedSets(connectSets);
378
379       std::list<SGTexturedTriangleBin::TriangleVector>::iterator j;
380       for (j = connectSets.begin(); j != connectSets.end(); ++j) {
381         osg::Geometry* geometry = i->second.buildGeometry(*j);
382         if (mat)
383           geometry->setStateSet(mat->get_state());
384         geode->addDrawable(geometry);
385       }
386 #else
387       osg::Geometry* geometry = i->second.buildGeometry();
388       SGMaterial *mat = matlib->find(i->first);
389       if (mat)
390         geometry->setStateSet(mat->get_state());
391       geode->addDrawable(geometry);
392 #endif
393     }
394     return geode;
395   }
396
397   void computeRandomSurfaceLights(SGMaterialLib* matlib)
398   {
399     SGMaterialTriangleMap::const_iterator i;
400     for (i = materialTriangleMap.begin(); i != materialTriangleMap.end(); ++i) {
401       SGMaterial *mat = matlib->find(i->first);
402       if (!mat)
403         continue;
404
405       float coverage = mat->get_light_coverage();
406       if (coverage <= 0)
407         continue;
408       if (coverage < 10000.0) {
409         SG_LOG(SG_INPUT, SG_ALERT, "Light coverage is "
410                << coverage << ", pushing up to 10000");
411         coverage = 10000;
412       }
413       
414       // generate a repeatable random seed
415       sg_srandom(unsigned(coverage));
416
417       std::vector<SGVec3f> randomPoints;
418       i->second.addRandomSurfacePoints(coverage, 3, randomPoints);
419       std::vector<SGVec3f>::iterator j;
420       for (j = randomPoints.begin(); j != randomPoints.end(); ++j) {
421         float zombie = sg_random();
422         // factor = sg_random() ^ 2, range = 0 .. 1 concentrated towards 0
423         float factor = sg_random();
424         factor *= factor;
425
426         float bright = 1;
427         SGVec4f color;
428         if ( zombie > 0.5 ) {
429           // 50% chance of yellowish
430           color = SGVec4f(0.9f, 0.9f, 0.3f, bright - factor * 0.2f);
431         } else if (zombie > 0.15f) {
432           // 35% chance of whitish
433           color = SGVec4f(0.9, 0.9f, 0.8f, bright - factor * 0.2f);
434         } else if (zombie > 0.05f) {
435           // 10% chance of orangish
436           color = SGVec4f(0.9f, 0.6f, 0.2f, bright - factor * 0.2f);
437         } else {
438           // 5% chance of redish
439           color = SGVec4f(0.9f, 0.2f, 0.2f, bright - factor * 0.2f);
440         }
441         randomTileLights.insert(*j, color);
442       }
443     }
444   }
445
446   bool insertBinObj(const SGBinObject& obj, SGMaterialLib* matlib)
447   {
448     if (!insertPtGeometry(obj, matlib))
449       return false;
450     if (!insertSurfaceGeometry(obj, matlib))
451       return false;
452     return true;
453   }
454 };
455
456 osg::Node*
457 SGLoadBTG(const std::string& path, SGMaterialLib *matlib, bool calc_lights, bool use_random_objects)
458 {
459   SGBinObject obj;
460   if (!obj.read_bin(path))
461     return false;
462
463   SGTileGeometryBin tileGeometryBin;
464   if (!tileGeometryBin.insertBinObj(obj, matlib))
465     return false;
466
467   SGVec3d center = obj.get_gbs_center2();
468   SGGeod geodPos = SGGeod::fromCart(center);
469   SGQuatd hlOr = SGQuatd::fromLonLat(geodPos);
470   SGVec3f up = toVec3f(hlOr.backTransform(SGVec3d(0, 0, -1)));
471   GroundLightManager* lightManager = GroundLightManager::instance();
472
473   osg::ref_ptr<osg::Group> lightGroup = new SGOffsetTransform(0.94);
474   osg::Group* terrainGroup = new osg::Group;
475   osg::Node* node = tileGeometryBin.getSurfaceGeometry(matlib);
476   if (node)
477     terrainGroup->addChild(node);
478
479   if (calc_lights) {
480     // FIXME: ugly, has a side effect
481     tileGeometryBin.computeRandomSurfaceLights(matlib);
482
483     if (tileGeometryBin.tileLights.getNumLights() > 0
484         || tileGeometryBin.randomTileLights.getNumLights() > 0) {
485       osg::Group* groundLights0 = new osg::Group;
486       groundLights0->setStateSet(lightManager->getGroundLightStateSet());
487       groundLights0->setNodeMask(GROUNDLIGHTS0_BIT);
488       osg::Geode* geode = new osg::Geode;
489       geode->addDrawable(SGLightFactory::getLights(tileGeometryBin.tileLights));
490       geode->addDrawable(SGLightFactory::getLights(tileGeometryBin.randomTileLights, 4, -0.3f));
491       groundLights0->addChild(geode);
492       lightGroup->addChild(groundLights0);
493     }
494     if (tileGeometryBin.randomTileLights.getNumLights() > 0) {
495       osg::Group* groundLights1 = new osg::Group;
496       groundLights1->setStateSet(lightManager->getGroundLightStateSet());
497       groundLights1->setNodeMask(GROUNDLIGHTS1_BIT);
498       osg::Group* groundLights2 = new osg::Group;
499       groundLights2->setStateSet(lightManager->getGroundLightStateSet());
500       groundLights2->setNodeMask(GROUNDLIGHTS2_BIT);
501       osg::Geode* geode = new osg::Geode;
502       geode->addDrawable(SGLightFactory::getLights(tileGeometryBin.randomTileLights, 2, -0.15f));
503       groundLights1->addChild(geode);
504       lightGroup->addChild(groundLights1);
505       geode = new osg::Geode;
506       geode->addDrawable(SGLightFactory::getLights(tileGeometryBin.randomTileLights));
507       groundLights2->addChild(geode);
508       lightGroup->addChild(groundLights2);
509     }
510   }
511
512   if (!tileGeometryBin.vasiLights.empty()) {
513     SGVec4f red(1, 0, 0, 1);
514     SGMaterial* mat = matlib->find("RWY_RED_LIGHTS");
515     if (mat)
516       red = mat->get_light_color();
517     SGVec4f white(1, 1, 1, 1);
518     mat = matlib->find("RWY_WHITE_LIGHTS");
519     if (mat)
520       white = mat->get_light_color();
521
522     osg::Geode* geode = new osg::Geode;
523     SGDirectionalLightListBin::const_iterator i;
524     for (i = tileGeometryBin.vasiLights.begin();
525          i != tileGeometryBin.vasiLights.end(); ++i) {
526       geode->addDrawable(SGLightFactory::getVasi(up, *i, red, white));
527     }
528     osg::Group* vasiLights = new osg::Group;
529     vasiLights->setCullCallback(new SGPointSpriteLightCullCallback(osg::Vec3(1, 0.0001, 0.000001), 6));
530     vasiLights->setStateSet(lightManager->getRunwayLightStateSet());
531     vasiLights->addChild(geode);
532     lightGroup->addChild(vasiLights);
533   }
534
535   if (tileGeometryBin.runwayLights.getNumLights() > 0
536       || !tileGeometryBin.rabitLights.empty()
537       || !tileGeometryBin.reilLights.empty()
538       || !tileGeometryBin.odalLights.empty()) {
539     osg::Group* rwyLights = new osg::Group;
540     rwyLights->setCullCallback(new SGPointSpriteLightCullCallback);
541     rwyLights->setStateSet(lightManager->getRunwayLightStateSet());
542     rwyLights->setNodeMask(RUNWAYLIGHTS_BIT);
543     if (tileGeometryBin.runwayLights.getNumLights() != 0) {
544       osg::Geode* geode = new osg::Geode;
545       geode->addDrawable(SGLightFactory::getLights(tileGeometryBin
546                                                    .runwayLights));
547       rwyLights->addChild(geode);
548     }
549     SGDirectionalLightListBin::const_iterator i;
550     for (i = tileGeometryBin.rabitLights.begin();
551          i != tileGeometryBin.rabitLights.end(); ++i) {
552       rwyLights->addChild(SGLightFactory::getSequenced(*i));
553     }
554     for (i = tileGeometryBin.reilLights.begin();
555          i != tileGeometryBin.reilLights.end(); ++i) {
556       rwyLights->addChild(SGLightFactory::getSequenced(*i));
557     }
558     SGLightListBin::const_iterator j;
559     for (j = tileGeometryBin.odalLights.begin();
560          j != tileGeometryBin.odalLights.end(); ++j) {
561       rwyLights->addChild(SGLightFactory::getOdal(*j));
562     }
563     lightGroup->addChild(rwyLights);
564   }
565
566   if (tileGeometryBin.taxiLights.getNumLights() > 0) {
567     osg::Group* taxiLights = new osg::Group;
568     taxiLights->setCullCallback(new SGPointSpriteLightCullCallback);
569     taxiLights->setStateSet(lightManager->getTaxiLightStateSet());
570     taxiLights->setNodeMask(RUNWAYLIGHTS_BIT);
571     osg::Geode* geode = new osg::Geode;
572     geode->addDrawable(SGLightFactory::getLights(tileGeometryBin.taxiLights));
573     taxiLights->addChild(geode);
574     lightGroup->addChild(taxiLights);
575   }
576
577   // The toplevel transform for that tile.
578   osg::MatrixTransform* transform = new osg::MatrixTransform;
579   transform->setName(path);
580   transform->setMatrix(osg::Matrix::translate(center.osg()));
581   transform->addChild(terrainGroup);
582   if (lightGroup->getNumChildren() > 0) {
583     osg::LOD* lightLOD = new osg::LOD;
584     lightLOD->addChild(lightGroup.get(), 0, 30000);
585     unsigned nodeMask = ~0u;
586     nodeMask &= ~SG_NODEMASK_CASTSHADOW_BIT;
587     nodeMask &= ~SG_NODEMASK_RECIEVESHADOW_BIT;
588     nodeMask &= ~SG_NODEMASK_PICK_BIT;
589     nodeMask &= ~SG_NODEMASK_TERRAIN_BIT;
590     lightLOD->setNodeMask(nodeMask);
591     transform->addChild(lightLOD);
592   }
593   return transform;
594 }