source: trunk/packages/vizservers/nanovis/VolumeRenderer.cpp @ 3464

Last change on this file since 3464 was 3452, checked in by ldelgass, 11 years ago

Remove XINETD define from nanovis. We only support server mode now, no glut
interaction loop with mouse/keyboard handlers. Fixes for trace logging to make
output closer to vtkvis: inlcude function name for trace messages, remove
newlines from format strings in macros since newlines get added by syslog.

  • Property svn:eol-style set to native
File size: 17.9 KB
RevLine 
[2798]1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
[406]2/*
3 * ----------------------------------------------------------------------
4 * VolumeRenderer.cpp : VolumeRenderer class for volume visualization
5 *
6 * ======================================================================
7 *  AUTHOR:  Wei Qiao <qiaow@purdue.edu>
8 *           Purdue Rendering and Perceptualization Lab (PURPL)
9 *
[3177]10 *  Copyright (c) 2004-2012  HUBzero Foundation, LLC
[406]11 *
12 *  See the file "license.terms" for information on usage and
13 *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14 * ======================================================================
15 */
[2822]16#include <stdlib.h>
[2973]17#include <float.h>
[2804]18
[2972]19#include <vector>
20
[2822]21#include <GL/glew.h>
22
23#include <tcl.h>
24
[2804]25#include "nanovis.h"
[2822]26#include "VolumeRenderer.h"
27#include "ConvexPolygon.h"
[617]28#include "NvStdVertexShader.h"
[884]29#include "Trace.h"
[406]30
[2974]31VolumeRenderer::VolumeRenderer()
[406]32{
[2877]33    initShaders();
[617]34
[884]35    _volumeInterpolator = new VolumeInterpolator();
[418]36}
37
[580]38VolumeRenderer::~VolumeRenderer()
39{
[3362]40    delete _cutplaneShader;
[617]41    delete _zincBlendeShader;
42    delete _regularVolumeShader;
43    delete _stdVertexShader;
[884]44    delete _volumeInterpolator;
[580]45}
[418]46
47//initialize the volume shaders
[2877]48void VolumeRenderer::initShaders()
[2822]49{
[3362]50    _cutplaneShader = new NvShader();
51    _cutplaneShader->loadVertexProgram("cutplane_vp.cg", "main");
52    _cutplaneShader->loadFragmentProgram("cutplane_fp.cg", "main");
53
[2822]54    //standard vertex program
55    _stdVertexShader = new NvStdVertexShader();
[406]56
[2822]57    //volume rendering shader: one cubic volume
58    _regularVolumeShader = new NvRegularVolumeShader();
[524]59
[2822]60    //volume rendering shader: one zincblende orbital volume.
61    //This shader renders one orbital of the simulation.
62    //A sim has S, P, D, SS orbitals. thus a full rendering requires 4 zincblende orbital volumes.
63    //A zincblende orbital volume is decomposed into 2 "interlocking" cubic 4-component volumes and passed to the shader.
64    //We render each orbital with a independent transfer functions then blend the result.
65    //
66    //The engine is already capable of rendering multiple volumes and combine them. Thus, we just invoke this shader on
67    //S, P, D and SS orbitals with different transfor functions. The result is a multi-orbital rendering.
68    _zincBlendeShader = new NvZincBlendeVolumeShader();
[406]69}
70
[1258]71struct SortElement {
72    float z;
[2877]73    int volumeId;
74    int sliceId;
[2822]75
[2804]76    SortElement(float _z, int _v, int _s) :
[2877]77        z(_z),
78        volumeId(_v),
79        sliceId(_s)
[2804]80    {}
[415]81};
82
[2877]83static int sliceSort(const void *a, const void *b)
[2822]84{
85    if ((*((SortElement*)a)).z > (*((SortElement*)b)).z)
[2804]86        return 1;
87    else
88        return -1;
[415]89}
90
[1258]91void
[2877]92VolumeRenderer::renderAll()
[580]93{
[1478]94    size_t total_rendered_slices = 0;
[900]95
[2877]96    if (_volumeInterpolator->isStarted()) {
[1478]97#ifdef notdef
[884]98        ani_vol = _volumeInterpolator->getVolume();
[1478]99        ani_tf = ani_vol->transferFunction();
100#endif
[2853]101        TRACE("VOLUME INTERPOLATOR IS STARTED ----------------------------");
[884]102    }
[1478]103    // Determine the volumes that are to be rendered.
[2804]104    std::vector<Volume *> volumes;
[1493]105    Tcl_HashEntry *hPtr;
106    Tcl_HashSearch iter;
107    for (hPtr = Tcl_FirstHashEntry(&NanoVis::volumeTable, &iter); hPtr != NULL;
[2853]108         hPtr = Tcl_NextHashEntry(&iter)) {
[3362]109        Volume *volPtr;
[2853]110        volPtr = (Volume *)Tcl_GetHashValue(hPtr);
[2804]111        if (!volPtr->visible()) {
[2853]112            continue; // Skip this volume
113        }
114        // BE CAREFUL: Set the number of slices to something slightly
115        // different for each volume.  If we have identical volumes at exactly
116        // the same position with exactly the same number of slices, the
117        // second volume will overwrite the first, so the first won't appear
118        // at all.
119        volumes.push_back(volPtr);
[2877]120        volPtr->numSlices(256 - volumes.size());
[1478]121    }
[884]122
[2932]123    glPushAttrib(GL_ENABLE_BIT);
124
[1258]125    //two dimension pointer array
[2853]126    ConvexPolygon ***polys = new ConvexPolygon**[volumes.size()];
[1258]127    //number of actual slices for each volume
[2932]128    size_t *actual_slices = new size_t[volumes.size()];
[3362]129    float *z_steps = new float[volumes.size()];
[884]130
[3452]131    TRACE("start loop %d", volumes.size());
[1478]132    for (size_t i = 0; i < volumes.size(); i++) {
[2853]133        Volume *volPtr = volumes[i];
[1478]134        polys[i] = NULL;
135        actual_slices[i] = 0;
[1429]136
[2877]137        int n_slices = volPtr->numSlices();
[1478]138        if (volPtr->isosurface()) {
[2853]139            // double the number of slices
140            n_slices <<= 1;
141        }
[2804]142
[900]143        //volume start location
[3362]144        Vector3 volPos = volPtr->location();
145        Vector3 volScaling = volPtr->getPhysicalScaling();
[2804]146
[3452]147        TRACE("VOL POS: %g %g %g",
[3362]148              volPos.x, volPos.y, volPos.z);
[3452]149        TRACE("VOL SCALE: %g %g %g",
[3362]150              volScaling.x, volScaling.y, volScaling.z);
151
[900]152        double x0 = 0;
153        double y0 = 0;
154        double z0 = 0;
[2804]155
[900]156        Mat4x4 model_view_no_trans, model_view_trans;
157        Mat4x4 model_view_no_trans_inverse, model_view_trans_inverse;
[2804]158
[2853]159        //initialize volume plane with world coordinates
[900]160        Plane volume_planes[6];
[2906]161        volume_planes[0].setCoeffs( 1,  0,  0, -x0);
162        volume_planes[1].setCoeffs(-1,  0,  0,  x0+1);
163        volume_planes[2].setCoeffs( 0,  1,  0, -y0);
164        volume_planes[3].setCoeffs( 0, -1,  0,  y0+1);
165        volume_planes[4].setCoeffs( 0,  0,  1, -z0);
166        volume_planes[5].setCoeffs( 0,  0, -1,  z0+1);
[2804]167
[2853]168        //get modelview matrix with no translation
[900]169        glPushMatrix();
[3362]170        glScalef(volScaling.x, volScaling.y, volScaling.z);
[2804]171
[900]172        glEnable(GL_DEPTH_TEST);
[2804]173
[900]174        GLfloat mv_no_trans[16];
175        glGetFloatv(GL_MODELVIEW_MATRIX, mv_no_trans);
[2804]176
[900]177        model_view_no_trans = Mat4x4(mv_no_trans);
178        model_view_no_trans_inverse = model_view_no_trans.inverse();
[2804]179
[900]180        glPopMatrix();
[2804]181
[2853]182        //get modelview matrix with translation
[900]183        glPushMatrix();
[3362]184        glTranslatef(volPos.x, volPos.y, volPos.z);
185        glScalef(volScaling.x, volScaling.y, volScaling.z);
186
[900]187        GLfloat mv_trans[16];
188        glGetFloatv(GL_MODELVIEW_MATRIX, mv_trans);
[2804]189
[900]190        model_view_trans = Mat4x4(mv_trans);
191        model_view_trans_inverse = model_view_trans.inverse();
[2804]192
[2853]193        //draw volume bounding box with translation (the correct location in
194        //space)
[1478]195        if (volPtr->outline()) {
[900]196            float olcolor[3];
[2877]197            volPtr->getOutlineColor(olcolor);
198            drawBoundingBox(x0, y0, z0, x0+1, y0+1, z0+1,
[2853]199                (double)olcolor[0], (double)olcolor[1], (double)olcolor[2],
200                1.5);
[900]201        }
202        glPopMatrix();
[2804]203
[3362]204        // transform volume_planes to eye coordinates.
205        // Need to transform without translation since we don't want
206        // to translate plane normals, just rotate them
[2804]207        for (size_t j = 0; j < 6; j++) {
[1478]208            volume_planes[j].transform(model_view_no_trans);
[2853]209        }
[2973]210        double eyeMinX, eyeMaxX, eyeMinY, eyeMaxY, zNear, zFar;
211        getEyeSpaceBounds(model_view_no_trans,
212                          eyeMinX, eyeMaxX,
213                          eyeMinY, eyeMaxY,
214                          zNear, zFar);
[2804]215
[2853]216        //compute actual rendering slices
217        float z_step = fabs(zNear-zFar)/n_slices;
[3362]218        z_steps[i] = z_step;
[1478]219        size_t n_actual_slices;
[2804]220
[2877]221        if (volPtr->dataEnabled()) {
[3362]222            if (z_step == 0.0f)
223                n_actual_slices = 1;
224            else
225                n_actual_slices = (int)(fabs(zNear-zFar)/z_step + 1);
[1478]226            polys[i] = new ConvexPolygon*[n_actual_slices];
[900]227        } else {
228            n_actual_slices = 0;
[1478]229            polys[i] = NULL;
[900]230        }
[1478]231        actual_slices[i] = n_actual_slices;
[2804]232
[3362]233        TRACE("near: %g far: %g eye space bounds: (%g,%g)-(%g,%g) z_step: %g slices: %d",
234              zNear, zFar, eyeMinX, eyeMaxX, eyeMinY, eyeMaxY, z_step, n_actual_slices);
[2804]235
[3362]236        Vector4 vert1, vert2, vert3, vert4;
237
[2853]238        // Render cutplanes first with depth test enabled.  They will mark the
239        // image with their depth values.  Then we render other volume slices.
240        // These volume slices will be occluded correctly by the cutplanes and
241        // vice versa.
[1825]242
[2877]243        for (int j = 0; j < volPtr->getCutplaneCount(); j++) {
244            if (!volPtr->isCutplaneEnabled(j)) {
[2853]245                continue;
246            }
[2877]247            float offset = volPtr->getCutplane(j)->offset;
248            int axis = volPtr->getCutplane(j)->orient;
[3362]249
250            switch (axis) {
251            case 1:
252                vert1 = Vector4(offset, 0, 0, 1);
253                vert2 = Vector4(offset, 1, 0, 1);
254                vert3 = Vector4(offset, 1, 1, 1);
255                vert4 = Vector4(offset, 0, 1, 1);
256                break;
257            case 2:
258                vert1 = Vector4(0, offset, 0, 1);
259                vert2 = Vector4(1, offset, 0, 1);
260                vert3 = Vector4(1, offset, 1, 1);
261                vert4 = Vector4(0, offset, 1, 1);
262                break;
263            case 3:
264            default:
265                vert1 = Vector4(0, 0, offset, 1);
266                vert2 = Vector4(1, 0, offset, 1);
267                vert3 = Vector4(1, 1, offset, 1);
268                vert4 = Vector4(0, 1, offset, 1);
269                break;
[2853]270            }
[2804]271
[3362]272            Vector4 texcoord1 = vert1;
273            Vector4 texcoord2 = vert2;
274            Vector4 texcoord3 = vert3;
275            Vector4 texcoord4 = vert4;
[2804]276
[3362]277            _cutplaneShader->bind();
278            _cutplaneShader->setFPTextureParameter("volume", volPtr->textureID());
279            _cutplaneShader->setFPTextureParameter("tf", volPtr->transferFunction()->id());
[2804]280
[2853]281            glPushMatrix();
[3362]282            glTranslatef(volPos.x, volPos.y, volPos.z);
283            glScalef(volScaling.x, volScaling.y, volScaling.z);
284            _cutplaneShader->setGLStateMatrixVPParameter("modelViewProjMatrix",
285                                                         NvShader::MODELVIEW_PROJECTION_MATRIX);
[2853]286            glPopMatrix();
[2804]287
[2853]288            glEnable(GL_DEPTH_TEST);
289            glDisable(GL_BLEND);
[2804]290
[3362]291            glBegin(GL_QUADS);
292            glTexCoord3f(texcoord1.x, texcoord1.y, texcoord1.z);
293            glVertex3f(vert1.x, vert1.y, vert1.z);
294            glTexCoord3f(texcoord2.x, texcoord2.y, texcoord2.z);
295            glVertex3f(vert2.x, vert2.y, vert2.z);
296            glTexCoord3f(texcoord3.x, texcoord3.y, texcoord3.z);
297            glVertex3f(vert3.x, vert3.y, vert3.z);
298            glTexCoord3f(texcoord4.x, texcoord4.y, texcoord4.z);
299            glVertex3f(vert4.x, vert4.y, vert4.z);
[2853]300            glEnd();
[3362]301
[2853]302            glDisable(GL_DEPTH_TEST);
[3362]303            _cutplaneShader->disableFPTextureParameter("tf");
304            _cutplaneShader->disableFPTextureParameter("volume");
305            _cutplaneShader->unbind();
[2853]306        } //done cutplanes
[2804]307
[3362]308        // Now prepare proxy geometry slices
[2853]309
[2973]310        // Initialize view-aligned quads with eye space bounds of
311        // volume
[3362]312        vert1 = Vector4(eyeMinX, eyeMinY, -0.5, 1);
313        vert2 = Vector4(eyeMaxX, eyeMinY, -0.5, 1);
314        vert3 = Vector4(eyeMaxX, eyeMaxY, -0.5, 1);
315        vert4 = Vector4(eyeMinX, eyeMaxY, -0.5, 1);
[2804]316
[1478]317        size_t counter = 0;
[2804]318
[3362]319        // Transform slices and store them
[900]320        float slice_z;
[1478]321        for (size_t j = 0; j < n_actual_slices; j++) {
[2853]322            slice_z = zFar + j * z_step; //back to front
[2804]323
[900]324            ConvexPolygon *poly = new ConvexPolygon();
[1478]325            polys[i][counter] = poly;
[900]326            counter++;
[2804]327
[900]328            poly->vertices.clear();
[2877]329            poly->setId(i);
[2804]330
[2973]331            // Set eye space Z-coordinate of slice
[900]332            vert1.z = slice_z;
333            vert2.z = slice_z;
334            vert3.z = slice_z;
335            vert4.z = slice_z;
[2804]336
[2877]337            poly->appendVertex(vert1);
338            poly->appendVertex(vert2);
339            poly->appendVertex(vert3);
340            poly->appendVertex(vert4);
[2804]341
342            for (size_t k = 0; k < 6; k++) {
[3362]343                if (!poly->clip(volume_planes[k], true))
344                    break;
[2853]345            }
[2804]346
[3362]347            if (poly->vertices.size() >= 3) {
348                poly->transform(model_view_no_trans_inverse);
349                poly->transform(model_view_trans);
[2853]350                total_rendered_slices++;
[3362]351            }
[900]352        }
[1258]353    } //iterate all volumes
[3452]354    TRACE("end loop");
[2804]355
[1493]356    // We sort all the polygons according to their eye-space depth, from
357    // farthest to the closest.  This step is critical for correct blending
[2804]358
[2853]359    SortElement *slices = (SortElement *)
360        malloc(sizeof(SortElement) * total_rendered_slices);
[2804]361
[1478]362    size_t counter = 0;
[2804]363    for (size_t i = 0; i < volumes.size(); i++) {
364        for (size_t j = 0; j < actual_slices[i]; j++) {
365            if (polys[i][j]->vertices.size() >= 3) {
[900]366                slices[counter] = SortElement(polys[i][j]->vertices[0].z, i, j);
367                counter++;
368            }
369        }
[884]370    }
[2804]371
[1258]372    //sort them
[2877]373    qsort(slices, total_rendered_slices, sizeof(SortElement), sliceSort);
[2804]374
[1258]375    //Now we are ready to render all the slices from back to front
[900]376    glEnable(GL_DEPTH_TEST);
[2932]377    // Non pre-multiplied alpha
378    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
[900]379    glEnable(GL_BLEND);
[2804]380
381    for (size_t i = 0; i < total_rendered_slices; i++) {
[2853]382        Volume *volPtr = NULL;
[1478]383
[2877]384        int volume_index = slices[i].volumeId;
385        int slice_index = slices[i].sliceId;
[3362]386        ConvexPolygon *currentSlice = polys[volume_index][slice_index];
387        float z_step = z_steps[volume_index];
[2804]388
[2853]389        volPtr = volumes[volume_index];
390
[3362]391        Vector3 volScaling = volPtr->getPhysicalScaling();
392
[900]393        glPushMatrix();
[3362]394        glScalef(volScaling.x, volScaling.y, volScaling.z);
[2804]395
[3362]396        // FIXME: compute view-dependent volume sample distance
397        double avgSampleDistance = 1.0 / pow(volPtr->width() * volScaling.x *
398                                             volPtr->height() * volScaling.y *
399                                             volPtr->depth() * volScaling.z, 1.0/3.0);
400        float sampleRatio = z_step / avgSampleDistance;
401
[1493]402#ifdef notdef
[3452]403        TRACE("shading slice: volume %s addr=%x slice=%d, volume=%d z_step=%g avgSD=%g",
[3362]404              volPtr->name(), volPtr, slice_index, volume_index, z_step, avgSampleDistance);
[1493]405#endif
[3362]406        activateVolumeShader(volPtr, false, sampleRatio);
[900]407        glPopMatrix();
[2804]408
[900]409        glBegin(GL_POLYGON);
[3362]410        currentSlice->emit(true);
[900]411        glEnd();
[884]412
[2877]413        deactivateVolumeShader();
[884]414    }
[2804]415
[2932]416    glPopAttrib();
[2804]417
[900]418    //Deallocate all the memory used
[2804]419    for (size_t i = 0; i < volumes.size(); i++) {
420        for (size_t j = 0; j <actual_slices[i]; j++) {
[900]421            delete polys[i][j];
422        }
[1258]423        if (polys[i]) {
[900]424            delete[] polys[i];
425        }
[452]426    }
[900]427    delete[] polys;
428    delete[] actual_slices;
[3362]429    delete[] z_steps;
[900]430    free(slices);
[415]431}
432
[1478]433void
[2877]434VolumeRenderer::drawBoundingBox(float x0, float y0, float z0,
435                                float x1, float y1, float z1,
436                                float r, float g, float b,
437                                float line_width)
[406]438{
[2932]439    glPushAttrib(GL_ENABLE_BIT);
440
[1028]441    glEnable(GL_DEPTH_TEST);
442    glDisable(GL_TEXTURE_2D);
443    glEnable(GL_BLEND);
[406]444
[2932]445    glMatrixMode(GL_MODELVIEW);
446    glPushMatrix();
447
[1028]448    glColor4d(r, g, b, 1.0);
449    glLineWidth(line_width);
[2804]450
[1028]451    glBegin(GL_LINE_LOOP);
452    {
[2853]453        glVertex3d(x0, y0, z0);
454        glVertex3d(x1, y0, z0);
455        glVertex3d(x1, y1, z0);
456        glVertex3d(x0, y1, z0);
[1028]457    }
458    glEnd();
[2804]459
[1028]460    glBegin(GL_LINE_LOOP);
461    {
[2853]462        glVertex3d(x0, y0, z1);
463        glVertex3d(x1, y0, z1);
464        glVertex3d(x1, y1, z1);
465        glVertex3d(x0, y1, z1);
[1028]466    }
467    glEnd();
[2853]468
[1028]469    glBegin(GL_LINE_LOOP);
470    {
[2853]471        glVertex3d(x0, y0, z0);
472        glVertex3d(x0, y0, z1);
473        glVertex3d(x0, y1, z1);
474        glVertex3d(x0, y1, z0);
[1028]475    }
476    glEnd();
[2804]477
[1028]478    glBegin(GL_LINE_LOOP);
479    {
[2853]480        glVertex3d(x1, y0, z0);
481        glVertex3d(x1, y0, z1);
482        glVertex3d(x1, y1, z1);
483        glVertex3d(x1, y1, z0);
[1028]484    }
485    glEnd();
[406]486
[1028]487    glPopMatrix();
[2932]488    glPopAttrib();
[406]489}
490
[1478]491void
[3362]492VolumeRenderer::activateVolumeShader(Volume *volPtr, bool sliceMode,
493                                     float sampleRatio)
[617]494{
[1478]495    //vertex shader
496    _stdVertexShader->bind();
[1493]497    TransferFunction *tfPtr  = volPtr->transferFunction();
[2877]498    if (volPtr->volumeType() == Volume::CUBIC) {
[3362]499        _regularVolumeShader->bind(tfPtr->id(), volPtr, sliceMode, sampleRatio);
[2877]500    } else if (volPtr->volumeType() == Volume::ZINCBLENDE) {
[3362]501        _zincBlendeShader->bind(tfPtr->id(), volPtr, sliceMode, sampleRatio);
[1478]502    }
[406]503}
[2877]504
505void VolumeRenderer::deactivateVolumeShader()
[580]506{
[617]507    _stdVertexShader->unbind();
508    _regularVolumeShader->unbind();
509    _zincBlendeShader->unbind();
[406]510}
511
[2973]512void VolumeRenderer::getEyeSpaceBounds(const Mat4x4& mv,
513                                       double& xMin, double& xMax,
514                                       double& yMin, double& yMax,
515                                       double& zNear, double& zFar)
[406]516{
[2822]517    double x0 = 0;
518    double y0 = 0;
519    double z0 = 0;
520    double x1 = 1;
521    double y1 = 1;
522    double z1 = 1;
[406]523
[2822]524    double zMin, zMax;
[2973]525    xMin = DBL_MAX;
526    xMax = -DBL_MAX;
527    yMin = DBL_MAX;
528    yMax = -DBL_MAX;
529    zMin = DBL_MAX;
530    zMax = -DBL_MAX;
[406]531
[2822]532    double vertex[8][4];
[406]533
[2822]534    vertex[0][0]=x0; vertex[0][1]=y0; vertex[0][2]=z0; vertex[0][3]=1.0;
535    vertex[1][0]=x1; vertex[1][1]=y0; vertex[1][2]=z0; vertex[1][3]=1.0;
536    vertex[2][0]=x0; vertex[2][1]=y1; vertex[2][2]=z0; vertex[2][3]=1.0;
537    vertex[3][0]=x0; vertex[3][1]=y0; vertex[3][2]=z1; vertex[3][3]=1.0;
538    vertex[4][0]=x1; vertex[4][1]=y1; vertex[4][2]=z0; vertex[4][3]=1.0;
539    vertex[5][0]=x1; vertex[5][1]=y0; vertex[5][2]=z1; vertex[5][3]=1.0;
540    vertex[6][0]=x0; vertex[6][1]=y1; vertex[6][2]=z1; vertex[6][3]=1.0;
541    vertex[7][0]=x1; vertex[7][1]=y1; vertex[7][2]=z1; vertex[7][3]=1.0;
[406]542
[2822]543    for (int i = 0; i < 8; i++) {
[2973]544        Vector4 eyeVert = mv.transform(Vector4(vertex[i][0],
545                                               vertex[i][1],
546                                               vertex[i][2],
547                                               vertex[i][3]));
548        if (eyeVert.x < xMin) xMin = eyeVert.x;
549        if (eyeVert.x > xMax) xMax = eyeVert.x;
550        if (eyeVert.y < yMin) yMin = eyeVert.y;
551        if (eyeVert.y > yMax) yMax = eyeVert.y;
552        if (eyeVert.z < zMin) zMin = eyeVert.z;
553        if (eyeVert.z > zMax) zMax = eyeVert.z;
[2822]554    }
[406]555
[2822]556    zNear = zMax;
557    zFar = zMin;
[406]558}
Note: See TracBrowser for help on using the repository browser.