1
2 #include <stdlib.h>
3 #include <osg/Geode>
4 #include <osgUtil/TriStripVisitor>
5
6 #include "VBSPGeometry.h"
7
8
9 using namespace osg;
10 using namespace bsp;
11
12
VBSPGeometry(VBSPData * bspData)13 VBSPGeometry::VBSPGeometry(VBSPData * bspData)
14 {
15 // Keep track of the bsp data, as it has all of the data lists that we
16 // need
17 bsp_data = bspData;
18
19 // Create arrays for the vertex attributes
20 vertex_array = new Vec3Array();
21 normal_array = new Vec3Array();
22 texcoord_array = new Vec2Array();
23
24 // Create a primitive set for drawing variable length primitives (VBSP
25 // primitives are only guaranteed to be convex polygons)
26 primitive_set = new DrawArrayLengths(PrimitiveSet::POLYGON);
27
28 // Create a second set of arrays for displacement surfaces
29 disp_vertex_array = new Vec3Array();
30 disp_normal_array = new Vec3Array();
31 disp_texcoord_array = new Vec2Array();
32 disp_vertex_attr_array = new Vec4Array();
33
34 // Create a second primitive set for drawing indexed triangles, which is
35 // the quickest method for drawing the displacement surfaces
36 disp_primitive_set = new DrawElementsUInt(PrimitiveSet::TRIANGLES);
37 }
38
39
~VBSPGeometry()40 VBSPGeometry::~VBSPGeometry()
41 {
42 }
43
44
doesEdgeExist(int row,int col,int direction,int vertsPerEdge)45 bool VBSPGeometry::doesEdgeExist(int row, int col, int direction,
46 int vertsPerEdge)
47 {
48 // See if there is an edge on the displacement surface from the given
49 // vertex in the given direction (we only need to know the vertices
50 // indices, because all displacement surfaces are tessellated in the
51 // same way)
52 switch (direction)
53 {
54 case 0:
55 // False if we're on the left edge, otherwise true
56 if ((row - 1) < 0)
57 return false;
58 else
59 return true;
60
61 case 1:
62 // False if we're on the top edge, otherwise true
63 if ((col + 1) >= vertsPerEdge)
64 return false;
65 else
66 return true;
67
68 case 2:
69 // False if we're on the right edge, otherwise true
70 if ((row + 1) >= vertsPerEdge)
71 return false;
72 else
73 return true;
74
75 case 3:
76 // False if we're on the bottom edge, otherwise true
77 if ((col - 1) < 0)
78 return false;
79 else
80 return true;
81
82 default:
83 return false;
84 }
85 }
86
87
getNormalFromEdges(int row,int col,unsigned char edgeBits,int firstVertex,int vertsPerEdge)88 osg::Vec3 VBSPGeometry::getNormalFromEdges(int row, int col,
89 unsigned char edgeBits,
90 int firstVertex, int vertsPerEdge)
91 {
92 osg::Vec3 * vertexData;
93 osg::Vec3 * surfaceVerts;
94 osg::Vec3 finalNormal;
95 osg::Vec3 v1, v2, v3;
96 osg::Vec3 e1, e2;
97 osg::Vec3 tempNormal;
98 int normalCount;
99
100 // Constants for direction. If the bit is set in the edgeBits, then
101 // there is an edge connected to the current vertex in that direction
102 const unsigned char NEG_X = 1 << 0;
103 const unsigned char POS_Y = 1 << 1;
104 const unsigned char POS_X = 1 << 2;
105 const unsigned char NEG_Y = 1 << 3;
106
107 // Constants for quadrants. If both bits are set, then there are
108 // exactly two triangles in that quadrant
109 const unsigned char QUAD_1 = POS_X | POS_Y;
110 const unsigned char QUAD_2 = NEG_X | POS_Y;
111 const unsigned char QUAD_3 = NEG_X | NEG_Y;
112 const unsigned char QUAD_4 = POS_X | NEG_Y;
113
114
115 // Grab the vertex data from the displaced vertex array (if there's a
116 // better way to randomly access the data in this array, I'm all ears)
117 vertexData = (osg::Vec3 *)disp_vertex_array->getDataPointer();
118
119 // Move to the surface we're interested in, and start counting vertices
120 // from there
121 surfaceVerts = &vertexData[firstVertex];
122
123 // Start with no normals computed
124 finalNormal.set(0.0, 0.0, 0.0);
125 normalCount = 0;
126
127 // The process is fairly simple. For all four quadrants surrounding
128 // the vertex, check each quadrant to see if there are triangles there.
129 // If so, calculate the normals of the two triangles in that quadrant, and
130 // add them to the final normal. When fininshed, scale the final normal
131 // based on the number of contributing triangle normals
132
133 // Check quadrant 1 (+X,+Y)
134 if ((edgeBits & QUAD_1) == QUAD_1)
135 {
136 // First triangle
137 v1 = surfaceVerts[(col+1) * vertsPerEdge + row];
138 v2 = surfaceVerts[col * vertsPerEdge + row];
139 v3 = surfaceVerts[col * vertsPerEdge + (row+1)];
140 e1 = v1 - v2;
141 e2 = v3 - v2;
142 tempNormal = e2 ^ e1;
143 tempNormal.normalize();
144 finalNormal += tempNormal;
145 normalCount++;
146
147 // Second triangle
148 v1 = surfaceVerts[(col+1) * vertsPerEdge + row];
149 v2 = surfaceVerts[col * vertsPerEdge + (row+1)];
150 v3 = surfaceVerts[(col+1) * vertsPerEdge + (row+1)];
151 e1 = v1 - v2;
152 e2 = v3 - v2;
153 tempNormal = e2 ^ e1;
154 tempNormal.normalize();
155 finalNormal += tempNormal;
156 normalCount++;
157 }
158
159 // Check quadrant 2 (-X,+Y)
160 if ((edgeBits & QUAD_2) == QUAD_2)
161 {
162 // First triangle
163 v1 = surfaceVerts[(col+1) * vertsPerEdge + (row-1)];
164 v2 = surfaceVerts[col * vertsPerEdge + (row-1)];
165 v3 = surfaceVerts[col * vertsPerEdge + row];
166 e1 = v1 - v2;
167 e2 = v3 - v2;
168 tempNormal = e2 ^ e1;
169 tempNormal.normalize();
170 finalNormal += tempNormal;
171 normalCount++;
172
173 // Second triangle
174 v1 = surfaceVerts[(col+1) * vertsPerEdge + (row-1)];
175 v2 = surfaceVerts[col * vertsPerEdge + row];
176 v3 = surfaceVerts[(col+1) * vertsPerEdge + row];
177 e1 = v1 - v2;
178 e2 = v3 - v2;
179 tempNormal = e2 ^ e1;
180 tempNormal.normalize();
181 finalNormal += tempNormal;
182 normalCount++;
183 }
184
185 // Check quadrant 3 (-X,-Y)
186 if ((edgeBits & QUAD_3) == QUAD_3)
187 {
188 // First triangle
189 v1 = surfaceVerts[col * vertsPerEdge + (row-1)];
190 v2 = surfaceVerts[(col-1) * vertsPerEdge + (row-1)];
191 v3 = surfaceVerts[(col-1) * vertsPerEdge + row];
192 e1 = v1 - v2;
193 e2 = v3 - v2;
194 tempNormal = e2 ^ e1;
195 tempNormal.normalize();
196 finalNormal += tempNormal;
197 normalCount++;
198
199 // Second triangle
200 v1 = surfaceVerts[col * vertsPerEdge + (row-1)];
201 v2 = surfaceVerts[(col-1) * vertsPerEdge + row];
202 v3 = surfaceVerts[col * vertsPerEdge + row];
203 e1 = v1 - v2;
204 e2 = v3 - v2;
205 tempNormal = e2 ^ e1;
206 tempNormal.normalize();
207 finalNormal += tempNormal;
208 normalCount++;
209 }
210
211 // Check quadrant 4 (+X,-Y)
212 if ((edgeBits & QUAD_4) == QUAD_4)
213 {
214 // First triangle
215 v1 = surfaceVerts[col * vertsPerEdge + row];
216 v2 = surfaceVerts[(col-1) * vertsPerEdge + row];
217 v3 = surfaceVerts[(col-1) * vertsPerEdge + (row+1)];
218 e1 = v1 - v2;
219 e2 = v3 - v2;
220 tempNormal = e2 ^ e1;
221 tempNormal.normalize();
222 finalNormal += tempNormal;
223 normalCount++;
224
225 // Second triangle
226 v1 = surfaceVerts[col * vertsPerEdge + row];
227 v2 = surfaceVerts[(col-1) * vertsPerEdge + (row+1)];
228 v3 = surfaceVerts[col * vertsPerEdge + (row+1)];
229 e1 = v1 - v2;
230 e2 = v3 - v2;
231 tempNormal = e2 ^ e1;
232 tempNormal.normalize();
233 finalNormal += tempNormal;
234 normalCount++;
235 }
236
237 // Scale the final normal according to how many triangle normals are
238 // contributing
239 finalNormal *= (1.0f / (float)normalCount);
240
241 return finalNormal;
242 }
243
244
createDispSurface(Face & face,DisplaceInfo & dispInfo)245 void VBSPGeometry::createDispSurface(Face & face, DisplaceInfo & dispInfo)
246 {
247 TexInfo currentTexInfo;
248 TexData currentTexData;
249 Vec3 texU;
250 float texUOffset;
251 float texUScale;
252 Vec3 texV;
253 float texVOffset;
254 float texVScale;
255 int i, j;
256 unsigned int k;
257 double dist, minDist;
258 int minIndex = 0;
259 osg::Vec3 temp;
260 int edgeIndex;
261 int currentSurfEdge;
262 Edge currentEdge;
263 osg::Vec3 currentVertex;
264 osg::Vec3 vertices[4];
265 unsigned int firstVertex;
266 int numEdgeVertices;
267 double subdivideScale;
268 osg::Vec3 leftEdge, rightEdge;
269 osg::Vec3 leftEdgeStep, rightEdgeStep;
270 osg::Vec3 leftEnd, rightEnd;
271 osg::Vec3 leftRightSeg, leftRightStep;
272 unsigned int dispVertIndex;
273 DisplacedVertex dispVertInfo;
274 osg::Vec3 flatVertex, dispVertex;
275 unsigned int index;
276 osg::Vec3 normal;
277 float u, v;
278 osg::Vec2 texCoord;
279 float alphaBlend;
280 unsigned char edgeBits;
281
282
283 // Get the texture info for this face
284 currentTexInfo = bsp_data->getTexInfo(face.texinfo_index);
285 currentTexData = bsp_data->getTexData(currentTexInfo.texdata_index);
286
287 // Get the texture vectors and offsets. These are used to calculate
288 // texture coordinates
289 texU.set(currentTexInfo.texture_vecs[0][0],
290 currentTexInfo.texture_vecs[0][1],
291 currentTexInfo.texture_vecs[0][2]);
292 texUOffset = currentTexInfo.texture_vecs[0][3];
293 texV.set(currentTexInfo.texture_vecs[1][0],
294 currentTexInfo.texture_vecs[1][1],
295 currentTexInfo.texture_vecs[1][2]);
296 texVOffset = currentTexInfo.texture_vecs[1][3];
297
298 // Scale the texture vectors from inches to meters
299 texU *= 39.37f;
300 texV *= 39.37f;
301
302 // Get the size of the texture involved, as the planar texture projection
303 // assumes non-normalized texture coordinates
304 texUScale = 1.0f / (float)currentTexData.texture_width;
305 texVScale = 1.0f / (float)currentTexData.texture_height;
306
307 // Get the first edge index
308 edgeIndex = face.first_edge;
309
310 // Get the base vertices for this face
311 for (i = 0; i < face.num_edges; i++)
312 {
313 // Look up the edge specified by the surface edge index, the
314 // index might be negative (see below), so take the absolute
315 // value
316 currentSurfEdge = bsp_data->getSurfaceEdge(edgeIndex);
317 currentEdge = bsp_data->getEdge(abs(currentSurfEdge));
318
319 // The sign of the surface edge index specifies which vertex is
320 // "first" for this face. A negative index means the edge should
321 // be flipped, and the second vertex treated as the first
322 if (currentSurfEdge < 0)
323 currentVertex = bsp_data->getVertex(currentEdge.vertex[1]);
324 else
325 currentVertex = bsp_data->getVertex(currentEdge.vertex[0]);
326
327 // Add the vertex to the array
328 vertices[i] = currentVertex;
329
330 // Move on to the next vertex
331 edgeIndex++;
332 }
333
334 // Rotate the base coordinates for the surface until the first vertex
335 // matches the start position
336 minDist = 1.0e9;
337 for (i = 0; i < 4; i++)
338 {
339 // Calculate the distance of the start position from this vertex
340 dist = (vertices[i] - dispInfo.start_position * 0.0254f).length();
341
342 // If this is the smallest distance we've seen, remember it
343 if (dist < minDist)
344 {
345 minDist = dist;
346 minIndex = i;
347 }
348 }
349
350 // Rotate the displacement surface quad until we get the starting vertex
351 // in the 0th position
352 for (i = 0; i < minIndex; i++)
353 {
354 temp = vertices[0];
355 vertices[0] = vertices[1];
356 vertices[1] = vertices[2];
357 vertices[2] = vertices[3];
358 vertices[3] = temp;
359 }
360
361 // Calculate the vectors for the left and right edges of the surface
362 // (remembering that the surface is wound clockwise)
363 leftEdge = vertices[1] - vertices[0];
364 rightEdge = vertices[2] - vertices[3];
365
366 // Calculate the number of vertices along each edge of the surface
367 numEdgeVertices = (1 << dispInfo.power) + 1;
368
369 // Calculate the subdivide scale, which will tell us how far apart to
370 // put each vertex (relative to the length of the surface's edges)
371 subdivideScale = 1.0 / (double)(numEdgeVertices - 1);
372
373 // Calculate the step size between vertices on the left and right edges
374 leftEdgeStep = leftEdge * subdivideScale;
375 rightEdgeStep = rightEdge * subdivideScale;
376
377 // Remember the first vertex index in the vertex array
378 firstVertex = disp_vertex_array->size();
379
380 // Generate the displaced vertices (this technique comes from the
381 // Source SDK)
382 for (i = 0; i < numEdgeVertices; i++)
383 {
384 // Calculate the two endpoints for this section of the surface
385 leftEnd = leftEdgeStep * (double) i;
386 leftEnd += vertices[0];
387 rightEnd = rightEdgeStep * (double) i;
388 rightEnd += vertices[3];
389
390 // Now, get the vector from left to right, and subdivide it as well
391 leftRightSeg = rightEnd - leftEnd;
392 leftRightStep = leftRightSeg * subdivideScale;
393
394 // Generate the vertices for this section
395 for (j = 0; j < numEdgeVertices; j++)
396 {
397 // Get the displacement info for this vertex
398 dispVertIndex = dispInfo.disp_vert_start;
399 dispVertIndex += i * numEdgeVertices + j;
400 dispVertInfo = bsp_data->getDispVertex(dispVertIndex);
401
402 // Calculate the flat vertex
403 flatVertex = leftEnd + (leftRightStep * (double) j);
404
405 // Calculate the displaced vertex
406 dispVertex =
407 dispVertInfo.displace_vec *
408 (dispVertInfo.displace_dist * 0.0254);
409 dispVertex += flatVertex;
410
411 // Add the vertex to the displaced vertex array
412 disp_vertex_array->push_back(dispVertex);
413
414 // Calculate the texture coordinates for this vertex. Texture
415 // coordinates are calculated using a planar projection, so we need
416 // to use the non-displaced vertex position here
417 u = texU * flatVertex + texUOffset;
418 u *= texUScale;
419 v = texV * flatVertex + texVOffset;
420 v *= texVScale;
421 texCoord.set(u, v);
422
423 // Add the texture coordinate to the array
424 disp_texcoord_array->push_back(texCoord);
425
426 // Get the texture blend parameter for this vertex as well, and
427 // assign it as the alpha channel for the primary vertex color.
428 // We'll use a combiner operation to do the texture blending
429 alphaBlend = dispVertInfo.alpha_blend / 255.0;
430 disp_vertex_attr_array->push_back(
431 osg::Vec4f(1.0, 1.0, 1.0, 1.0 - alphaBlend));
432 }
433 }
434
435 // Calculate normals at each vertex (this is adapted from the Source SDK,
436 // including the two helper functions)
437 for (i = 0; i < numEdgeVertices; i++)
438 {
439 for (j = 0; j < numEdgeVertices; j++)
440 {
441 // See which of the 4 possible edges (left, up, right, or down) are
442 // incident on this vertex
443 edgeBits = 0;
444 for (k = 0; k < 4; k++)
445 {
446 if (doesEdgeExist(j, i, k, numEdgeVertices))
447 edgeBits |= 1 << k;
448 }
449
450 // Calculate the normal based on the adjacent edges
451 normal = getNormalFromEdges(j, i, edgeBits, firstVertex,
452 numEdgeVertices);
453
454 // Add the normal to the normal array
455 disp_normal_array->push_back(normal);
456 }
457 }
458
459 // Now, triangulate the surface (this technique comes from the Source SDK)
460 for (i = 0; i < numEdgeVertices-1; i++)
461 {
462 for (j = 0; j < numEdgeVertices-1; j++)
463 {
464 // Get the current vertex index (local to this surface)
465 index = i * numEdgeVertices + j;
466
467 // See if this index is odd
468 if ((index % 2) == 1)
469 {
470 // Add the vertex offset (so we reference this surface's
471 // vertices in the array)
472 index += firstVertex;
473
474 // Create two triangles on this vertex from top-left to
475 // bottom-right
476 disp_primitive_set->push_back(index);
477 disp_primitive_set->push_back(index + 1);
478 disp_primitive_set->push_back(index + numEdgeVertices);
479 disp_primitive_set->push_back(index + 1);
480 disp_primitive_set->push_back(index + numEdgeVertices + 1);
481 disp_primitive_set->push_back(index + numEdgeVertices);
482 }
483 else
484 {
485 // Add the vertex offset (so we reference this surface's
486 // vertices in the array)
487 index += firstVertex;
488
489 // Create two triangles on this vertex from bottom-left to
490 // top-right
491 disp_primitive_set->push_back(index);
492 disp_primitive_set->push_back(index + numEdgeVertices + 1);
493 disp_primitive_set->push_back(index + numEdgeVertices);
494 disp_primitive_set->push_back(index);
495 disp_primitive_set->push_back(index + 1);
496 disp_primitive_set->push_back(index + numEdgeVertices + 1);
497 }
498 }
499 }
500 }
501
502
addFace(int faceIndex)503 void VBSPGeometry::addFace(int faceIndex)
504 {
505 Face currentFace;
506 Edge currentEdge;
507 DisplaceInfo currentDispInfo;
508 TexInfo currentTexInfo;
509 TexData currentTexData;
510 Vec3 normal;
511 int edgeIndex;
512 int i;
513 int currentSurfEdge;
514 Vec3 currentVertex;
515 Vec3 texU;
516 float texUOffset;
517 float texUScale;
518 Vec3 texV;
519 float texVOffset;
520 float texVScale;
521 float u, v;
522 Vec2f texCoord;
523
524 // Make sure this face is not "on node" (an internal node of the BSP tree).
525 // These faces are not used for visible geometry
526 currentFace = bsp_data->getFace(faceIndex);
527
528 // See if this is a displacement surface
529 if (currentFace.dispinfo_index != -1)
530 {
531 // Get the displacement info
532 currentDispInfo =
533 bsp_data->getDispInfo(currentFace.dispinfo_index);
534
535 // Generate the displacement surface
536 createDispSurface(currentFace, currentDispInfo);
537 }
538 else
539 {
540 // Get the face normal, using the plane information
541 normal = bsp_data->getPlane(currentFace.plane_index).plane_normal;
542 if (currentFace.plane_side != 0)
543 normal = -normal;
544
545 // Get the texture info and data structures
546 currentTexInfo = bsp_data->getTexInfo(currentFace.texinfo_index);
547 currentTexData = bsp_data->getTexData(currentTexInfo.texdata_index);
548
549 // Get the texture vectors and offsets. These are used to calculate
550 // texture coordinates
551 texU.set(currentTexInfo.texture_vecs[0][0],
552 currentTexInfo.texture_vecs[0][1],
553 currentTexInfo.texture_vecs[0][2]);
554 texUOffset = currentTexInfo.texture_vecs[0][3];
555 texV.set(currentTexInfo.texture_vecs[1][0],
556 currentTexInfo.texture_vecs[1][1],
557 currentTexInfo.texture_vecs[1][2]);
558 texVOffset = currentTexInfo.texture_vecs[1][3];
559
560 // Scale the texture vectors from inches to meters
561 texU *= 39.37f;
562 texV *= 39.37f;
563
564 // Get the texture size, as the planar texture projection results in
565 // non-normalized texture coordinates
566 texUScale = 1.0f / (float)currentTexData.texture_width;
567 texVScale = 1.0f / (float)currentTexData.texture_height;
568
569 // Start with the last edge index, because we need to switch from
570 // clockwise winding (DirectX) to counter-clockwise winding (OpenGL)
571 edgeIndex = currentFace.first_edge + currentFace.num_edges - 1;
572
573 // Set the length of this primitive on the primitive set
574 primitive_set->push_back(currentFace.num_edges);
575
576 // Iterate over the edges in this face, and extract the vertex data
577 for (i = 0; i < currentFace.num_edges; i++)
578 {
579 // Look up the edge specified by the surface edge index, the
580 // index might be negative (see below), so take the absolute
581 // value
582 currentSurfEdge = bsp_data->getSurfaceEdge(edgeIndex);
583 currentEdge = bsp_data->getEdge(abs(currentSurfEdge));
584
585 // The sign of the surface edge index specifies which vertex is
586 // "first" for this face. A negative index means the edge should
587 // be flipped, and the second vertex treated as the first
588 if (currentSurfEdge < 0)
589 currentVertex = bsp_data->getVertex(currentEdge.vertex[1]);
590 else
591 currentVertex = bsp_data->getVertex(currentEdge.vertex[0]);
592
593 // Add the vertex to the array
594 vertex_array->push_back(currentVertex);
595
596 // Set the normal
597 normal_array->push_back(normal);
598
599 // Calculate the texture coordinates for this vertex
600 u = texU * currentVertex + texUOffset;
601 u *= texUScale;
602 v = texV * currentVertex + texVOffset;
603 v *= texVScale;
604 texCoord.set(u, v);
605
606 // Add the texture coordinate to the array
607 texcoord_array->push_back(texCoord);
608
609 // Move on to the next (previous?) vertex
610 edgeIndex--;
611 }
612 }
613 }
614
615
createGeometry()616 ref_ptr<Group> VBSPGeometry::createGeometry()
617 {
618 ref_ptr<Group> rootGroup;
619 ref_ptr<Geode> geode;
620 ref_ptr<Geometry> geometry;
621 Vec4f color;
622 ref_ptr<Vec4Array> colorArray;
623
624 // Create the root group (we'll attach everything to this group and
625 // return it)
626 rootGroup = new Group();
627
628 // Create a geode for the geometries
629 geode = new Geode();
630 rootGroup->addChild(geode.get());
631
632 // See if there are any regular (non-displaced) faces to render
633 if (primitive_set->size() > 0)
634 {
635 // Create a geometry object for the regular surfaces
636 geometry = new Geometry();
637
638 // Add the vertex attributes
639 geometry->setVertexArray(vertex_array.get());
640 geometry->setNormalArray(normal_array.get(), Array::BIND_PER_VERTEX);
641 geometry->setTexCoordArray(0, texcoord_array.get());
642
643 // Add an overall color
644 color.set(1.0, 1.0, 1.0, 1.0);
645 colorArray = new Vec4Array(1, &color);
646 geometry->setColorArray(colorArray.get(), Array::BIND_OVERALL);
647
648 // Add our primitive set to the geometry
649 geometry->addPrimitiveSet(primitive_set.get());
650
651 // Add the geometry to the geode
652 geode->addDrawable(geometry.get());
653
654 // Now, stripify the geode to convert the POLYGON primitives to
655 // triangle strips
656 osgUtil::TriStripVisitor tsv;
657 geode->accept(tsv);
658 tsv.stripify();
659 }
660
661 // Now do the same for the displacement surfaces (if any)
662 if (disp_primitive_set->size() > 0)
663 {
664 // Create a geometry object for the regular surfaces
665 geometry = new Geometry();
666
667 // Add the vertex attributes
668 geometry->setVertexArray(disp_vertex_array.get());
669 geometry->setNormalArray(disp_normal_array.get(), Array::BIND_PER_VERTEX);
670 geometry->setColorArray(disp_vertex_attr_array.get(), Array::BIND_PER_VERTEX);
671 geometry->setTexCoordArray(0, disp_texcoord_array.get());
672 geometry->setTexCoordArray(1, disp_texcoord_array.get());
673
674 // Add our primitive set to the geometry
675 geometry->addPrimitiveSet(disp_primitive_set.get());
676
677 // Add the geometry to the geode
678 geode->addDrawable(geometry.get());
679 }
680
681 // Return the root group
682 return rootGroup;
683 }
684
685