source: nanovis/trunk/VelocityArrowsSlice.cpp @ 5722

Last change on this file since 5722 was 5587, checked in by ldelgass, 4 years ago

Some fixes from release branch

  • Property svn:eol-style set to native
File size: 18.8 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2004-2013  HUBzero Foundation, LLC
4 *
5 */
6
7#include <math.h>
8
9#include <GL/glew.h>
10#include <GL/gl.h>
11
12#include <vrmath/Vector3f.h>
13#include <util/FilePath.h>
14#include <Image.h>
15#include <ImageLoaderFactory.h>
16#include <ImageLoader.h>
17
18#include "nanovis.h"
19#include "VelocityArrowsSlice.h"
20#include "Volume.h"
21#include "Shader.h"
22#include "Camera.h"
23
24using namespace nv;
25using namespace nv::util;
26using namespace vrmath;
27
28static inline float deg2rad(float deg)
29{
30    return ((deg * M_PI) / 180.);
31}
32
33static inline float rad2deg(float rad)
34{
35    return ((rad * 180.) / M_PI);
36}
37
38VelocityArrowsSlice::VelocityArrowsSlice() :
39    _vectorFieldGraphicsID(0),
40    _slicePos(0.5f),
41    _axis(AXIS_Z),
42    _fbo(0),
43    _tex(0),
44    _maxPointSize(1.0f),
45    _renderTargetWidth(128),
46    _renderTargetHeight(128),
47    _velocities(NULL),
48    _projectionVector(1, 1, 0),
49    _tickCountForMinSizeAxis(10),
50    _tickCountX(0),
51    _tickCountY(0),
52    _tickCountZ(0),
53    _pointCount(0),
54    _maxVelocityScale(1, 1, 1),
55    _arrowColor(1, 1, 0),
56    _visible(false),
57    _dirty(true),
58    _vertexBufferGraphicsID(0),
59    _arrowsTex(NULL),
60    _renderMode(LINES)
61{
62    setSliceAxis(_axis);
63
64    _queryVelocityFP.loadFragmentProgram("queryvelocity.cg");
65
66    // Delay loading of shaders only required for glyph style rendering
67    if (_renderMode == GLYPHS) {
68        _particleShader.loadVertexProgram("velocityslicevp.cg");
69        _particleShader.loadFragmentProgram("velocityslicefp.cg");
70    }
71
72    createRenderTarget();
73
74    std::string path = FilePath::getInstance()->getPath("arrows.bmp");
75    if (!path.empty()) {
76        ImageLoader *loader = ImageLoaderFactory::getInstance()->createLoader("bmp");
77        if (loader != NULL) {
78            Image *image = loader->load(path.c_str(), Image::IMG_RGBA);
79            if (image != NULL) {
80                unsigned char *bytes = (unsigned char *)image->getImageBuffer();
81                if (bytes != NULL) {
82                    _arrowsTex = new Texture2D(image->getWidth(), image->getHeight(),
83                                               GL_UNSIGNED_BYTE, GL_LINEAR, 4, NULL);
84                    _arrowsTex->setWrapS(GL_MIRRORED_REPEAT);
85                    _arrowsTex->setWrapT(GL_MIRRORED_REPEAT);
86                    _arrowsTex->initialize(image->getImageBuffer());
87                }
88                delete image;
89            } else {
90                ERROR("Failed to load arrows image");
91            }
92            delete loader;
93        } else {
94            ERROR("Couldn't find loader for arrows image");
95        }
96    } else {
97        ERROR("Couldn't find arrows image");
98    }
99
100    GLfloat minMax[2];
101    glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, minMax);
102    TRACE("Aliased point size range: %g %g", minMax[0], minMax[1]);
103    _maxPointSize = minMax[1];
104    glGetFloatv(GL_SMOOTH_POINT_SIZE_RANGE, minMax);
105    TRACE("Smooth point size range: %g %g", minMax[0], minMax[1]);
106    _maxPointSize = minMax[1] > _maxPointSize ? minMax[1] : _maxPointSize;
107    TRACE("Max point size: %g", _maxPointSize);
108
109    TRACE("Leaving VelocityArrowsSlice constructor");
110}
111
112VelocityArrowsSlice::~VelocityArrowsSlice()
113{
114    if (_tex != 0) {
115        glDeleteTextures(1, &_tex);
116    }
117    if (_fbo != 0) {
118        glDeleteFramebuffersEXT(1, &_fbo);
119    }
120    if (_arrowsTex != NULL) {
121        delete _arrowsTex;
122    }
123    if (_vertexBufferGraphicsID != 0) {
124        glDeleteBuffers(1, &_vertexBufferGraphicsID);
125    }
126    if (_velocities != NULL) {
127        delete [] _velocities;
128    }
129}
130
131void VelocityArrowsSlice::createRenderTarget()
132{
133    if (_velocities != NULL) {
134        delete [] _velocities;
135    }
136    _velocities = new Vector3f[_renderTargetWidth * _renderTargetHeight];
137
138    if (_vertexBufferGraphicsID != 0) {
139        glDeleteBuffers(1, &_vertexBufferGraphicsID);
140    }
141
142    glGenBuffers(1, &_vertexBufferGraphicsID);
143    glBindBufferARB(GL_ARRAY_BUFFER_ARB, _vertexBufferGraphicsID);
144    glBufferDataARB(GL_ARRAY_BUFFER_ARB, _renderTargetWidth * _renderTargetHeight * 3 * sizeof(float), 
145                    0, GL_DYNAMIC_DRAW_ARB);
146    glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
147
148    if (_tex != 0) {
149        glDeleteTextures(1, &_tex);
150    }
151    if (_fbo != 0) {
152        glDeleteFramebuffersEXT(1, &_fbo);
153    }
154
155    glGenFramebuffersEXT(1, &_fbo);
156    glGenTextures(1, &_tex);
157    int fboOrig;
158    glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fboOrig);
159    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _fbo);
160
161    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, _tex);
162    glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
163    glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
164    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
165    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
166
167    glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA32F_ARB, 
168                 _renderTargetWidth, _renderTargetHeight, 0,
169                 GL_RGBA, GL_FLOAT, NULL);
170
171    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, 
172                              GL_TEXTURE_RECTANGLE_ARB, _tex, 0);
173
174    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboOrig);
175}
176
177void VelocityArrowsSlice::setSliceAxis(FlowSliceAxis axis)
178{
179    _axis = axis;
180    switch (_axis) {
181    case AXIS_X:
182        _projectionVector.x = 0;
183        _projectionVector.y = 1;
184        _projectionVector.z = 1;
185        break;
186    case AXIS_Y:
187        _projectionVector.x = 1;
188        _projectionVector.y = 0;
189        _projectionVector.z = 1;
190        break;
191    case AXIS_Z:
192        _projectionVector.x = 1;
193        _projectionVector.y = 1;
194        _projectionVector.z = 0;
195        break;
196    }
197    _dirty = true;
198}
199
200void VelocityArrowsSlice::queryVelocity()
201{
202    glPushAttrib(GL_VIEWPORT_BIT | GL_ENABLE_BIT);
203    int fboOrig;
204    glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fboOrig);
205
206    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _fbo);
207
208    _queryVelocityFP.bind();
209    _queryVelocityFP.setFPTextureParameter("vfield", _vectorFieldGraphicsID);
210
211    glDisable(GL_DEPTH_TEST);
212    glViewport(0, 0, _renderTargetWidth, _renderTargetHeight);
213    glMatrixMode(GL_PROJECTION);
214    glPushMatrix();
215    glLoadIdentity();
216    glOrtho(0, _renderTargetWidth, 0, _renderTargetHeight, -10.0f, 10.0f);
217    glMatrixMode(GL_MODELVIEW);
218    glPushMatrix();
219    glLoadIdentity();
220
221    glBegin(GL_QUADS);
222    switch (_axis) {
223    case AXIS_X:
224        glTexCoord3f(_slicePos, 0, 0); glVertex2i(0,                  0);
225        glTexCoord3f(_slicePos, 1, 0); glVertex2i(_renderTargetWidth, 0);
226        glTexCoord3f(_slicePos, 1, 1); glVertex2i(_renderTargetWidth, _renderTargetHeight);
227        glTexCoord3f(_slicePos, 0, 1); glVertex2i(0,                  _renderTargetHeight);
228        break;
229    case AXIS_Y:
230        glTexCoord3f(0, _slicePos, 0); glVertex2i(0,                  0);
231        glTexCoord3f(1, _slicePos, 0); glVertex2i(_renderTargetWidth, 0);
232        glTexCoord3f(1, _slicePos, 1); glVertex2i(_renderTargetWidth, _renderTargetHeight);
233        glTexCoord3f(0, _slicePos, 1); glVertex2i(0,                  _renderTargetHeight);
234        break;
235    case AXIS_Z:
236    default:
237        glTexCoord3f(0, 0, _slicePos); glVertex2i(0,                  0);
238        glTexCoord3f(1, 0, _slicePos); glVertex2i(_renderTargetWidth, 0);
239        glTexCoord3f(1, 1, _slicePos); glVertex2i(_renderTargetWidth, _renderTargetHeight);
240        glTexCoord3f(0, 1, _slicePos); glVertex2i(0,                  _renderTargetHeight);
241        break;
242    }
243    glEnd();
244
245    _queryVelocityFP.disableFPTextureParameter("vfield");
246    _queryVelocityFP.unbind();
247
248    glReadPixels(0, 0, _renderTargetWidth, _renderTargetHeight, GL_RGB, GL_FLOAT, _velocities);
249
250    glMatrixMode(GL_PROJECTION);
251    glPopMatrix();
252    glMatrixMode(GL_MODELVIEW);
253    glPopMatrix();
254
255    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboOrig);
256
257    glPopAttrib();
258}
259
260static void drawLineArrow(FlowSliceAxis axis)
261{
262    glBegin(GL_LINES);
263    switch (axis) {
264    case AXIS_X: // ZY plane
265        glVertex3f(0.0, 0.0, 0.0);
266        glVertex3f(0.0, 0.0, 1.0);
267
268        glVertex3f(0.0, 0.0, 1.0);
269        glVertex3f(0.0, 0.25, 0.75);
270
271        glVertex3f(0.0, 0.0, 1.0);
272        glVertex3f(0.0, -0.25, 0.75);
273        break;
274    case AXIS_Y: // XZ plane
275        glVertex3f(0.0, 0.0, 0.0);
276        glVertex3f(1.0, 0.0, 0.0);
277
278        glVertex3f(1.0, 0.0, 0.0);
279        glVertex3f(0.75, 0.0, 0.25);
280
281        glVertex3f(1.0, 0.0, 0.0);
282        glVertex3f(0.75, 0.0, -0.25);
283        break;
284    case AXIS_Z: // XY plane
285    default:
286        glVertex3f(0.0, 0.0, 0.0);
287        glVertex3f(1.0, 0.0, 0.0);
288
289        glVertex3f(1.0, 0.0, 0.0);
290        glVertex3f(0.75, 0.25, 0.0);
291
292        glVertex3f(1.0, 0.0, 0.0);
293        glVertex3f(0.75, -0.25, 0.0);
294        break;
295    }
296    glEnd();
297}
298
299void VelocityArrowsSlice::render()
300{
301    if (!_visible)
302        return;
303
304    if (_dirty) {
305        computeSamplingTicks();
306        queryVelocity();
307        _dirty = false;
308    }
309
310    TRACE("_scale: %g %g %g", _scale.x, _scale.y, _scale.z);
311    TRACE("_maxVelocityScale: %g %g %g",
312          _maxVelocityScale.x, _maxVelocityScale.y, _maxVelocityScale.z);
313
314    glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
315
316    glMatrixMode(GL_MODELVIEW);
317    glPushMatrix();
318
319    glTranslatef(_origin.x, _origin.y, _origin.z);
320    glScalef(_scale.x, _scale.y, _scale.z);
321
322    if (_renderMode == LINES) {
323        glDisable(GL_TEXTURE_2D);
324        glDisable(GL_LIGHTING);
325        glLineWidth(2.0);
326        glColor3f(_arrowColor.x, _arrowColor.y, _arrowColor.z);
327 
328        Vector3f pos;
329        Vector3f vel;
330        Vector3f refVec;
331        Vector3f blue(0, 0, 1);
332        Vector3f red(1, 0, 0);
333
334        int index = 0, icount, jcount;
335        switch (_axis) {
336        case AXIS_X:
337            icount = _tickCountZ;
338            jcount = _tickCountY;
339            refVec.set(0, 0, 1);
340            break;
341        case AXIS_Y:
342            icount = _tickCountZ;
343            jcount = _tickCountX;
344            refVec.set(1, 0, 0);
345            break;
346        case AXIS_Z:
347        default:
348            icount = _tickCountY;
349            jcount = _tickCountX;
350            refVec.set(1, 0, 0);
351            break;
352        }
353
354        for (int i = 0; i < icount; ++i) {
355            for (int j = 0; j < jcount; ++j, ++index) {
356                pos = _samplingPositions[index];
357                // Normalized velocity: [-1,1] components
358                // Project 3D vector onto sample plane
359                vel = _velocities[index].scale(_projectionVector);
360                // Length: [0,1]
361                double length = vel.length();
362                if (length < 0.0 || length > 1.0) {
363                    TRACE("***vec: (%g, %g, %g) length: %g***", vel.x, vel.y, vel.z, length);
364                    continue;
365                }
366                if (length > 0.0) {
367                    Vector3f vnorm = vel.normalize();
368                    Vector3f rotationAxis = refVec.cross(vnorm);
369                    double angle = rad2deg(acos(refVec.dot(vnorm)));
370                    Vector3f color = blue * (1.0 - length) + red * length;
371                    float scale = length;
372                    if (scale < 0.10) scale = 0.10;
373                    glMatrixMode(GL_MODELVIEW);
374                    glPushMatrix();
375                    glColor3f(color.x, color.y, color.z);
376                    glTranslatef(pos.x, pos.y, pos.z);
377                    glScalef(2.0 * _maxVelocityScale.x,
378                             2.0 * _maxVelocityScale.y,
379                             2.0 * _maxVelocityScale.z);
380                    glScalef(scale, scale, scale);
381                    if (angle > 1.0e-6 || angle < -1.0e-6)
382                        glRotated(angle, rotationAxis.x, rotationAxis.y, rotationAxis.z);
383                    drawLineArrow(_axis);
384                    glPopMatrix();
385                }
386            }
387        }
388
389        glLineWidth(1.0);
390    } else {
391        glColor4f(_arrowColor.x, _arrowColor.y, _arrowColor.z, 1.0f);
392        glEnable(GL_DEPTH_TEST);
393        glDisable(GL_LIGHTING);
394#if 0
395        glBlendFunc(GL_SRC_ALPHA, GL_ONE);
396        glEnable(GL_BLEND);
397        glDepthMask(GL_FALSE);
398#else
399        glDisable(GL_BLEND);
400#endif
401        glAlphaFunc(GL_GREATER, 0.6);
402        glEnable(GL_ALPHA_TEST);
403        glEnable(GL_POINT_SPRITE_ARB);
404        glPointSize(20);
405        glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
406
407        _arrowsTex->activate();
408        glEnable(GL_TEXTURE_2D);
409        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
410
411        GLfloat atten[] = {1, 0, 0};
412        glPointParameterfvARB(GL_POINT_DISTANCE_ATTENUATION_ARB, atten);
413        glPointParameterfARB(GL_POINT_FADE_THRESHOLD_SIZE_ARB, 0.0f);
414        glPointParameterfARB(GL_POINT_SIZE_MIN_ARB, 1.0f);
415        glPointParameterfARB(GL_POINT_SIZE_MAX_ARB, _maxPointSize);
416        glTexEnvi(GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_TRUE);
417
418        // FIXME: This vertex shader won't compile with ARB_vertex_program,
419        // so it should use GLSL
420        if (!_particleShader.isVertexProgramLoaded()) {
421            _particleShader.loadVertexProgram("velocityslicevp.cg");
422        }
423        if (!_particleShader.isFragmentProgramLoaded()) {
424            _particleShader.loadFragmentProgram("velocityslicefp.cg");
425        }
426
427        _particleShader.bind();
428        _particleShader.setVPTextureParameter("vfield", _vectorFieldGraphicsID);
429        _particleShader.setFPTextureParameter("arrows", _arrowsTex->id());
430        _particleShader.setVPParameter1f("tanHalfFOV",
431                                         tan(NanoVis::getCamera()->getFov() * 0.5) * NanoVis::winHeight * 0.5);
432        _particleShader.setGLStateMatrixVPParameter("modelview",
433                                                    Shader::MODELVIEW_MATRIX,
434                                                    Shader::MATRIX_IDENTITY);
435        _particleShader.setGLStateMatrixVPParameter("mvp",
436                                                    Shader::MODELVIEW_PROJECTION_MATRIX,
437                                                    Shader::MATRIX_IDENTITY);
438
439        glEnableClientState(GL_VERTEX_ARRAY);
440        glBindBufferARB(GL_ARRAY_BUFFER_ARB, _vertexBufferGraphicsID);
441        glVertexPointer(3, GL_FLOAT, 0, 0);
442        //glEnableClientState(GL_COLOR_ARRAY);
443
444        // TBD..
445        glDrawArrays(GL_POINTS, 0, _pointCount);
446        glPointSize(1);
447
448        glDisableClientState(GL_VERTEX_ARRAY);
449        glDisable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
450        glDisable(GL_POINT_SPRITE_ARB);
451
452        _particleShader.disableVPTextureParameter("vfield");
453        _particleShader.disableFPTextureParameter("arrows");
454        _particleShader.unbind();
455
456        glActiveTexture(GL_TEXTURE0);
457        glTexEnvi(GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_FALSE);
458        _arrowsTex->deactivate();
459    }
460
461    glPopMatrix();
462    glPopAttrib();
463}
464
465void 
466VelocityArrowsSlice::setVectorField(Volume *volume)
467{
468    _vectorFieldGraphicsID = volume->textureID();
469    Vector3f bmin, bmax;
470    volume->getBounds(bmin, bmax);
471    _origin = bmin;
472    _scale.set(bmax.x-bmin.x, bmax.y-bmin.y, bmax.z-bmin.z);
473
474    _dirty = true;
475}
476
477void VelocityArrowsSlice::computeSamplingTicks()
478{
479    if (_scale.x < _scale.y) {
480        if (_scale.x < _scale.z || _scale.z == 0.0) {
481            // _scale.x
482            _tickCountX = _tickCountForMinSizeAxis;
483
484            float step = _scale.x / (_tickCountX + 1);
485            _tickCountY = (int)(_scale.y/step);
486            _tickCountZ = (int)(_scale.z/step);
487        } else {
488            // _scale.z
489            _tickCountZ = _tickCountForMinSizeAxis;
490
491            float step = _scale.z / (_tickCountZ + 1);
492            _tickCountX = (int)(_scale.x/step);
493            _tickCountY = (int)(_scale.y/step);
494        }
495    } else {
496        if (_scale.y < _scale.z || _scale.z == 0.0) {
497            // _scale.y
498            _tickCountY = _tickCountForMinSizeAxis;
499
500            float step = _scale.y / (_tickCountY + 1);
501            _tickCountX = (int)(_scale.x/step);
502            _tickCountZ = (int)(_scale.z/step);
503        } else {
504            // _scale.z
505            _tickCountZ = _tickCountForMinSizeAxis;
506
507            float step = _scale.z / (_tickCountZ + 1);
508            _tickCountX = (int)(_scale.x/step);
509            _tickCountY = (int)(_scale.y/step);
510        }
511    }
512
513    switch (_axis) {
514    case AXIS_X:
515        _tickCountX = 1;
516        _renderTargetWidth = _tickCountY;
517        _renderTargetHeight = _tickCountZ;
518        break;
519    case AXIS_Y:
520        _tickCountY = 1;
521        _renderTargetWidth = _tickCountX;
522        _renderTargetHeight = _tickCountZ;
523        break;
524    default:
525    case AXIS_Z:
526        _tickCountZ = 1;
527        _renderTargetWidth = _tickCountX;
528        _renderTargetHeight = _tickCountY;
529        break;
530    }
531
532    _maxVelocityScale.x = (1.0f / _tickCountX) * 0.8f;
533    _maxVelocityScale.y = (1.0f / _tickCountY) * 0.8f;
534    _maxVelocityScale.z = (1.0f / _tickCountZ) * 0.8f;
535
536    TRACE("Tick counts: %d %d %d", _tickCountX, _tickCountY, _tickCountZ);
537
538    int pointCount = _tickCountX * _tickCountY * _tickCountZ;
539    if (_pointCount != pointCount) {
540        _samplingPositions.clear();
541        _samplingPositions.reserve(pointCount);
542        _pointCount = pointCount;
543    }
544
545    createRenderTarget();
546
547    Vector3f pos;
548    Vector3f *pinfo = NULL;
549    if (_renderMode == GLYPHS) {
550        glBindBufferARB(GL_ARRAY_BUFFER_ARB, _vertexBufferGraphicsID);
551        pinfo = (Vector3f *)glMapBufferARB(GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB);
552    }
553
554    if (_axis == AXIS_Z) {
555        for (int y = 1; y <= _tickCountY; ++y) {
556            for (int x = 1; x <= _tickCountX; ++x) {
557                pos.x = (1.0f / (_tickCountX + 1)) * x;
558                pos.y = (1.0f / (_tickCountY + 1)) * y;
559                pos.z = _slicePos;
560                if (_renderMode == LINES) {
561                    _samplingPositions.push_back(pos);
562                } else {
563                    *pinfo = pos;
564                    ++pinfo;
565                }
566            }
567        }
568    } else if (_axis == AXIS_Y) {
569        for (int z = 1; z <= _tickCountZ; ++z) {
570            for (int x = 1; x <= _tickCountX; ++x) {
571                pos.x = (1.0f / (_tickCountX + 1)) * x;
572                pos.y = _slicePos;
573                pos.z = (1.0f / (_tickCountZ + 1)) * z;
574                if (_renderMode == LINES) {
575                    _samplingPositions.push_back(pos);
576                } else {
577                    *pinfo = pos;
578                    ++pinfo;
579                }
580            }
581        }
582    } else if (_axis == AXIS_X) {
583        for (int z = 1; z <= _tickCountZ; ++z) {
584            for (int y = 1; y <= _tickCountY; ++y) {
585                pos.x = _slicePos;
586                pos.y = (1.0f / (_tickCountY + 1)) * y;
587                pos.z = (1.0f / (_tickCountZ + 1)) * z;
588                if (_renderMode == LINES) {
589                    _samplingPositions.push_back(pos);
590                } else {
591                    *pinfo = pos;
592                    ++pinfo;
593                }
594            }
595        }
596    }
597
598    if (_renderMode == GLYPHS) {
599        glUnmapBufferARB(GL_ARRAY_BUFFER_ARB);
600    }
601}
Note: See TracBrowser for help on using the repository browser.