Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: orxonox.OLD/trunk/src/lib/graphics/importer/height_map.cc @ 7469

Last change on this file since 7469 was 7469, checked in by patrick, 18 years ago

orxonox: introduced some new variable names, some code cleanup

  • Property svn:executable set to *
File size: 18.6 KB
Line 
1/*
2   orxonox - the future of 3D-vertical-scrollers
3
4   Copyright (C) 2004 orx
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 2, or (at your option)
9   any later version.
10
11   ### File Specific:
12   main-programmer: bottac@ee.ethz.ch
13
14   review: patrick boenzli, patrick@orxonox.ethz.ch
15*/
16
17#include "height_map.h"
18#include "model.h"
19#include "texture.h"
20#include "vector.h"
21#include "material.h"
22#include "p_node.h"
23#include "state.h"
24#include "util/loading/resource_manager.h"
25#include "debug.h"
26
27// INCLUDING SDL_Image
28#ifdef HAVE_SDL_IMAGE_H
29#include <SDL_image.h>
30#else
31#include <SDL/SDL_image.h>
32#endif
33
34
35/**
36 * default constructor
37 *  @param i1
38 */
39Tile::Tile(int i1, int j1, int i2, int j2, HeightMap* heightMapReference )
40{
41  PRINTF(0)("Tile Constructor\n");
42  this->highResModel = new VertexArrayModel();
43  this->lowResModel  = new VertexArrayModel();
44
45  this->heightMapReference = heightMapReference;
46
47  // create high res model
48  this->load(i1, j1, i2, j2, this->highResModel, 4);
49  // create low res model
50  this->load(i1, j1, i2, j2, this->lowResModel, 8);
51}
52
53
54Tile::~Tile()
55{
56  if( highResModel)
57    delete highResModel;
58  if( lowResModel)
59    delete lowResModel;
60}
61
62
63/**
64 * this darws the tile in diefferent resolutions
65 */
66void Tile::draw()
67{
68  // draw the tile depending on the distance from the camera with different LOD (level of details)
69  float cameraDistance = fabs((State::getCameraNode()->getAbsCoor() - Vector(this->x, heightMapReference->offsetY , this->z) ).len());
70
71  if (cameraDistance < HM_LOD_HIGH_RES )
72  {
73    this->drawHighRes();
74  }
75  else if( cameraDistance < HM_LOD_LOW_RES)
76  {
77    this->drawLowRes();
78  }
79}
80
81
82/**
83 * loads a tile
84 */
85void Tile::load(int i1, int j1, int i2, int j2, VertexArrayModel* model, int sampleRate)
86{
87
88// #define heightMap this->heightMapReference->heightMap
89#define colours   this->heightMapReference->colours
90#define scaleX this->heightMapReference->scaleX
91#define scaleY this->heightMapReference->scaleY
92#define scaleZ this->heightMapReference->scaleZ
93#define shiftX this->heightMapReference->shiftX
94#define shiftY this->heightMapReference->shiftY
95#define shiftZ this->heightMapReference->shiftZ
96#define normalVectorField this->heightMapReference->normalVectorField
97
98  this->x = this->heightMapReference->offsetX + ( this->heightMapReference->heightMap->h - ((i1 + i2) / 2)) * scaleX;
99  this->z = this->heightMapReference->offsetZ + ((j1 + j2 ) / 2 ) * scaleZ;
100
101  float height = 0;
102  int offset = 0;
103
104  float r = 0.0;
105  float g = 0.0;
106  float b = 0.0;
107
108
109
110
111  if(this->heightMapReference->heightMap != NULL && this->heightMapReference->heightMap->format->BitsPerPixel == 8 )
112
113  SDL_LockSurface(this->heightMapReference->heightMap);
114  SDL_LockSurface(this->heightMapReference->colourMap);
115
116  for(int i = i1 ; i <= i2  ; i +=sampleRate)
117  {
118    int w = 0;
119
120
121
122    if(this->heightMapReference->hasColourMap)
123    {
124      r = colours[(3*w+2 + 3*i*(this->heightMapReference->heightMap->w )) ];
125      g = colours[(3*w+1 + 3*i*(this->heightMapReference->heightMap->w)) ];
126      b = colours[(3*w+0 + 3*i*(this->heightMapReference->heightMap->w))];
127    }
128
129    w = j1;
130    model->addVertex(scaleX*(this->heightMapReference->heightMap->h -i)+ shiftX,shiftY,scaleZ*(w)+ shiftZ); // Top Right
131    model->addNormal(normalVectorField[i % this->heightMapReference->heightMap->h][w % this->heightMapReference->heightMap->w].y,normalVectorField[i % this->heightMapReference->heightMap->h][w % this->heightMapReference->heightMap->w].z,normalVectorField[i % this->heightMapReference->heightMap->h][w % this->heightMapReference->heightMap->w].x);
132    model->addTexCoor((float)(j1-sampleRate) /(texRate), (float)(i %this->heightMapReference->heightMap->h)/(texRate));
133
134    model->addColor(r/255.0,g/255.0,b/255.0);
135
136    for(int j = j1  ; j <= j2    ;  j += sampleRate)
137    {
138
139
140      // To be fixed
141      if(this->heightMapReference->hasColourMap)
142      {
143        r = colours[(3*j+2 + 3*i*(this->heightMapReference->heightMap->w )) ];
144        g =  colours[(3*j+1 + 3*i*(this->heightMapReference->heightMap->w)) ];
145        b =  colours[(3*j+0 + 3*i*(this->heightMapReference->heightMap->w))];
146      }
147      height = (float)(unsigned char) this->heightMapReference->heights[(j +sampleRate+ i*(this->heightMapReference->heightMap->w )) ];
148      height += (float)(unsigned char) this->heightMapReference->heights[(j+ 1 + sampleRate + (i+1)*(this->heightMapReference->heightMap->w )) ];
149      height +=  (float) (unsigned char) this->heightMapReference->heights[(j -1+ sampleRate   + (i+1)*(this->heightMapReference->heightMap->w ))];
150      height +=  (float)(unsigned char)this->heightMapReference->heights[(j +sampleRate+ (i+2)*(this->heightMapReference->heightMap->w )) ];
151      height +=  (float)(unsigned char)this->heightMapReference->heights[(j+sampleRate + (i)*(this->heightMapReference->heightMap->w )) ];
152      height=height/5.0;
153
154      model->addVertex(scaleX*(this->heightMapReference->heightMap->h -i) + shiftX ,((double)(height)*scaleY) + shiftY ,scaleZ*(j) + shiftZ); // Top Right
155      model->addNormal(normalVectorField[i % this->heightMapReference->heightMap->h][j % this->heightMapReference->heightMap->w].y,normalVectorField[i % this->heightMapReference->heightMap->h][j % this->heightMapReference->heightMap->w].z,normalVectorField[i % this->heightMapReference->heightMap->h][j % this->heightMapReference->heightMap->w].x);
156      model->addTexCoor((float)(j) /(texRate), (float)(i %this->heightMapReference->heightMap->h)/(texRate));
157
158      //PRINTF(0)("TexCoord:  %f %f \n",(float)j / 100.0, (float)(i %this->heightMapReference->h)/100.0);
159
160      model->addColor(r/255.0,g/255.0,b/255.0);
161      w = j;
162    }
163
164
165    model->addVertex(scaleX*(this->heightMapReference->heightMap->h -i)+ shiftX,shiftY,scaleZ*(w)+ shiftZ); // Top Right
166    model->addNormal(normalVectorField[i % this->heightMapReference->heightMap->h][w % this->heightMapReference->heightMap->w].y,normalVectorField[i % this->heightMapReference->heightMap->h][w % this->heightMapReference->heightMap->w].z,normalVectorField[i% this->heightMapReference->heightMap->h][w % this->heightMapReference->heightMap->w].x);
167    model->addTexCoor((float)(j2+sampleRate) /(texRate), (float)(i %this->heightMapReference->heightMap->h)/(texRate));
168    model->addColor(r/255.0,g/255.0,b/255.0);
169
170  }
171
172
173
174
175
176  SDL_UnlockSurface(this->heightMapReference->heightMap);
177  int cnt = 0;
178  for(int i = i1   ; i < i2  ; i +=sampleRate)
179  {
180
181    for(int j = j1-sampleRate  ; j < j2  + 2*sampleRate  ;  j += sampleRate)
182    {
183
184      model->addIndice(cnt);
185      model->addIndice(cnt  + (j2 -j1 + 3* sampleRate  )/ sampleRate );
186      cnt++;
187
188    }
189
190
191
192    model->newStripe();
193
194
195  }
196  cnt += (j2 -j1 + 3* sampleRate)/ sampleRate;
197
198  for(int j = j1 ; j <= j2    ;  j += sampleRate)
199  {
200    int i = i1;
201
202    // To be fixed
203    if(this->heightMapReference->hasColourMap)
204    {
205      r = (float)colours[(3*j+2 + 3*i*(this->heightMapReference->heightMap->w )) ];
206      g = (float)colours[(3*j+1 + 3*i*(this->heightMapReference->heightMap->w)) ];
207      b = (float)colours[(3*j+0 + 3*i*(this->heightMapReference->heightMap->w))];
208    }
209
210    model->addVertex(scaleX*(this->heightMapReference->heightMap->h -i) + shiftX , shiftY ,scaleZ*(j) + shiftZ); // Top Right
211    model->addNormal(normalVectorField[i % this->heightMapReference->heightMap->h][j % this->heightMapReference->heightMap->w].y,normalVectorField[i % this->heightMapReference->heightMap->h][j % this->heightMapReference->heightMap->w].z,normalVectorField[i % this->heightMapReference->heightMap->h][j % this->heightMapReference->heightMap->w].x);
212    model->addTexCoor((float)j /(texRate), (float)((i - sampleRate) %this->heightMapReference->heightMap->h)/(texRate));
213    model->addColor(r/255.0,g/255.0,b/255.0);
214
215  }
216
217  for(int j = j1  ; j <= j2    ;  j += sampleRate)
218  {
219    int i = i1;
220    height = (float)(unsigned char) this->heightMapReference->heights[(j +sampleRate+ i*(this->heightMapReference->heightMap->w )) ];
221    height += (float)(unsigned char) this->heightMapReference->heights[(j+ 1 + sampleRate + (i+1)*(this->heightMapReference->heightMap->w )) ];
222    height +=  (float) (unsigned char) this->heightMapReference->heights[(j -1+ sampleRate   + (i+1)*(this->heightMapReference->heightMap->w ))];
223    height +=  (float)(unsigned char)this->heightMapReference->heights[(j +sampleRate+ (i+2)*(this->heightMapReference->heightMap->w )) ];
224    height +=  (float)(unsigned char)this->heightMapReference->heights[(j+sampleRate + (i)*(this->heightMapReference->heightMap->w )) ];
225    height=height/5.0;
226
227    model->addVertex(scaleX*(this->heightMapReference->heightMap->h -i) + shiftX , ((double)(height)*scaleY) +shiftY ,scaleZ*(j) + shiftZ); // Top Right
228    model->addNormal(normalVectorField[i % this->heightMapReference->heightMap->h][j % this->heightMapReference->heightMap->w].y,normalVectorField[i % this->heightMapReference->heightMap->h][j% this->heightMapReference->heightMap->w].z,normalVectorField[i%this->heightMapReference->heightMap->h][j%this->heightMapReference->heightMap->w].x);
229    model->addTexCoor((float)j /(texRate), (float)(i %this->heightMapReference->heightMap->h)/(texRate));
230    model->addColor(r/255.0,g/255.0,b/255.0);
231
232  }
233
234
235
236  for(int j = j1  ; j <= j2    ;  j += sampleRate)
237  {
238    int i = i2;
239
240    // To be fixed
241    if(this->heightMapReference->hasColourMap)
242    {
243      r = (float)colours[3*j+2 + 3*i*(this->heightMapReference->heightMap->w )];
244      g = (float)colours[3*j+1 + 3*i*(this->heightMapReference->heightMap->w)];
245      b = (float)colours[3*j+0 + 3*i*(this->heightMapReference->heightMap->w)];
246    }
247
248    model->addVertex(scaleX*(this->heightMapReference->heightMap->h -i) + shiftX , shiftY ,scaleZ*(j) + shiftZ); // Top Right
249    model->addNormal(normalVectorField[i%this->heightMapReference->heightMap->h][j%this->heightMapReference->heightMap->w].y,normalVectorField[i%this->heightMapReference->heightMap->h][j%this->heightMapReference->heightMap->w].z,normalVectorField[i%this->heightMapReference->heightMap->h][j%this->heightMapReference->heightMap->w].x);
250    model->addTexCoor((float)j /(texRate), (float)((i+ sampleRate) %this->heightMapReference->heightMap->h)/(texRate));
251    model->addColor(r/255.0,g/255.0,b/255.0);
252
253  }
254
255
256  for(int j = j1 ; j <= j2    ;  j += sampleRate)
257  {
258    int i = i2;
259    height = (float)(unsigned char) this->heightMapReference->heights[(j +sampleRate+ i*(this->heightMapReference->heightMap->w )) ];
260    height += (float)(unsigned char) this->heightMapReference->heights[(j+ 1 + sampleRate + (i+1)*(this->heightMapReference->heightMap->w )) ];
261    height +=  (float) (unsigned char) this->heightMapReference->heights[(j -1+ sampleRate   + (i+1)*(this->heightMapReference->heightMap->w ))];
262    height +=  (float)(unsigned char)this->heightMapReference->heights[(j +sampleRate+ (i+2)*(this->heightMapReference->heightMap->w ))];
263    height +=  (float)(unsigned char)this->heightMapReference->heights[(j+sampleRate + (i)*(this->heightMapReference->heightMap->w )) ];
264    height=height/5.0;
265    model->addVertex(scaleX*(this->heightMapReference->heightMap->h -i) + shiftX , ((double)(height)*scaleY) +shiftY ,scaleZ*(j) + shiftZ); // Top Right
266    model->addNormal(normalVectorField[i%this->heightMapReference->heightMap->h][j%this->heightMapReference->heightMap->w].y,normalVectorField[i%this->heightMapReference->heightMap->h][j%this->heightMapReference->heightMap->w].z,normalVectorField[i%this->heightMapReference->heightMap->h][j%this->heightMapReference->heightMap->w].x);
267    model->addTexCoor((float)j /(texRate), (float)(i %this->heightMapReference->heightMap->h)/(texRate));
268    model->addColor(r/255.0,g/255.0,b/255.0);
269
270  }
271
272
273
274
275  // link Boarder Stripe
276  for(int j = j1-sampleRate  ; j < j2    ;  j += sampleRate)
277  {
278
279    model->addIndice(cnt);
280    model->addIndice(cnt  + (j2 -j1 +  sampleRate  )/ sampleRate );
281    cnt++;
282
283  }
284
285  cnt++;
286
287  model->newStripe();
288
289
290
291
292
293  cnt += (j2-j1)/ sampleRate;
294
295  // link 2nd BoarderStripe
296  for(int j = j1-sampleRate  ; j < j2    ;  j += sampleRate)
297  {
298
299    model->addIndice(cnt);
300    model->addIndice(cnt  + (j2 -j1 +  sampleRate  )/ sampleRate );
301    cnt++;
302
303  }
304
305
306  SDL_UnlockSurface(this->heightMapReference->colourMap);
307
308  model->finalize();
309
310
311
312
313// #undef heightMap
314        #undef colours
315        #undef scaleX
316        #undef scaleY
317        #undef scaleZ
318        #undef shiftX
319        #undef shiftY
320        #undef shiftZ
321        #undef normalVectorField
322
323}
324
325
326HeightMap::HeightMap()
327{
328}
329
330
331HeightMap::HeightMap(const std::string& height_map_name = "")
332    : VertexArrayModel()
333{
334  this->setClassID(CL_HEIGHT_MAP, "HeightMap");
335  heightMap =  IMG_Load(height_map_name.c_str());
336  if(heightMap!=NULL)
337  {
338
339    PRINTF(0)("loading Image %s\n", height_map_name.c_str());
340    PRINTF(0)("width : %i\n", heightMap->w);
341    PRINTF(0)("height : %i\n", heightMap->h);
342    PRINTF(0)("%i Byte(s) per Pixel \n", heightMap->format->BytesPerPixel);
343    PRINTF(0)("Rshift : %i\n", heightMap->format->Rshift);
344    PRINTF(0)("Bshift: %i\n", heightMap->format->Bshift);
345    PRINTF(0)("Gshift: %i\n", heightMap->format->Gshift);
346    PRINTF(0)("Rmask: %i\n", heightMap->format->Rmask);
347    PRINTF(0)("Gmask: %i\n", heightMap->format->Gmask);
348  }
349
350  else
351    PRINTF(4)("oops! couldn't load %s for some reason.\n", height_map_name.c_str());
352
353
354  generateNormalVectorField();
355
356  shiftX = 0;
357  shiftY = 0;
358  shiftZ = 0;
359
360}
361
362HeightMap::HeightMap(const std::string& height_map_name = NULL, const std::string& colour_map_name = NULL)
363    : VertexArrayModel()
364{
365  this->setClassID(CL_HEIGHT_MAP, "HeightMap");
366
367  heightMap =  IMG_Load(height_map_name.c_str());
368  if(heightMap!=NULL)
369  {
370
371    PRINTF(0)("loading Image %s\n", height_map_name.c_str());
372    PRINTF(0)("width : %i\n", heightMap->w);
373    PRINTF(0)("height : %i\n", heightMap->h);
374    PRINTF(0)("%i Byte(s) per Pixel \n", heightMap->format->BytesPerPixel);
375    PRINTF(0)("Rshift : %i\n", heightMap->format->Rshift);
376    PRINTF(0)("Bshift: %i\n", heightMap->format->Bshift);
377    PRINTF(0)("Gshift: %i\n", heightMap->format->Gshift);
378    PRINTF(0)("Rmask: %i\n", heightMap->format->Rmask);
379    PRINTF(0)("Gmask: %i\n", heightMap->format->Gmask);
380  }
381
382  else
383    PRINTF(4)("oops! couldn't load %s for some reason.\n", height_map_name.c_str());
384
385
386  generateNormalVectorField();
387
388  colourMap=NULL;
389  if(colour_map_name != "")
390  {
391    colourMap = IMG_Load(colour_map_name.c_str());
392  }
393
394  if(colourMap != NULL)
395  {
396    PRINTF(0)("loading Image %s\n", colour_map_name.c_str());
397    PRINTF(0)("width : %i\n", colourMap->w);
398    PRINTF(0)("height : %i\n", colourMap->h);
399    PRINTF(0)("%i Byte(s) per Pixel \n", colourMap->format->BytesPerPixel);
400    PRINTF(0)("Rshift : %i\n", colourMap->format->Rshift);
401    PRINTF(0)("Bshift: %i\n", colourMap->format->Bshift);
402    PRINTF(0)("Gshift: %i\n", colourMap->format->Gshift);
403    PRINTF(0)("Rmask: %i\n", colourMap->format->Rmask);
404    PRINTF(0)("Gmask: %i\n", colourMap->format->Gmask);
405  }
406  else
407    PRINTF(0)("oops! couldn't load colorMap for some reason.\n");
408
409
410
411  if(colourMap != NULL)
412  {
413    colours = (unsigned char *) colourMap->pixels;
414    hasColourMap = true;
415  }
416  else
417    hasColourMap = false;
418
419
420  heights  = (unsigned char*) heightMap->pixels;
421  shiftX = 0;
422  shiftY = 0;
423  shiftZ = 0;
424}
425
426
427HeightMap::~HeightMap()
428{
429  delete heightMap;
430  delete colourMap;
431
432
433  for(int i=0;i <    heightMap->h/tileSize ; i++)
434  {
435    for(int j = 0; j < heightMap->w/ tileSize; j++)
436    {
437      delete tiles [i][j];
438    }
439  }
440  for(int i=0;i <    heightMap->h/tileSize ; i++)
441    delete[] tiles[i];
442  delete[] tiles;
443
444
445
446
447
448  for(int i=0;i<heightMap->h;i++)
449    delete[] normalVectorField [i];
450  delete[] normalVectorField;
451
452
453
454
455
456
457
458}
459
460void HeightMap::load()
461{
462
463  //Create a Dynamicly sized 2D-Array for Tiles
464  tiles =  new Tile** [heightMap->h/tileSize];
465  for(int i=0;i <    heightMap->h/tileSize ; i++)
466    tiles [i]= new (Tile* [heightMap->w /tileSize ]);
467
468  //SetUp Arrays
469  for(int i = 0; i < (heightMap->)/ tileSize; i ++)
470  {
471    for(int j = 0; j < (heightMap->w )/ tileSize; j ++)
472    {
473
474      tiles[i][j] =    new Tile( i*tileSize ,  j*tileSize , (i+1)*tileSize, (j+1)*tileSize , this ) ;
475    }
476  }
477
478}
479
480
481void HeightMap::draw()
482{
483  const PNode* camera = State::getCameraNode();
484  Vector v = camera->getAbsCoor();
485
486  int i_min = 0;
487  int i_max = (heightMap->h )/ tileSize;
488  int j_min = 0;
489  int j_max= (heightMap->) / tileSize;
490
491
492
493  for(int i = 0; i <  i_max        ; i ++)
494  {
495    for(int j = 0; j < j_max ; j++)
496    {
497      tiles[i][j]->draw();
498    }
499  }
500
501}
502void HeightMap::generateNormalVectorField()
503{
504  int delta = 1;
505  heights  = (unsigned char*) heightMap->pixels;
506
507  //Create a Dynamicly sized 2D-Array to store our normals
508  normalVectorField =  new Vector* [heightMap->h];
509  for(int i=0;i<heightMap->h;i++)
510    normalVectorField [i]= new (Vector [heightMap->w]);
511
512
513
514
515  // Initialize
516  for(int i=0; i< heightMap->h; i++)
517  {
518    for(int j = 0; j> heightMap->w; j++)
519    {
520      Vector v = Vector(0.0, 1.0, 0.0);
521      normalVectorField[i][j] = v;
522    }
523  }
524
525  // !!! Does not yet calculate the normals of some border points!!!!!
526
527  if(heightMap != NULL && heightMap->format->BitsPerPixel == 8 )
528  {
529    SDL_LockSurface(heightMap);
530    for(int i = 0 ; i < heightMap->h - 1  ; i ++)
531    {
532      for(int j = 0; j < heightMap->- 1  ;  j ++)
533      {
534
535
536        delta = (int)heights[j + (i+1)*(heightMap->w )] -  (int) heights[j + i*(heightMap->w )];
537        Vector a =  Vector(-scaleX,(float)delta*scaleY  ,0.0f);
538
539        delta = (int)heights[j+1 + i*(heightMap->w )] - (int)heights[j + i*(heightMap->w )];
540        Vector b =  Vector(0.0f,(float) delta*scaleY ,scaleZ);
541
542
543        normalVectorField[i][j] = b.cross(a);
544        normalVectorField[i][j].normalize();
545
546      }
547    }
548    SDL_UnlockSurface(heightMap);
549
550  }
551
552
553
554
555}
556
557
558void HeightMap::scale(Vector v)
559{
560  scaleX = v.x;
561  scaleY = v.y;
562  scaleZ = v.z;
563  generateNormalVectorField();
564}
565
566void HeightMap::setAbsCoor(Vector v)
567{
568  offsetX = v.x;
569  offsetY = v.y;
570  offsetZ  = v.z;
571}
572
573
574float HeightMap::getHeight(float x, float y)
575{
576
577  x -= offsetX;
578  y -= offsetZ;
579
580
581  int xInt = (int)x / scaleX;
582  x -= (float)((int)x);
583  xInt = heightMap->h - xInt;
584  int yInt = (int)y / scaleZ;
585  y -= (float) ((int) y); /*yInt = heightMap->w - yInt;*/
586
587  //PRINTF(0)("xInt: %i, yInt: %i, x: %f, y: %f\n", xInt, yInt, x, y);
588
589  if(xInt <= 0 || xInt >= heightMap->h || yInt <= 0 || yInt >= heightMap->)
590    return 0;
591  if( y >= 0.5*x)
592  {
593    // Check for ...
594  }
595
596  float height = heights[yInt + (xInt)*heightMap->w]*scaleY;
597
598
599  float a = normalVectorField[(xInt)][yInt].x;
600  float b = normalVectorField [(xInt)][yInt].z;
601  float c = normalVectorField [(xInt)][yInt].y;
602
603  PRINTF(0)("a: %f \n" ,a);
604  PRINTF(0)("b: %f \n" ,b);
605  PRINTF(0)("c: %f \n" ,c);
606
607  height -= ( (a/c)*(x) + (b/c)*(y));
608
609  PRINTF(0)("height: %f \n" ,height );
610  return (height + offsetZ);
611}
Note: See TracBrowser for help on using the repository browser.