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

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

Fix a couple more GL state leaks, AxisRange? method rename

  • 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    min = 0.0, max = 1.0;
374    xMin = yMin = min = 0.0;
375    xMax = yMax = max = 1.0;
376    // Save the scales.
377    _scale.x = _scale.y = _scale.z = 1.0;
378
379    updatePending = true;
380
381    _centerPoint.set(0.5, 0.5, 0.5);
382   
383#ifndef notdef
384    Vector3* texcoord = new Vector3[_vertexCount];
385    for (int i = 0; i < _vertexCount; ++i) {
386        texcoord[i].set(0, 0, heights[i]);
387    }
388   
389    Vector3* map = createHeightVertices(xMin, yMin, xMax, yMax, xNum, yNum,
390                                        heights);
391   
392    glGenBuffers(1, &_vertexBufferObjectID);
393    glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferObjectID);
394    glBufferData(GL_ARRAY_BUFFER, _vertexCount * sizeof(Vector3), map,
395        GL_STATIC_DRAW);
396    glGenBuffers(1, &_textureBufferObjectID);
397    glBindBuffer(GL_ARRAY_BUFFER, _textureBufferObjectID);
398    glBufferData(GL_ARRAY_BUFFER, _vertexCount * sizeof(float) * 3, texcoord,
399        GL_STATIC_DRAW);
400    glBindBuffer(GL_ARRAY_BUFFER, 0);
401   
402    delete [] texcoord;
403   
404   
405    if (_contour != NULL) {
406        delete _contour;
407        _contour = NULL;
408    }
409    ContourLineFilter lineFilter;
410    //lineFilter.transferFunction(_tfPtr);
411    _contour = lineFilter.create(0.0f, 1.0f, 10, map, xNum, yNum);
412   
413#if TOPCONTOUR
414    ContourLineFilter topLineFilter;
415    topLineFilter.setHeightTop(true);
416    _topContour = topLineFilter.create(0.0f, 1.0f, 10, map, xNum, yNum);
417#endif
418    this->createIndexBuffer(xNum, yNum, heights);
419    delete [] map;
420#endif
421}
422
423Vector3 *
424HeightMap::createHeightVertices(float xMin, float yMin, float xMax,
425                                float yMax, int xNum, int yNum, float *height)
426{
427    Vector3* vertices = new Vector3[xNum * yNum];
428
429    Vector3* dstDataPtr = vertices;
430    float* srcDataPtr = height;
431   
432    for (int y = 0; y < yNum; ++y) {
433        float yCoord;
434
435        yCoord = yMin + ((yMax - yMin) * y) / (yNum - 1);
436        for (int x = 0; x < xNum; ++x) {
437            float xCoord;
438
439            xCoord = xMin + ((xMax - xMin) * x) / (xNum - 1);
440            dstDataPtr->set(xCoord, *srcDataPtr, yCoord);
441
442            ++dstDataPtr;
443            ++srcDataPtr;
444        }
445    }
446    return vertices;
447}
448
449/**
450 * \brief Maps the data coordinates of the surface into the grid's axes.
451 */
452void
453HeightMap::mapToGrid(Grid *gridPtr)
454{
455    int count = _xNum * _yNum;
456
457    reset();
458
459    // The range of the grid's y-axis 0..1 represents the distance between the
460    // smallest and largest major ticks for all surfaces plotted.  Translate
461    // this surface's y-values (heights) into the grid's axis coordinates.
462
463    float yScale = 1.0 / (gridPtr->yAxis.max() - gridPtr->yAxis.min());
464    float *p, *q, *pend;
465    float *normHeights = new float[count];
466    for (p = _heights, pend = p + count, q = normHeights; p < pend; p++, q++) {
467        *q = (*p - gridPtr->yAxis.min()) * yScale;
468    }
469    Vector3 *t, *texcoord;
470    texcoord = new Vector3[count];
471    for (t = texcoord, p = normHeights, pend = p + count; p < pend; p++, t++) {
472        t->set(0, 0, *p);
473    }
474
475    // Normalize the mesh coordinates (x and z min/max) the range of the major
476    // ticks for the x and z grid axes as well.
477
478    float xScale, zScale;
479    float xMin, xMax, zMin, zMax;
480
481    xScale = 1.0 / (gridPtr->xAxis.max() - gridPtr->xAxis.min());
482    xMin = (xAxis.min() - gridPtr->xAxis.min()) * xScale;
483    xMax = (xAxis.max() - gridPtr->xAxis.min()) * xScale;
484    zScale = 1.0 / (gridPtr->zAxis.max() - gridPtr->zAxis.min());
485    zMin = (zAxis.min() - gridPtr->zAxis.min()) * zScale;
486    zMax = (zAxis.max() - gridPtr->zAxis.min()) * zScale;
487
488    Vector3* vertices;
489    vertices = createHeightVertices(xMin, zMin, xMax, zMax, _xNum, _yNum,
490        normHeights);
491   
492    glGenBuffers(1, &_vertexBufferObjectID);
493    glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferObjectID);
494    glBufferData(GL_ARRAY_BUFFER, _vertexCount * sizeof(Vector3), vertices,
495        GL_STATIC_DRAW);
496    glGenBuffers(1, &_textureBufferObjectID);
497    glBindBuffer(GL_ARRAY_BUFFER, _textureBufferObjectID);
498    glBufferData(GL_ARRAY_BUFFER, _vertexCount * sizeof(float) * 3, texcoord,
499        GL_STATIC_DRAW);
500    glBindBuffer(GL_ARRAY_BUFFER, 0);
501    delete [] texcoord;
502
503    if (_contour != NULL) {
504        delete _contour;
505        _contour = NULL;
506    }
507    ContourLineFilter lineFilter;
508    //lineFilter.transferFunction(_tfPtr);
509    _contour = lineFilter.create(0.0f, 1.0f, 10, vertices, _xNum, _yNum);
510   
511#if TOPCONTOUR
512    ContourLineFilter topLineFilter;
513    topLineFilter.setHeightTop(true);
514    _topContour = topLineFilter.create(0.0f, 1.0f, 10, vertices, _xNum, _yNum);
515#endif
516    this->createIndexBuffer(_xNum, _yNum, normHeights);
517    delete [] normHeights;
518    delete [] vertices;
519}
520
521void
522HeightMap::renderTopview(graphics::RenderContext* renderContext,
523                         int render_width, int render_height)
524{
525    glClear(GL_COLOR_BUFFER_BIT);
526    glPushAttrib(GL_VIEWPORT_BIT);
527    glViewport(0, 0, render_width, render_height);
528    glMatrixMode(GL_PROJECTION);
529    glPushMatrix();
530    glLoadIdentity();
531    //gluOrtho2D(0, render_width, 0, render_height);
532    glOrtho(-.5, .5, -.5, .5, -50, 50);
533    glMatrixMode(GL_MODELVIEW);
534    glPushMatrix();
535    glLoadIdentity();
536
537    glTranslatef(0.0, 0.0, -10.0);
538
539    // put camera rotation and translation
540    //glScalef(1 / _scale.x, 1 / _scale.y , 1 / _scale.z);
541
542    if (renderContext->getCullMode() == graphics::RenderContext::NO_CULL) {
543        glDisable(GL_CULL_FACE);
544    } else {
545        glEnable(GL_CULL_FACE);
546        glCullFace((GLuint) renderContext->getCullMode());
547    }
548
549    glPolygonMode(GL_FRONT_AND_BACK, (GLuint) renderContext->getPolygonMode());
550    glShadeModel((GLuint) renderContext->getShadingModel());
551
552    glPushMatrix();
553
554    //glTranslatef(-_centerPoint.x, -_centerPoint.y, -_centerPoint.z);
555
556    //glScalef(0.01, 0.01, 0.01f);
557    glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
558    glTranslatef(-_centerPoint.x, -_centerPoint.y, -_centerPoint.z);
559    if (_contour != NULL) {
560        glDepthRange (0.001, 1.0);
561    }
562       
563    glEnable(GL_DEPTH_TEST);
564
565    glEnable(GL_BLEND);
566    glEnable(GL_TEXTURE_2D);
567    if (_vertexBufferObjectID)
568    {
569        TRACE("TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT\n");
570        glColor3f(1.0f, 1.0f, 1.0f);
571        glShadeModel(GL_SMOOTH);
572        glEnable(GL_BLEND);
573        glEnableClientState(GL_VERTEX_ARRAY);
574        glDisableClientState(GL_COLOR_ARRAY);
575        glDisableClientState(GL_INDEX_ARRAY);
576        glDisableClientState(GL_NORMAL_ARRAY);
577       
578        if (_tfPtr != NULL) {
579            _shader->bind();
580           
581            cgGLSetTextureParameter(_tfParam, _tfPtr->id());
582            cgGLEnableTextureParameter(_tfParam);
583
584            glEnable(GL_TEXTURE_1D);
585            _tfPtr->getTexture()->activate();
586
587            glEnableClientState(GL_TEXTURE_COORD_ARRAY);
588        } else {
589            glDisableClientState(GL_TEXTURE_COORD_ARRAY);
590        }
591
592        glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferObjectID);
593        glVertexPointer(3, GL_FLOAT, 12, 0);
594
595        glBindBuffer(GL_ARRAY_BUFFER, _textureBufferObjectID);
596        glTexCoordPointer(3, GL_FLOAT, 12, 0);
597
598#define _TRIANGLES_
599#ifdef _TRIANGLES_
600        glDrawElements(GL_TRIANGLES, _indexCount, GL_UNSIGNED_INT,
601                       _indexBuffer);
602#else
603        glDrawElements(GL_QUADS, _indexCount, GL_UNSIGNED_INT,
604                       _indexBuffer);
605#endif
606
607        glBindBuffer(GL_ARRAY_BUFFER, 0);
608
609        glDisableClientState(GL_VERTEX_ARRAY);
610        if (_tfPtr != NULL) {
611            _tfPtr->getTexture()->deactivate();
612            glDisableClientState(GL_TEXTURE_COORD_ARRAY);
613
614            _shader->unbind();
615        }
616    }
617
618    glShadeModel(GL_FLAT);
619    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
620
621    if (_contour != NULL) {
622        if (_contourVisible) {
623            glDisable(GL_BLEND);
624            glDisable(GL_TEXTURE_2D);
625            glColor4f(_contourColor.x, _contourColor.y, _contourColor.z, 1.0f);
626            glDepthRange (0.0, 0.999);
627            _contour->render();
628            glDepthRange (0.0, 1.0);
629        }
630
631#if TOPCONTOUR
632        if (_topContourVisible) {
633            glDisable(GL_BLEND);
634            glDisable(GL_TEXTURE_2D);
635            glColor4f(_contourColor.x, _contourColor.y, _contourColor.z, 1.0f);
636            //glDepthRange (0.0, 0.999);
637            _topContour->render();
638            //glDepthRange (0.0, 1.0);
639        }
640#endif
641    }
642   
643    glPopMatrix();
644    glPopMatrix();
645
646    glMatrixMode(GL_PROJECTION);
647    glPushMatrix();
648    glMatrixMode(GL_MODELVIEW);
649
650    glPopAttrib();
651}
Note: See TracBrowser for help on using the repository browser.