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

Last change on this file since 2927 was 2927, checked in by ldelgass, 12 years ago

Style fixes and GL state leak fixes in HeightMap?

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