source: trunk/packages/vizservers/nanovis/HeightMap.cpp @ 2376

Last change on this file since 2376 was 2376, checked in by gah, 13 years ago
  • Property svn:eol-style set to native
File size: 18.8 KB
Line 
1 
2#include <memory.h>
3#include <stdlib.h>
4#include <sys/time.h>
5#include <sys/types.h>
6#include <unistd.h>
7#include <fcntl.h>
8#include <stdlib.h>
9#include <GL/glew.h>
10#include <GL/gl.h>
11#include "Grid.h"
12#include "HeightMap.h"
13#include "ContourLineFilter.h"
14#include "TypeDefs.h"
15#include "Texture1D.h"
16#include "R2/R2FilePath.h"
17#include "RpField1D.h"
18#include "RenderContext.h"
19
20bool HeightMap::update_pending = false;
21double HeightMap::valueMin = 0.0;
22double HeightMap::valueMax = 1.0;
23
24#define TOPCONTOUR      0
25//#define TOPCONTOUR    1
26HeightMap::HeightMap() :
27    _vertexBufferObjectID(0),
28    _textureBufferObjectID(0),
29    _vertexCount(0),
30    _contour(0),
31    _topContour(0),
32    _tfPtr(0),
33    _opacity(0.5f),
34    _indexBuffer(0),
35    _indexCount(0),
36    _contourColor(1.0f, 0.0f, 0.0f),
37    _contourVisible(false),
38    _topContourVisible(true),
39    _visible(false),
40    _scale(1.0f, 1.0f, 1.0f),
41    _centerPoint(0.0f, 0.0f, 0.0f),
42    _heights(NULL)
43{
44    _shader = new NvShader();
45    _shader->loadFragmentProgram("heightcolor.cg", "main");
46    _tfParam      = _shader->getNamedParameterFromFP("tf");
47    _opacityParam = _shader->getNamedParameterFromFP("opacity");
48}
49
50HeightMap::~HeightMap()
51{
52    reset();
53
54    if (_shader) {
55        delete _shader;
56    }
57    if (_heights != NULL) {
58        free(_heights);
59    }
60}
61
62void
63HeightMap::render(graphics::RenderContext* renderContext)
64{
65    if (renderContext->getCullMode() == graphics::RenderContext::NO_CULL) {
66        glDisable(GL_CULL_FACE);
67    } else {
68        glEnable(GL_CULL_FACE);
69        glCullFace((GLuint) renderContext->getCullMode());
70    }
71    glPolygonMode(GL_FRONT_AND_BACK, (GLuint) renderContext->getPolygonMode());
72    glShadeModel((GLuint) renderContext->getShadingModel());
73
74    glPushMatrix();
75
76#ifndef notdef
77    if (_scale.x != 0.0) {
78        glScalef(1 / _scale.x, 1 / _scale.y , 1 / _scale.z);
79    }
80#endif
81    glTranslatef(-_centerPoint.x, -_centerPoint.y, -_centerPoint.z);
82
83    if (_contour != NULL) {
84        glDepthRange (0.001, 1.0);
85    }
86       
87    glEnable(GL_DEPTH_TEST);
88
89    if (_vertexBufferObjectID) {
90        glColor3f(1.0f, 1.0f, 1.0f);
91        glShadeModel(GL_SMOOTH);
92        glEnable(GL_BLEND);
93        glEnableClientState(GL_VERTEX_ARRAY);
94        glDisableClientState(GL_COLOR_ARRAY);
95        glDisableClientState(GL_INDEX_ARRAY);
96        glDisableClientState(GL_NORMAL_ARRAY);
97       
98        if (_tfPtr) {
99            // PUT vertex program here
100            //
101            //
102           
103            cgGLBindProgram(_shader->getFP());
104            cgGLEnableProfile(CG_PROFILE_FP30);
105           
106            cgGLSetTextureParameter(_tfParam, _tfPtr->id());
107            cgGLEnableTextureParameter(_tfParam);
108            cgGLSetParameter1f(_opacityParam, _opacity);
109           
110            glEnable(GL_TEXTURE_1D);
111            _tfPtr->getTexture()->activate();
112           
113            glEnableClientState(GL_TEXTURE_COORD_ARRAY);
114        }
115       
116        glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferObjectID);
117        glVertexPointer(3, GL_FLOAT, 12, 0);
118       
119        glBindBuffer(GL_ARRAY_BUFFER, _textureBufferObjectID);
120        ::glTexCoordPointer(3, GL_FLOAT, 12, 0);
121       
122#define _TRIANGLES_
123#ifdef _TRIANGLES_
124        glDrawElements(GL_TRIANGLES, _indexCount, GL_UNSIGNED_INT,
125                       _indexBuffer);
126#else                   
127        glDrawElements(GL_QUADS, _indexCount, GL_UNSIGNED_INT,
128                       _indexBuffer);
129#endif
130
131        glBindBuffer(GL_ARRAY_BUFFER, 0);
132       
133        glDisableClientState(GL_VERTEX_ARRAY);
134        if (_tfPtr != NULL) {
135            _tfPtr->getTexture()->deactivate();
136            glDisableClientState(GL_TEXTURE_COORD_ARRAY);
137           
138            cgGLDisableProfile(CG_PROFILE_FP30);
139        }
140    }
141    glShadeModel(GL_FLAT);
142    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
143   
144    if (_contour != NULL) {
145        if (_contourVisible) {
146            glDisable(GL_BLEND);
147            glDisable(GL_TEXTURE_2D);
148            glColor4f(_contourColor.x, _contourColor.y, _contourColor.z,
149                _opacity /*1.0f*/);
150            glDepthRange (0.0, 0.999);
151            _contour->render();
152            glDepthRange (0.0, 1.0);
153        }
154
155#if TOPCONTOUR
156        if (_topContourVisible) {
157            glDisable(GL_BLEND);
158            glDisable(GL_TEXTURE_2D);
159            glColor4f(_contourColor.x, _contourColor.y, _contourColor.z,
160                _opacity /*1.0f*/);
161            //glDepthRange (0.0, 0.999);
162            _topContour->render();
163            //glDepthRange (0.0, 1.0);
164        }
165#endif
166    }
167    glPopMatrix();
168}
169
170void
171HeightMap::createIndexBuffer(int xCount, int zCount, float* heights)
172{
173    if (_indexBuffer != NULL) {
174        delete [] _indexBuffer;
175        _indexBuffer = NULL;
176    }
177    _indexCount = (xCount - 1) * (zCount - 1) * 6;
178    _indexBuffer = new int[_indexCount];
179   
180    int i, j;
181    int boundaryWidth = xCount - 1;
182    int boundaryHeight = zCount - 1;
183    int* ptr = _indexBuffer;
184    int index1, index2, index3, index4;
185    bool index1Valid, index2Valid, index3Valid, index4Valid;
186    index1Valid = index2Valid = index3Valid = index4Valid = true;
187
188    if (heights) {
189        int ic = 0;
190        for (i = 0; i < boundaryHeight; ++i) {
191            for (j = 0; j < boundaryWidth; ++j) {
192                index1 = i * xCount +j;
193                if (isnan(heights[index1])) index1Valid = false;
194                index2 = (i + 1) * xCount + j;
195                if (isnan(heights[index2])) index2Valid = false;
196                index3 = (i + 1) * xCount + j + 1;
197                if (isnan(heights[index3])) index3Valid = false;
198                index4 = i * xCount + j + 1;
199                if (isnan(heights[index4])) index4Valid = false;
200           
201#ifdef _TRIANGLES_
202                if (index1Valid && index2Valid && index3Valid) {
203                    *ptr = index1; ++ptr;
204                    *ptr = index2; ++ptr;
205                    *ptr = index3; ++ptr;
206                    ++ic;
207                }
208                if (index1Valid && index3Valid && index4Valid) {
209                    *ptr = index1; ++ptr;
210                    *ptr = index3; ++ptr;
211                    *ptr = index4; ++ptr;
212                    ++ic;
213                }
214#else
215                if (index1Valid && index2Valid && index3Valid && index4Valid) {
216                    *ptr = index1; ++ptr;
217                    *ptr = index2; ++ptr;
218                    *ptr = index3; ++ptr;
219                    *ptr = index4; ++ptr;
220                    ++ic;
221                }
222#endif
223            }
224        }
225    } else {
226        for (i = 0; i < boundaryHeight; ++i) {
227            for (j = 0; j < boundaryWidth; ++j) {
228                *ptr = i * xCount + j; ++ptr;
229                *ptr = (i + 1) * xCount + j; ++ptr;
230                *ptr = (i + 1) * xCount + j + 1; ++ptr;
231                *ptr = i * xCount + j; ++ptr;
232                *ptr = (i + 1) * xCount + j + 1; ++ptr;
233                *ptr = i * xCount + j + 1; ++ptr;
234            }
235        }
236    }
237}
238
239void
240HeightMap::reset()
241{
242    if (_vertexBufferObjectID) {
243        glDeleteBuffers(1, &_vertexBufferObjectID);
244        _vertexBufferObjectID = NULL;
245    }
246    if (_textureBufferObjectID) {
247        glDeleteBuffers(1, &_textureBufferObjectID);
248        _textureBufferObjectID = NULL;
249    }
250    if (_contour != NULL) {
251        delete _contour;
252        _contour = NULL;
253    }
254    if (_indexBuffer != NULL) {
255        delete [] _indexBuffer;
256        _indexBuffer = NULL;
257    }
258}
259
260void
261HeightMap::setHeight(int xCount, int yCount, Vector3* heights)
262{
263    _vertexCount = xCount * yCount;
264    reset();
265   
266    _heights = (float *)heights;
267    float min, max;
268    min = heights[0].y, max = heights[0].y;
269
270    int count = xCount * yCount;
271    for (int i = 0; i < count; ++i) {
272        if (min > heights[i].y) {
273            min = heights[i].y;
274        }
275        if (max < heights[i].y) {
276            max = heights[i].y;
277        }
278    }
279
280    _scale.x = 1.0f;
281    _scale.z = max - min;
282    _scale.y = 1.0f;
283
284    xAxis.SetRange(0.0, 1.0);
285    yAxis.SetRange(0.0, 1.0);
286    zAxis.SetRange(0.0, 1.0);
287    wAxis.SetRange(min, max);
288    update_pending = true;
289   
290    _centerPoint.set(_scale.x * 0.5, _scale.z * 0.5 + min, _scale.y * 0.5);
291
292    Vector3* texcoord = new Vector3[count];
293    for (int i = 0; i < count; ++i) {
294        texcoord[i].set(0, 0, heights[i].y);
295    }
296   
297    glGenBuffers(1, &_vertexBufferObjectID);
298    glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferObjectID);
299    glBufferData(GL_ARRAY_BUFFER, _vertexCount * sizeof( Vector3 ), heights,
300        GL_STATIC_DRAW);
301    glGenBuffers(1, &_textureBufferObjectID);
302    glBindBuffer(GL_ARRAY_BUFFER, _textureBufferObjectID);
303    glBufferData(GL_ARRAY_BUFFER, _vertexCount * sizeof(float) * 3, texcoord,
304        GL_STATIC_DRAW);
305    glBindBuffer(GL_ARRAY_BUFFER, 0);
306   
307    delete [] texcoord;
308   
309    if (_contour != NULL) {
310        delete _contour;
311        _contour = NULL;
312    }
313    ContourLineFilter lineFilter;
314    _contour = lineFilter.create(0.0f, 1.0f, 10, heights, xCount, yCount);
315
316#if TOPCONTOUR
317    ContourLineFilter topLineFilter;
318    topLineFilter.setHeightTop(true);
319    _topContour = topLineFilter.create(0.0f, 1.0f, 10, heights, xCount, yCount);
320#endif
321
322    //if (heightMap)
323    //{
324    //  VertexBuffer* vertexBuffer = new VertexBuffer(VertexBuffer::POSITION3, xCount * yCount, sizeof(Vector3) * xCount * yCount, heightMap, false);
325    this->createIndexBuffer(xCount, yCount, 0);
326    //}
327    //else
328    //{
329    //ERROR("HeightMap::setHeight\n");
330    //}
331}
332
333void
334HeightMap::setHeight(float xMin, float yMin, float xMax, float yMax,
335                     int xNum, int yNum, float* heights)
336{
337    _vertexCount = xNum * yNum;
338    _xNum = xNum, _yNum = yNum;
339    _heights = heights;
340    reset();
341   
342    // Get the min/max of the heights. */
343    float min, max;
344    min = max = heights[0];
345    for (int i = 0; i < _vertexCount; ++i) {
346        if (min > heights[i]) {
347            min = heights[i];
348        } else if (max < heights[i]) {
349            max = heights[i];
350        }
351    }
352#ifdef notdef
353    if (retainScale_) {
354        // Check the units of each axis.  If they are the same, we want to
355        // retain the surface's aspect ratio when transforming coordinates to
356        // the grid. Use the range of the longest axis when the units are the
357        // same. 
358        if (xAxis.units() != NULL) && (xAxis.units() == yAxis.units()) {
359        }
360        if (yAxis.units() != NULL) && (yAxis.units() == zAxis.units()) {
361        }
362    }
363#endif
364
365    wAxis.SetRange(min, max);
366    yAxis.SetRange(min, max);
367    xAxis.SetRange(xMin, xMax);
368    zAxis.SetRange(yMin, yMax);
369   
370   
371    min = 0.0, max = 1.0;
372    xMin = yMin = min = 0.0;
373    xMax = yMax = max = 1.0;
374    // Save the scales.
375    _scale.x = _scale.y = _scale.z = 1.0;
376
377    update_pending = true;
378
379    _centerPoint.set(0.5, 0.5, 0.5);
380   
381#ifndef notdef
382    Vector3* texcoord = new Vector3[_vertexCount];
383    for (int i = 0; i < _vertexCount; ++i) {
384        texcoord[i].set(0, 0, heights[i]);
385    }
386   
387    Vector3* map = createHeightVertices(xMin, yMin, xMax, yMax, xNum, yNum,
388                                        heights);
389   
390    glGenBuffers(1, &_vertexBufferObjectID);
391    glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferObjectID);
392    glBufferData(GL_ARRAY_BUFFER, _vertexCount * sizeof(Vector3), map,
393        GL_STATIC_DRAW);
394    glGenBuffers(1, &_textureBufferObjectID);
395    glBindBuffer(GL_ARRAY_BUFFER, _textureBufferObjectID);
396    glBufferData(GL_ARRAY_BUFFER, _vertexCount * sizeof(float) * 3, texcoord,
397        GL_STATIC_DRAW);
398    glBindBuffer(GL_ARRAY_BUFFER, 0);
399   
400    delete [] texcoord;
401   
402   
403    if (_contour != NULL) {
404        delete _contour;
405        _contour = NULL;
406    }
407    ContourLineFilter lineFilter;
408    //lineFilter.transferFunction(_tfPtr);
409    _contour = lineFilter.create(0.0f, 1.0f, 10, map, xNum, yNum);
410   
411#if TOPCONTOUR
412    ContourLineFilter topLineFilter;
413    topLineFilter.setHeightTop(true);
414    _topContour = topLineFilter.create(0.0f, 1.0f, 10, map, xNum, yNum);
415#endif
416    this->createIndexBuffer(xNum, yNum, heights);
417    delete [] map;
418#endif
419}
420
421Vector3*
422HeightMap::createHeightVertices(float xMin, float yMin, float xMax,
423                                float yMax, int xNum, int yNum, float* height)
424{
425    Vector3* vertices = new Vector3[xNum * yNum];
426
427    Vector3* dstDataPtr = vertices;
428    float* srcDataPtr = height;
429   
430    for (int y = 0; y < yNum; ++y) {
431        float yCoord;
432
433        yCoord = yMin + ((yMax - yMin) * y) / (yNum - 1);
434        for (int x = 0; x < xNum; ++x) {
435            float xCoord;
436
437            xCoord = xMin + ((xMax - xMin) * x) / (xNum - 1);
438            dstDataPtr->set(xCoord, *srcDataPtr, yCoord);
439
440            ++dstDataPtr;
441            ++srcDataPtr;
442        }
443    }
444    return vertices;
445}
446
447// Maps the data coordinates of the surface into the grid's axes.
448void
449HeightMap::MapToGrid(Grid *gridPtr)
450{
451    int count = _xNum * _yNum;
452
453    reset();
454
455    // The range of the grid's y-axis 0..1 represents the distance between the
456    // smallest and largest major ticks for all surfaces plotted.  Translate
457    // this surface's y-values (heights) into the grid's axis coordinates.
458
459    float yScale = 1.0 / (gridPtr->yAxis.max() - gridPtr->yAxis.min());
460    float *p, *q, *pend;
461    float *normHeights = new float[count];
462    for (p = _heights, pend = p + count, q = normHeights; p < pend; p++, q++) {
463        *q = (*p - gridPtr->yAxis.min()) * yScale;
464    }
465    Vector3 *t, *texcoord;
466    texcoord = new Vector3[count];
467    for (t = texcoord, p = normHeights, pend = p + count; p < pend; p++, t++) {
468        t->set(0, 0, *p);
469    }
470
471    // Normalize the mesh coordinates (x and z min/max) the range of the major
472    // ticks for the x and z grid axes as well.
473
474    float xScale, zScale;
475    float xMin, xMax, zMin, zMax;
476
477    xScale = 1.0 / (gridPtr->xAxis.max() - gridPtr->xAxis.min());
478    xMin = (xAxis.min() - gridPtr->xAxis.min()) * xScale;
479    xMax = (xAxis.max() - gridPtr->xAxis.min()) * xScale;
480    zScale = 1.0 / (gridPtr->zAxis.max() - gridPtr->zAxis.min());
481    zMin = (zAxis.min() - gridPtr->zAxis.min()) * zScale;
482    zMax = (zAxis.max() - gridPtr->zAxis.min()) * zScale;
483
484    Vector3* vertices;
485    vertices = createHeightVertices(xMin, zMin, xMax, zMax, _xNum, _yNum,
486        normHeights);
487   
488    glGenBuffers(1, &_vertexBufferObjectID);
489    glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferObjectID);
490    glBufferData(GL_ARRAY_BUFFER, _vertexCount * sizeof(Vector3), vertices,
491        GL_STATIC_DRAW);
492    glGenBuffers(1, &_textureBufferObjectID);
493    glBindBuffer(GL_ARRAY_BUFFER, _textureBufferObjectID);
494    glBufferData(GL_ARRAY_BUFFER, _vertexCount * sizeof(float) * 3, texcoord,
495        GL_STATIC_DRAW);
496    glBindBuffer(GL_ARRAY_BUFFER, 0);
497    delete [] texcoord;
498
499    if (_contour != NULL) {
500        delete _contour;
501        _contour = NULL;
502    }
503    ContourLineFilter lineFilter;
504    //lineFilter.transferFunction(_tfPtr);
505    _contour = lineFilter.create(0.0f, 1.0f, 10, vertices, _xNum, _yNum);
506   
507#if TOPCONTOUR
508    ContourLineFilter topLineFilter;
509    topLineFilter.setHeightTop(true);
510    _topContour = topLineFilter.create(0.0f, 1.0f, 10, vertices, _xNum, _yNum);
511#endif
512    this->createIndexBuffer(_xNum, _yNum, normHeights);
513    delete [] normHeights;
514    delete [] vertices;
515}
516
517void
518HeightMap::render_topview(graphics::RenderContext* renderContext,
519                          int render_width, int render_height)
520{
521   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
522    glPushAttrib(GL_VIEWPORT_BIT);
523    glViewport(0, 0, render_width, render_height);
524    glMatrixMode(GL_PROJECTION);
525    glPushMatrix();
526    glLoadIdentity();
527    //gluOrtho2D(0, render_width, 0, render_height);
528    glOrtho(-.5, .5, -.5, .5, -50, 50);
529    glMatrixMode(GL_MODELVIEW);
530    glPushMatrix();
531    glLoadIdentity();
532
533    glTranslatef(0.0, 0.0, -10.0);
534
535    // put camera rotation and traslation
536    //glScalef(1 / _scale.x, 1 / _scale.y , 1 / _scale.z);
537
538    if (renderContext->getCullMode() == graphics::RenderContext::NO_CULL) {
539        glDisable(GL_CULL_FACE);
540    } else {
541        glEnable(GL_CULL_FACE);
542        glCullFace((GLuint) renderContext->getCullMode());
543    }
544
545    glPolygonMode(GL_FRONT_AND_BACK, (GLuint) renderContext->getPolygonMode());
546    glShadeModel((GLuint) renderContext->getShadingModel());
547
548    glPushMatrix();
549
550    //glTranslatef(-_centerPoint.x, -_centerPoint.y, -_centerPoint.z);
551
552    //glScalef(0.01, 0.01, 0.01f);
553    glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
554    glTranslatef(-_centerPoint.x, -_centerPoint.y, -_centerPoint.z);
555    if (_contour != NULL) {
556        glDepthRange (0.001, 1.0);
557    }
558       
559    glEnable(GL_DEPTH_TEST);
560
561    glEnable(GL_BLEND);
562    glEnable(GL_TEXTURE_2D);
563    if (_vertexBufferObjectID)
564    {
565        TRACE("TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT\n");
566        glColor3f(1.0f, 1.0f, 1.0f);
567        glShadeModel(GL_SMOOTH);
568        glEnable(GL_BLEND);
569        glEnableClientState(GL_VERTEX_ARRAY);
570        glDisableClientState(GL_COLOR_ARRAY);
571        glDisableClientState(GL_INDEX_ARRAY);
572        glDisableClientState(GL_NORMAL_ARRAY);
573       
574        if (_tfPtr != NULL) {
575            cgGLBindProgram(_shader->getFP());
576            cgGLEnableProfile(CG_PROFILE_FP30);
577           
578            cgGLSetTextureParameter(_tfParam, _tfPtr->id());
579            cgGLEnableTextureParameter(_tfParam);
580           
581            glEnable(GL_TEXTURE_1D);
582            _tfPtr->getTexture()->activate();
583           
584            glEnableClientState(GL_TEXTURE_COORD_ARRAY);
585        }
586        else {
587            glDisableClientState(GL_TEXTURE_COORD_ARRAY);
588        }
589       
590        glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferObjectID);
591        glVertexPointer(3, GL_FLOAT, 12, 0);
592       
593        glBindBuffer(GL_ARRAY_BUFFER, _textureBufferObjectID);
594        ::glTexCoordPointer(3, GL_FLOAT, 12, 0);
595       
596#define _TRIANGLES_
597#ifdef _TRIANGLES_
598        glDrawElements(GL_TRIANGLES, _indexCount, GL_UNSIGNED_INT,
599                       _indexBuffer);
600#else                   
601        glDrawElements(GL_QUADS, _indexCount, GL_UNSIGNED_INT,
602                       _indexBuffer);
603#endif
604
605        glBindBuffer(GL_ARRAY_BUFFER, 0);
606       
607        glDisableClientState(GL_VERTEX_ARRAY);
608        if (_tfPtr != NULL) {
609            _tfPtr->getTexture()->deactivate();
610            glDisableClientState(GL_TEXTURE_COORD_ARRAY);
611           
612            cgGLDisableProfile(CG_PROFILE_FP30);
613        }
614    }
615   
616    glShadeModel(GL_FLAT);
617    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
618   
619    if (_contour != NULL) {
620        if (_contourVisible) {
621            glDisable(GL_BLEND);
622            glDisable(GL_TEXTURE_2D);
623            glColor4f(_contourColor.x, _contourColor.y, _contourColor.z, 1.0f);
624            glDepthRange (0.0, 0.999);
625            _contour->render();
626            glDepthRange (0.0, 1.0);
627        }
628
629#if TOPCONTOUR
630        if (_topContourVisible) {
631            glDisable(GL_BLEND);
632            glDisable(GL_TEXTURE_2D);
633            glColor4f(_contourColor.x, _contourColor.y, _contourColor.z, 1.0f);
634            //glDepthRange (0.0, 0.999);
635            _topContour->render();
636            //glDepthRange (0.0, 1.0);
637        }
638#endif
639    }
640   
641    glPopMatrix();
642    glPopMatrix();
643
644    glMatrixMode(GL_PROJECTION);
645    glPushMatrix();
646    glMatrixMode(GL_MODELVIEW);
647
648    glPopAttrib();
649}
650
Note: See TracBrowser for help on using the repository browser.