source: nanovis/trunk/VelocityArrowsSlice.cpp @ 4902

Last change on this file since 4902 was 4167, checked in by ldelgass, 10 years ago

Texture environment is not part of texture object stored state, so don't set
environment mode before loading textures, should be done at render time.

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