1 // future API - just run through once to convert from OSG to SG
2 // then we can use these triangle lists for random
3 // trees/lights/buildings/objects
4 struct SGTexturedTriangle
5 {
6 public:
7     std::vector<SGVec3f> vertices;
8     std::vector<SGVec2f> texcoords;
9 };
10 
11 struct SGBorderContour
12 {
13 public:
14     SGVec3d start;
15     SGVec3d end;
16 };
17 
18 class SGTriangleInfo
19 {
20 public:
SGTriangleInfo(const SGVec3d & center)21     SGTriangleInfo( const SGVec3d& center ) {
22         gbs_center = center;
23         mt_init(&seed, 123);
24     }
25 
26     // API used to build the Info by the visitor
addGeometry(osg::Geometry * g)27     void addGeometry( osg::Geometry* g ) {
28         geometries.push_back(g);
29     }
30 
setMaterial(SGMaterial * m)31     void setMaterial( SGMaterial* m ) {
32         mat = m;
33     }
34 
getMaterial(void) const35     SGMaterial* getMaterial( void ) const {
36         return mat;
37     }
38 
39     // API used to get a specific texture or effect from a material.  Materials can have
40     // multiple textures - use the floor of the x coordinate of the first vertes to select it.
41     // This will be constant, and give the same result each time to select one effect/texture per drawable.
getTextureIndex(void) const42     int getTextureIndex( void ) const {
43         int texInfo = 0;
44         const osg::Vec3Array* vertices =  dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
45         if ( vertices ) {
46             const osg::Vec3 *v0 = &vertices->operator[](0);
47             texInfo = floor(v0->x());
48         }
49         return texInfo;
50     }
51 
52     // new API - TODO
getTriangles(std::vector<SGTexturedTriangle> & tris)53     void getTriangles( std::vector<SGTexturedTriangle>& tris )
54     {
55         const osg::Vec3Array* vertices  = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
56         const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0));
57 
58         int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
59         if ( numPrimitiveSets > 0 ) {
60             const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
61             unsigned int numIndices = ps->getNumIndices();
62 
63             for ( unsigned int i=2; i<numIndices; i+= 3 ) {
64                 SGTexturedTriangle tri;
65 
66                 tri.vertices.push_back( toSG(vertices->operator[](ps->index(i-2))) );
67                 tri.vertices.push_back( toSG(vertices->operator[](ps->index(i-1))) );
68                 tri.vertices.push_back( toSG(vertices->operator[](ps->index(i-0))) );
69 
70                 tri.texcoords.push_back( toSG(texcoords->operator[](ps->index(i-2))) );
71                 tri.texcoords.push_back( toSG(texcoords->operator[](ps->index(i-1))) );
72                 tri.texcoords.push_back( toSG(texcoords->operator[](ps->index(i-0))) );
73             }
74         }
75     }
76 
getBorderContours(std::vector<SGBorderContour> & border)77     void getBorderContours( std::vector<SGBorderContour>& border )
78     {
79         // each structure contains a list of target indexes and a count
80         int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
81         if ( numPrimitiveSets > 0 ) {
82             const osg::Vec3Array* vertices  = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
83 
84             const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
85             unsigned int numTriangles = ps->getNumIndices()/3;
86 
87             // use a map for fast lookup map the segment as a 64 bit int
88             std::map<uint64_t, int> segCounter;
89             uint32_t idx1, idx2;
90             uint64_t key;
91 
92             for ( unsigned int i=0; i<numTriangles; i+= 3 ) {
93                 // first seg
94                 if ( ps->index(i+0) < ps->index(i+1) ) {
95                     idx1 = ps->index(i+0);
96                     idx2 = ps->index(i+1);
97                 } else {
98                     idx1 = ps->index(i+1);
99                     idx2 = ps->index(i+0);
100                 }
101 
102                 key=( (uint64_t)idx1<<32) | (uint64_t)idx2;
103                 SG_LOG(SG_TERRAIN, SG_ALERT, "key " << std::hex << key << std::dec << " count is " << segCounter[key] );
104                 segCounter[key]++;
105                 SG_LOG(SG_TERRAIN, SG_ALERT, "after increment key " << std::hex << key << std::dec << " count is " << segCounter[key] );
106 
107                 // second seg
108                 if ( ps->index(i+1) < ps->index(i+2) ) {
109                     idx1 = ps->index(i+1);
110                     idx2 = ps->index(i+2);
111                 } else {
112                     idx1 = ps->index(i+2);
113                     idx2 = ps->index(i+1);
114                 }
115 
116                 key=( (uint64_t)idx1<<32) | (uint64_t)idx2;
117                 SG_LOG(SG_TERRAIN, SG_ALERT, "key " << std::hex << key << std::dec << " count is " << segCounter[key] );
118                 segCounter[key]++;
119                 SG_LOG(SG_TERRAIN, SG_ALERT, "after increment key " << std::hex << key << std::dec << " count is " << segCounter[key] );
120 
121                 // third seg
122                 if ( ps->index(i+2) < ps->index(i+0) ) {
123                     idx1 = ps->index(i+2);
124                     idx2 = ps->index(i+0);
125                 } else {
126                     idx1 = ps->index(i+0);
127                     idx2 = ps->index(i+2);
128                 }
129 
130                 key=( (uint64_t)idx1<<32) | (uint64_t)idx2;
131                 SG_LOG(SG_TERRAIN, SG_ALERT, "key " << std::hex << key << std::dec << " count is " << segCounter[key] );
132                 segCounter[key]++;
133                 SG_LOG(SG_TERRAIN, SG_ALERT, "after increment key " << std::hex << key << std::dec << " count is " << segCounter[key] );
134             }
135 
136             // return all segments with count = 1 ( border )
137             std::map<uint64_t, int>::iterator segIt = segCounter.begin();
138             while ( segIt != segCounter.end() ) {
139                 if ( segIt->second == 1 ) {
140                     SG_LOG(SG_TERRAIN, SG_ALERT, "key " << std::hex << segIt->first << std::dec << " count is " << segIt->second );
141 
142                     unsigned int iStart = segIt->first >> 32;
143                     unsigned int iEnd   = segIt->first & 0x00000000FFFFFFFF;
144 
145                     SGBorderContour bc;
146 
147                     bc.start = toVec3d(toSG(vertices->operator[](iStart)));
148                     bc.end   = toVec3d(toSG(vertices->operator[](iEnd)));
149                     border.push_back( bc );
150                 }
151                 segIt++;
152             }
153 
154 #if 0
155             // debug out - requires GDAL
156             //
157             //
158             //
159             SGGeod  geodPos = SGGeod::fromCart(gbs_center);
160             SGQuatd hlOr = SGQuatd::fromLonLat(geodPos)*SGQuatd::fromEulerDeg(0, 0, 180);
161 
162             for ( unsigned int i=0; i<border.size(); i++ ){
163                 // de-rotate and translate : todo - create a paralell vertex list so we just do this
164                 // once per vertex, not for every triangle's use of the vertex
165                 SGVec3d sgVStart = hlOr.backTransform( border[i].start) + gbs_center;
166                 SGVec3d sgVEnd   = hlOr.backTransform( border[i].end)   + gbs_center;
167 
168                 // convert from cartesian to Geodetic, and save as a list of Geods for output
169                 SGGeod gStart = SGGeod::fromCart(sgVStart);
170                 SGGeod gEnd   = SGGeod::fromCart(sgVEnd);
171 
172                 SGShapefile::FromSegment( gStart, gEnd, true, "./borders", mat->get_names()[0], "border" );
173             }
174 #endif
175         }
176     }
177 
178     // Random buildings API - get num triangles, then get a triangle at index
getNumTriangles(void) const179     unsigned int getNumTriangles( void ) const {
180         unsigned int num_triangles = 0;
181 
182         if ( !geometries.empty() ) {
183             int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
184             if ( numPrimitiveSets > 0 ) {
185                 const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
186                 unsigned int numIndices = ps->getNumIndices();
187                 num_triangles = numIndices/3;
188             }
189         }
190 
191         return num_triangles;
192     }
193 
getTriangle(unsigned int i,std::vector<SGVec3f> & triVerts,std::vector<SGVec2f> & triTCs) const194     void getTriangle(unsigned int i, std::vector<SGVec3f>& triVerts, std::vector<SGVec2f>& triTCs) const {
195         const osg::Vec3Array* vertices  = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
196         const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0));
197 
198         if ( !geometries.empty() ) {
199             int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
200             if ( numPrimitiveSets > 0 ) {
201                 const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
202                 int idxStart = i*3;
203 
204                 triVerts.push_back( toSG(vertices->operator[](ps->index(idxStart+0))) );
205                 triVerts.push_back( toSG(vertices->operator[](ps->index(idxStart+1))) );
206                 triVerts.push_back( toSG(vertices->operator[](ps->index(idxStart+2))) );
207 
208                 triTCs.push_back( toSG(texcoords->operator[](ps->index(idxStart+0))) );
209                 triTCs.push_back( toSG(texcoords->operator[](ps->index(idxStart+1))) );
210                 triTCs.push_back( toSG(texcoords->operator[](ps->index(idxStart+2))) );
211             }
212         }
213     }
214 
215     // random lights and trees - just get a list of points on where to add the light / tree
216     // TODO move this out - and handle in the random light / tree code
217     // just use generic triangle API.
addRandomSurfacePoints(float coverage,float offset,osg::Texture2D * object_mask,std::vector<SGVec3f> & points)218     void addRandomSurfacePoints(float coverage, float offset,
219                                 osg::Texture2D* object_mask,
220                                 std::vector<SGVec3f>& points)
221     {
222         if ( !geometries.empty() ) {
223             const osg::Vec3Array* vertices  = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
224             const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0));
225 
226             int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
227             if ( numPrimitiveSets > 0 ) {
228                 const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
229                 unsigned int numIndices = ps->getNumIndices();
230 
231                 for ( unsigned int i=2; i<numIndices; i+= 3 ) {
232                     SGVec3f v0 = toSG(vertices->operator[](ps->index(i-2)));
233                     SGVec3f v1 = toSG(vertices->operator[](ps->index(i-1)));
234                     SGVec3f v2 = toSG(vertices->operator[](ps->index(i-0)));
235 
236                     SGVec2f t0 = toSG(texcoords->operator[](ps->index(i-2)));
237                     SGVec2f t1 = toSG(texcoords->operator[](ps->index(i-1)));
238                     SGVec2f t2 = toSG(texcoords->operator[](ps->index(i-0)));
239 
240                     SGVec3f normal = cross(v1 - v0, v2 - v0);
241 
242                     // Compute the area
243                     float area = 0.5f*length(normal);
244                     if (area <= SGLimitsf::min())
245                         continue;
246 
247                     // For partial units of area, use a zombie door method to
248                     // create the proper random chance of a light being created
249                     // for this triangle
250                     float unit = area + mt_rand(&seed)*coverage;
251 
252                     SGVec3f offsetVector = offset*normalize(normal);
253                     // generate a light point for each unit of area
254 
255                     while ( coverage < unit ) {
256                         float a = mt_rand(&seed);
257                         float b = mt_rand(&seed);
258 
259                         if ( a + b > 1 ) {
260                             a = 1 - a;
261                             b = 1 - b;
262                         }
263                         float c = 1 - a - b;
264                         SGVec3f randomPoint = offsetVector + a*v0 + b*v1 + c*v2;
265 
266                         if (object_mask != NULL) {
267                             SGVec2f texCoord = a*t0 + b*t1 + c*t2;
268 
269                             // Check this random point against the object mask
270                             // red channel.
271                             osg::Image* img = object_mask->getImage();
272                             unsigned int x = (int) (img->s() * texCoord.x()) % img->s();
273                             unsigned int y = (int) (img->t() * texCoord.y()) % img->t();
274 
275                             if (mt_rand(&seed) < img->getColor(x, y).r()) {
276                                 points.push_back(randomPoint);
277                             }
278                         } else {
279                             // No object mask, so simply place the object
280                             points.push_back(randomPoint);
281                         }
282                         unit -= coverage;
283                     }
284                 }
285             }
286         }
287     }
288 
addRandomTreePoints(float wood_coverage,osg::Texture2D * object_mask,float vegetation_density,float cos_max_density_angle,float cos_zero_density_angle,bool is_plantation,std::vector<SGVec3f> & points,std::vector<SGVec3f> & normals)289     void addRandomTreePoints(float wood_coverage,
290                              osg::Texture2D* object_mask,
291                              float vegetation_density,
292                              float cos_max_density_angle,
293                              float cos_zero_density_angle,
294                              bool is_plantation,
295                              std::vector<SGVec3f>& points,
296 			     std::vector<SGVec3f>& normals)
297     {
298         if ( !geometries.empty() ) {
299             const osg::Vec3Array* vertices  = dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
300             const osg::Vec2Array* texcoords = dynamic_cast<osg::Vec2Array*>(geometries[0]->getTexCoordArray(0));
301 
302             int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
303             if ( numPrimitiveSets > 0 ) {
304                 const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
305                 unsigned int numIndices = ps->getNumIndices();
306 
307                 for ( unsigned int i=2; i<numIndices; i+= 3 ) {
308                     SGVec3f v0 = toSG(vertices->operator[](ps->index(i-2)));
309                     SGVec3f v1 = toSG(vertices->operator[](ps->index(i-1)));
310                     SGVec3f v2 = toSG(vertices->operator[](ps->index(i-0)));
311 
312                     SGVec2f t0 = toSG(texcoords->operator[](ps->index(i-2)));
313                     SGVec2f t1 = toSG(texcoords->operator[](ps->index(i-1)));
314                     SGVec2f t2 = toSG(texcoords->operator[](ps->index(i-0)));
315 
316                     SGVec3f normal = cross(v1 - v0, v2 - v0);
317 
318                     // Ensure the slope isn't too steep by checking the
319                     // cos of the angle between the slope normal and the
320                     // vertical (conveniently the z-component of the normalized
321                     // normal) and values passed in.
322                     float alpha = normalize(normal).z();
323                     float slope_density = 1.0;
324 
325                     if (alpha < cos_zero_density_angle)
326                         continue; // Too steep for any vegetation
327 
328                     if (alpha < cos_max_density_angle) {
329                         slope_density =
330                         (alpha - cos_zero_density_angle) / (cos_max_density_angle - cos_zero_density_angle);
331                     }
332 
333                     // Compute the area
334                     float area = 0.5f*length(normal);
335                     if (area <= SGLimitsf::min())
336                         continue;
337 
338                     if (is_plantation)  {  // regularly-spaced vegetation
339                         // separate vegetation in integral 1m units
340                         int separation = (int) ceil(sqrt(wood_coverage));
341                         float max_x = ceil(max(max(v1.x(),v2.x()),v0.x()));
342                         float min_x = floor(min(min(v1.x(),v2.x()),v0.x()));
343                         float max_y = ceil(max(max(v1.y(),v2.y()),v0.y()));
344                         float min_y = floor(min(min(v1.y(),v2.y()),v0.y()));
345 
346                         /* equation of the plane ax+by+cz+d=0, need d */
347 
348                         float d = -1*(normal.x()*v0.x() + normal.y()*v0.y()+normal.z()*v0.z());
349                         /* Now loop over a grid, skipping points not in the triangle */
350                         int x_steps = (int) (max_x - min_x)/separation;
351                         int y_steps = (int) (max_y - min_y)/separation;
352                         SGVec2f v02d = SGVec2f(v0.x(),v0.y());
353                         SGVec2f v12d = SGVec2f(v1.x(),v1.y());
354                         SGVec2f v22d = SGVec2f(v2.x(),v2.y());
355 
356                         for (int jx = 0; jx < x_steps; jx++) {
357                             float ptx = min_x + jx * separation;
358 
359                             for (int jy = 0; jy < y_steps; jy++) {
360                                 float pty = min_y + jy * separation;
361                                 SGVec2f newpt = SGVec2f(ptx,pty);
362                                 if (!point_in_triangle(newpt,v02d,v12d,v22d))
363                                     continue;
364 
365                                 // z = (-ax-by-d)/c; c is not zero as
366                                 // that would be alpha of 1.0
367 
368                                 float ptz = (-normal.x()*ptx - normal.y()*pty-d)/normal.z();
369                                 SGVec3f randomPoint = SGVec3f(ptx,pty,ptz);
370 
371                                 if (object_mask != NULL) {
372                                     // Check this point against the object mask
373                                     // green (for trees) channel.
374                                     osg::Image* img = object_mask->getImage();
375                                     unsigned int x = (int) (img->s() * newpt.x()) % img->s();
376                                     unsigned int y = (int) (img->t() * newpt.y()) % img->t();
377 
378                                     if (mt_rand(&seed) < img->getColor(x, y).g()) {
379                                         // The red channel contains the rotation for this object
380                                         points.push_back(randomPoint);
381                                         normals.push_back(normalize(normal));
382                                     }
383                                 } else {
384                                     points.push_back(randomPoint);
385                                     normals.push_back(normalize(normal));
386                                 }
387                             }
388                         }
389                     }  else  {
390                         // Determine the number of trees, taking into account vegetation
391                         // density (which is linear) and the slope density factor.
392                         // Use a zombie door method to create the proper random chance
393                         // of a tree being created for partial values.
394                         int woodcount = (int) (vegetation_density * vegetation_density *
395                                                slope_density *
396                                                area / wood_coverage + mt_rand(&seed));
397                         for (int j = 0; j < woodcount; j++) {
398                             // Use barycentric coordinates
399                             float a = mt_rand(&seed);
400                             float b = mt_rand(&seed);
401 
402                             if ( a + b > 1.0f ) {
403                                 a = 1.0f - a;
404                                 b = 1.0f - b;
405                             }
406 
407                             float c = 1.0f - a - b;
408 
409                             SGVec3f randomPoint = a*v0 + b*v1 + c*v2;
410                             if (object_mask != NULL) {
411                                 SGVec2f texCoord = a*t0 + b*t1 + c*t2;
412 
413                                 // Check this random point against the object mask
414                                 // green (for trees) channel.
415                                 osg::Image* img = object_mask->getImage();
416                                 unsigned int x = (int) (img->s() * texCoord.x()) % img->s();
417                                 unsigned int y = (int) (img->t() * texCoord.y()) % img->t();
418 
419                                 if (mt_rand(&seed) < img->getColor(x, y).g()) {
420                                     // The red channel contains the rotation for this object
421                                     points.push_back(randomPoint);
422                                     normals.push_back(normalize(normal));
423                                 }
424                             } else {
425                                 points.push_back(randomPoint);
426                                 normals.push_back(normalize(normal));
427 
428                             }
429                         }
430                     }
431                 }
432             }
433         }
434     }
435 
436 #if 0
437     // debug : this will save the tile as a shapefile that can be viewed in QGIS.
438     // NOTE: this is really slow....
439     // remember - we need to de-rotate the tile, then translate back to gbs_center.
440     void dumpBorder() {
441         //dump the first triangle only of the first geometry, for now...
442         SG_LOG(SG_TERRAIN, SG_ALERT, "effect geode has " << geometries.size() << " geometries" );
443 
444         const osg::Vec3Array* vertices =  dynamic_cast<osg::Vec3Array*>(geometries[0]->getVertexArray());
445         if ( vertices ) {
446             SG_LOG(SG_TERRAIN, SG_ALERT, " geometry has " << vertices->getNumElements() << " vertices" );
447         }
448 
449         if ( !geometries.empty() ) {
450             int numPrimitiveSets = geometries[0]->getNumPrimitiveSets();
451             SG_LOG(SG_TERRAIN, SG_ALERT, " geometry has " << numPrimitiveSets << " primitive sets" );
452 
453             if ( numPrimitiveSets > 0 ) {
454                 const osg::PrimitiveSet* ps = geometries[0]->getPrimitiveSet(0);
455                 unsigned int numIndices = ps->getNumIndices();
456 
457                 // create the same quat we used to rotate here
458                 // - use backTransform to go back to original node location
459                 SGGeod  geodPos = SGGeod::fromCart(gbs_center);
460                 SGQuatd hlOr = SGQuatd::fromLonLat(geodPos)*SGQuatd::fromEulerDeg(0, 0, 180);
461 
462                 SG_LOG(SG_TERRAIN, SG_ALERT, "  primitive set has has " << numIndices << " indices" );
463                 for ( unsigned int i=2; i<numIndices; i+= 3 ) {
464                     if ( numIndices >= 3 ) {
465                         unsigned int v0i = ps->index(i-2);
466                         unsigned int v1i = ps->index(i-1);
467                         unsigned int v2i = ps->index(i-0);
468 
469                         const osg::Vec3 *v0 = &vertices->operator[](v0i);
470                         const osg::Vec3 *v1 = &vertices->operator[](v1i);
471                         const osg::Vec3 *v2 = &vertices->operator[](v2i);
472 
473                         // de-rotate and translate : todo - create a paralell vertex list so we just do this
474                         // once per vertex, not for every triangle's use of the vertex
475                         SGVec3d vec0 = hlOr.backTransform( toVec3d(toSG(*v0))) + gbs_center;
476                         SGVec3d vec1 = hlOr.backTransform( toVec3d(toSG(*v1))) + gbs_center;
477                         SGVec3d vec2 = hlOr.backTransform( toVec3d(toSG(*v2))) + gbs_center;
478 
479                         // convert from cartesian to Geodetic, and save as a list of Geods for output
480                         std::vector<SGGeod> triangle;
481                         triangle.push_back( SGGeod::fromCart(vec0) );
482                         triangle.push_back( SGGeod::fromCart(vec1) );
483                         triangle.push_back( SGGeod::fromCart(vec2) );
484 
485                         SGShapefile::FromGeodList( triangle, true, "./triangles", mat->get_names()[0], "tri" );
486                     }
487                 }
488             }
489 
490         }
491     }
492 #endif
493 
494 private:
495     mt seed;
496     SGMaterial* mat;
497     SGVec3d gbs_center;
498     std::vector<osg::Geometry*> geometries;
499     std::vector<int> polygon_border;    // TODO
500 };
501 
502 // This visitor will generate an SGTriangleInfo.
503 // currently, it looks like it could save multiple lists, which could be the case
504 // if multiple osg::geods are found with osg::Geometry.
505 // But right now, we store a single PrimitiveSet under a single EffectGeod.
506 // so the traversal should only find a single EffectGeod - building a single SGTriangleInfo
507 class GetNodeTriangles : public osg::NodeVisitor
508 {
509 public:
GetNodeTriangles(const SGVec3d & c,std::vector<SGTriangleInfo> * nt)510     GetNodeTriangles(const SGVec3d& c, std::vector<SGTriangleInfo>* nt) : osg::NodeVisitor( osg::NodeVisitor::TRAVERSE_ALL_CHILDREN ), center(c), nodeTris(nt) {}
511 
512     // This method gets called for every node in the scene
513     //   graph. Check each node to see if it has user
514     //   out target. If so, save the node's address.
apply(osg::Node & node)515     virtual void apply( osg::Node& node )
516     {
517         EffectGeode* eg = dynamic_cast<EffectGeode*>(&node);
518         if ( eg ) {
519             // get the material from the user info
520             SGTriangleInfo triInfo( center );
521             triInfo.setMaterial( eg->getMaterial() );
522 
523             // let's find the drawables for this node
524             int numDrawables = eg->getNumDrawables();
525             for ( int i=0; i<numDrawables; i++ ) {
526                 triInfo.addGeometry( eg->getDrawable(i)->asGeometry() );
527             }
528 
529             nodeTris->push_back( triInfo );
530         }
531 
532         // Keep traversing the rest of the scene graph.
533         traverse( node );
534     }
535 
536 protected:
537     SGVec3d                         center;
538     std::vector<SGTriangleInfo>*    nodeTris;
539 };
540