Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

orxonox: working on kill event processing (game rule specific)

  • 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
99  this->x = this->heightMapReference->offsetX + ( this->heightMapReference->heightMap->h - ((i1 + i2) / 2)) * scaleX;
100  this->z = this->heightMapReference->offsetZ + ((j1 + j2 ) / 2 ) * scaleZ;
101
102  float height = 0;
103  int offset = 0;
104
105  float r = 0.0;
106  float g = 0.0;
107  float b = 0.0;
108
109
110
111
112  if(this->heightMapReference->heightMap != NULL && this->heightMapReference->heightMap->format->BitsPerPixel == 8 )
113
114  SDL_LockSurface(this->heightMapReference->heightMap);
115  SDL_LockSurface(this->heightMapReference->colourMap);
116
117  for(int i = i1 ; i <= i2  ; i +=sampleRate)
118  {
119    int w = 0;
120
121
122
123    if(this->heightMapReference->hasColourMap)
124    {
125      r = colours[(3*w+2 + 3*i*(this->heightMapReference->heightMap->w )) ];
126      g = colours[(3*w+1 + 3*i*(this->heightMapReference->heightMap->w)) ];
127      b = colours[(3*w+0 + 3*i*(this->heightMapReference->heightMap->w))];
128    }
129
130    w = j1;
131    model->addVertex(scaleX*(this->heightMapReference->heightMap->h -i)+ shiftX,shiftY,scaleZ*(w)+ shiftZ); // Top Right
132    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);
133    model->addTexCoor((float)(j1-sampleRate) /(texRate), (float)(i %this->heightMapReference->heightMap->h)/(texRate));
134
135    model->addColor(r/255.0,g/255.0,b/255.0);
136
137    for(int j = j1  ; j <= j2    ;  j += sampleRate)
138    {
139
140
141      // To be fixed
142      if(this->heightMapReference->hasColourMap)
143      {
144        r = colours[(3*j+2 + 3*i*(this->heightMapReference->heightMap->w )) ];
145        g =  colours[(3*j+1 + 3*i*(this->heightMapReference->heightMap->w)) ];
146        b =  colours[(3*j+0 + 3*i*(this->heightMapReference->heightMap->w))];
147      }
148      height = (float)(unsigned char) this->heightMapReference->heights[(j +sampleRate+ i*(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 -1+ sampleRate   + (i+1)*(this->heightMapReference->heightMap->w ))];
151      height +=  (float)(unsigned char)this->heightMapReference->heights[(j +sampleRate+ (i+2)*(this->heightMapReference->heightMap->w )) ];
152      height +=  (float)(unsigned char)this->heightMapReference->heights[(j+sampleRate + (i)*(this->heightMapReference->heightMap->w )) ];
153      height=height/5.0;
154
155      model->addVertex(scaleX*(this->heightMapReference->heightMap->h -i) + shiftX ,((double)(height)*scaleY) + shiftY ,scaleZ*(j) + shiftZ); // Top Right
156      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);
157      model->addTexCoor((float)(j) /(texRate), (float)(i %this->heightMapReference->heightMap->h)/(texRate));
158
159      //PRINTF(0)("TexCoord:  %f %f \n",(float)j / 100.0, (float)(i %this->heightMapReference->h)/100.0);
160
161      model->addColor(r/255.0,g/255.0,b/255.0);
162      w = j;
163    }
164
165
166    model->addVertex(scaleX*(this->heightMapReference->heightMap->h -i)+ shiftX,shiftY,scaleZ*(w)+ shiftZ); // Top Right
167    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);
168    model->addTexCoor((float)(j2+sampleRate) /(texRate), (float)(i %this->heightMapReference->heightMap->h)/(texRate));
169    model->addColor(r/255.0,g/255.0,b/255.0);
170
171  }
172
173
174
175
176
177  SDL_UnlockSurface(this->heightMapReference->heightMap);
178  int cnt = 0;
179  for(int i = i1   ; i < i2  ; i +=sampleRate)
180  {
181
182    for(int j = j1-sampleRate  ; j < j2  + 2*sampleRate  ;  j += sampleRate)
183    {
184
185      model->addIndice(cnt);
186      model->addIndice(cnt  + (j2 -j1 + 3* sampleRate  )/ sampleRate );
187      cnt++;
188
189    }
190
191
192
193    model->newStripe();
194
195
196  }
197  cnt += (j2 -j1 + 3* sampleRate)/ sampleRate;
198
199  for(int j = j1 ; j <= j2    ;  j += sampleRate)
200  {
201    int i = i1;
202
203    // To be fixed
204    if(this->heightMapReference->hasColourMap)
205    {
206      r = (float)colours[(3*j+2 + 3*i*(this->heightMapReference->heightMap->w )) ];
207      g = (float)colours[(3*j+1 + 3*i*(this->heightMapReference->heightMap->w)) ];
208      b = (float)colours[(3*j+0 + 3*i*(this->heightMapReference->heightMap->w))];
209    }
210
211    model->addVertex(scaleX*(this->heightMapReference->heightMap->h -i) + shiftX , shiftY ,scaleZ*(j) + shiftZ); // Top Right
212    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);
213    model->addTexCoor((float)j /(texRate), (float)((i - sampleRate) %this->heightMapReference->heightMap->h)/(texRate));
214    model->addColor(r/255.0,g/255.0,b/255.0);
215
216  }
217
218  for(int j = j1  ; j <= j2    ;  j += sampleRate)
219  {
220    int i = i1;
221    height = (float)(unsigned char) this->heightMapReference->heights[(j +sampleRate+ i*(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 -1+ sampleRate   + (i+1)*(this->heightMapReference->heightMap->w ))];
224    height +=  (float)(unsigned char)this->heightMapReference->heights[(j +sampleRate+ (i+2)*(this->heightMapReference->heightMap->w )) ];
225    height +=  (float)(unsigned char)this->heightMapReference->heights[(j+sampleRate + (i)*(this->heightMapReference->heightMap->w )) ];
226    height=height/5.0;
227
228    model->addVertex(scaleX*(this->heightMapReference->heightMap->h -i) + shiftX , ((double)(height)*scaleY) +shiftY ,scaleZ*(j) + shiftZ); // Top Right
229    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);
230    model->addTexCoor((float)j /(texRate), (float)(i %this->heightMapReference->heightMap->h)/(texRate));
231    model->addColor(r/255.0,g/255.0,b/255.0);
232
233  }
234
235
236
237  for(int j = j1  ; j <= j2    ;  j += sampleRate)
238  {
239    int i = i2;
240
241    // To be fixed
242    if(this->heightMapReference->hasColourMap)
243    {
244      r = (float)colours[3*j+2 + 3*i*(this->heightMapReference->heightMap->w )];
245      g = (float)colours[3*j+1 + 3*i*(this->heightMapReference->heightMap->w)];
246      b = (float)colours[3*j+0 + 3*i*(this->heightMapReference->heightMap->w)];
247    }
248
249    model->addVertex(scaleX*(this->heightMapReference->heightMap->h -i) + shiftX , shiftY ,scaleZ*(j) + shiftZ); // Top Right
250    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);
251    model->addTexCoor((float)j /(texRate), (float)((i+ sampleRate) %this->heightMapReference->heightMap->h)/(texRate));
252    model->addColor(r/255.0,g/255.0,b/255.0);
253
254  }
255
256
257  for(int j = j1 ; j <= j2    ;  j += sampleRate)
258  {
259    int i = i2;
260    height = (float)(unsigned char) this->heightMapReference->heights[(j +sampleRate+ i*(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 -1+ sampleRate   + (i+1)*(this->heightMapReference->heightMap->w ))];
263    height +=  (float)(unsigned char)this->heightMapReference->heights[(j +sampleRate+ (i+2)*(this->heightMapReference->heightMap->w ))];
264    height +=  (float)(unsigned char)this->heightMapReference->heights[(j+sampleRate + (i)*(this->heightMapReference->heightMap->w )) ];
265    height=height/5.0;
266    model->addVertex(scaleX*(this->heightMapReference->heightMap->h -i) + shiftX , ((double)(height)*scaleY) +shiftY ,scaleZ*(j) + shiftZ); // Top Right
267    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);
268    model->addTexCoor((float)j /(texRate), (float)(i %this->heightMapReference->heightMap->h)/(texRate));
269    model->addColor(r/255.0,g/255.0,b/255.0);
270
271  }
272
273
274
275
276  // link Boarder Stripe
277  for(int j = j1-sampleRate  ; j < j2    ;  j += sampleRate)
278  {
279
280    model->addIndice(cnt);
281    model->addIndice(cnt  + (j2 -j1 +  sampleRate  )/ sampleRate );
282    cnt++;
283
284  }
285
286  cnt++;
287
288  model->newStripe();
289
290
291
292
293
294  cnt += (j2-j1)/ sampleRate;
295
296  // link 2nd BoarderStripe
297  for(int j = j1-sampleRate  ; j < j2    ;  j += sampleRate)
298  {
299
300    model->addIndice(cnt);
301    model->addIndice(cnt  + (j2 -j1 +  sampleRate  )/ sampleRate );
302    cnt++;
303
304  }
305
306
307  SDL_UnlockSurface(this->heightMapReference->colourMap);
308
309  model->finalize();
310
311
312
313
314// #undef heightMap
315        #undef colours
316        #undef scaleX
317        #undef scaleY
318        #undef scaleZ
319        #undef shiftX
320        #undef shiftY
321        #undef shiftZ
322        #undef normalVectorField
323
324}
325
326
327HeightMap::HeightMap()
328{
329}
330
331
332HeightMap::HeightMap(const std::string& height_map_name = "")
333    : VertexArrayModel()
334{
335  this->setClassID(CL_HEIGHT_MAP, "HeightMap");
336  heightMap =  IMG_Load(height_map_name.c_str());
337  if(heightMap!=NULL)
338  {
339
340    PRINTF(0)("loading Image %s\n", height_map_name.c_str());
341    PRINTF(0)("width : %i\n", heightMap->w);
342    PRINTF(0)("height : %i\n", heightMap->h);
343    PRINTF(0)("%i Byte(s) per Pixel \n", heightMap->format->BytesPerPixel);
344    PRINTF(0)("Rshift : %i\n", heightMap->format->Rshift);
345    PRINTF(0)("Bshift: %i\n", heightMap->format->Bshift);
346    PRINTF(0)("Gshift: %i\n", heightMap->format->Gshift);
347    PRINTF(0)("Rmask: %i\n", heightMap->format->Rmask);
348    PRINTF(0)("Gmask: %i\n", heightMap->format->Gmask);
349  }
350
351  else
352    PRINTF(4)("oops! couldn't load %s for some reason.\n", height_map_name.c_str());
353
354
355  generateNormalVectorField();
356
357  shiftX = 0;
358  shiftY = 0;
359  shiftZ = 0;
360
361}
362
363HeightMap::HeightMap(const std::string& height_map_name = NULL, const std::string& colour_map_name = NULL)
364    : VertexArrayModel()
365{
366  this->setClassID(CL_HEIGHT_MAP, "HeightMap");
367
368  heightMap =  IMG_Load(height_map_name.c_str());
369  if(heightMap!=NULL)
370  {
371
372    PRINTF(0)("loading Image %s\n", height_map_name.c_str());
373    PRINTF(0)("width : %i\n", heightMap->w);
374    PRINTF(0)("height : %i\n", heightMap->h);
375    PRINTF(0)("%i Byte(s) per Pixel \n", heightMap->format->BytesPerPixel);
376    PRINTF(0)("Rshift : %i\n", heightMap->format->Rshift);
377    PRINTF(0)("Bshift: %i\n", heightMap->format->Bshift);
378    PRINTF(0)("Gshift: %i\n", heightMap->format->Gshift);
379    PRINTF(0)("Rmask: %i\n", heightMap->format->Rmask);
380    PRINTF(0)("Gmask: %i\n", heightMap->format->Gmask);
381  }
382
383  else
384    PRINTF(4)("oops! couldn't load %s for some reason.\n", height_map_name.c_str());
385
386
387  generateNormalVectorField();
388
389  colourMap=NULL;
390  if(colour_map_name != "")
391  {
392    colourMap = IMG_Load(colour_map_name.c_str());
393  }
394
395  if(colourMap != NULL)
396  {
397    PRINTF(0)("loading Image %s\n", colour_map_name.c_str());
398    PRINTF(0)("width : %i\n", colourMap->w);
399    PRINTF(0)("height : %i\n", colourMap->h);
400    PRINTF(0)("%i Byte(s) per Pixel \n", colourMap->format->BytesPerPixel);
401    PRINTF(0)("Rshift : %i\n", colourMap->format->Rshift);
402    PRINTF(0)("Bshift: %i\n", colourMap->format->Bshift);
403    PRINTF(0)("Gshift: %i\n", colourMap->format->Gshift);
404    PRINTF(0)("Rmask: %i\n", colourMap->format->Rmask);
405    PRINTF(0)("Gmask: %i\n", colourMap->format->Gmask);
406  }
407  else
408    PRINTF(0)("oops! couldn't load colorMap for some reason.\n");
409
410
411
412  if(colourMap != NULL)
413  {
414    colours = (unsigned char *) colourMap->pixels;
415    hasColourMap = true;
416  }
417  else
418    hasColourMap = false;
419
420
421  heights  = (unsigned char*) heightMap->pixels;
422  shiftX = 0;
423  shiftY = 0;
424  shiftZ = 0;
425}
426
427
428HeightMap::~HeightMap()
429{
430  delete heightMap;
431  delete colourMap;
432
433
434  for(int i=0;i <    heightMap->h/tileSize ; i++)
435  {
436    for(int j = 0; j < heightMap->w/ tileSize; j++)
437    {
438      delete tiles [i][j];
439    }
440  }
441  for(int i=0;i <    heightMap->h/tileSize ; i++)
442    delete[] tiles[i];
443  delete[] tiles;
444
445
446
447
448
449  for(int i=0;i<heightMap->h;i++)
450    delete[] normalVectorField [i];
451  delete[] normalVectorField;
452
453
454
455
456
457
458
459}
460
461void HeightMap::load()
462{
463
464  //Create a Dynamicly sized 2D-Array for Tiles
465  tiles =  new Tile** [heightMap->h/tileSize];
466  for(int i=0;i <    heightMap->h/tileSize ; i++)
467    tiles [i]= new (Tile* [heightMap->w /tileSize ]);
468
469  //SetUp Arrays
470  for(int i = 0; i < (heightMap->)/ tileSize; i ++)
471  {
472    for(int j = 0; j < (heightMap->w )/ tileSize; j ++)
473    {
474
475      tiles[i][j] =    new Tile( i*tileSize ,  j*tileSize , (i+1)*tileSize, (j+1)*tileSize , this ) ;
476    }
477  }
478
479}
480
481
482void HeightMap::draw()
483{
484  const PNode* camera = State::getCameraNode();
485  Vector v = camera->getAbsCoor();
486
487  int i_min = 0;
488  int i_max = (heightMap->h )/ tileSize;
489  int j_min = 0;
490  int j_max= (heightMap->) / tileSize;
491
492
493
494  for(int i = 0; i <  i_max        ; i ++)
495  {
496    for(int j = 0; j < j_max ; j++)
497    {
498      tiles[i][j]->draw();
499    }
500  }
501
502}
503void HeightMap::generateNormalVectorField()
504{
505  int delta = 1;
506  heights  = (unsigned char*) heightMap->pixels;
507
508  //Create a Dynamicly sized 2D-Array to store our normals
509  normalVectorField =  new Vector* [heightMap->h];
510  for(int i=0;i<heightMap->h;i++)
511    normalVectorField [i]= new (Vector [heightMap->w]);
512
513
514
515
516  // Initialize
517  for(int i=0; i< heightMap->h; i++)
518  {
519    for(int j = 0; j> heightMap->w; j++)
520    {
521      Vector v = Vector(0.0, 1.0, 0.0);
522      normalVectorField[i][j] = v;
523    }
524  }
525
526  // !!! Does not yet calculate the normals of some border points!!!!!
527
528  if(heightMap != NULL && heightMap->format->BitsPerPixel == 8 )
529  {
530    SDL_LockSurface(heightMap);
531    for(int i = 0 ; i < heightMap->h - 1  ; i ++)
532    {
533      for(int j = 0; j < heightMap->- 1  ;  j ++)
534      {
535
536
537        delta = (int)heights[j + (i+1)*(heightMap->w )] -  (int) heights[j + i*(heightMap->w )];
538        Vector a =  Vector(-scaleX,(float)delta*scaleY  ,0.0f);
539
540        delta = (int)heights[j+1 + i*(heightMap->w )] - (int)heights[j + i*(heightMap->w )];
541        Vector b =  Vector(0.0f,(float) delta*scaleY ,scaleZ);
542
543
544        normalVectorField[i][j] = b.cross(a);
545        normalVectorField[i][j].normalize();
546
547      }
548    }
549    SDL_UnlockSurface(heightMap);
550
551  }
552
553
554
555
556}
557
558
559void HeightMap::scale(Vector v)
560{
561  scaleX = v.x;
562  scaleY = v.y;
563  scaleZ = v.z;
564  generateNormalVectorField();
565}
566
567void HeightMap::setAbsCoor(Vector v)
568{
569  offsetX = v.x;
570  offsetY = v.y;
571  offsetZ  = v.z;
572}
573
574
575float HeightMap::getHeight(float x, float y)
576{
577
578  x -= offsetX;
579  y -= offsetZ;
580
581
582  int xInt = (int)x / scaleX;
583  x -= (float)((int)x);
584  xInt = heightMap->h - xInt;
585  int yInt = (int)y / scaleZ;
586  y -= (float) ((int) y); /*yInt = heightMap->w - yInt;*/
587
588  //PRINTF(0)("xInt: %i, yInt: %i, x: %f, y: %f\n", xInt, yInt, x, y);
589
590  if(xInt <= 0 || xInt >= heightMap->h || yInt <= 0 || yInt >= heightMap->)
591    return 0;
592  if( y >= 0.5*x)
593  {
594    // Check for ...
595  }
596
597  float height = heights[yInt + (xInt)*heightMap->w]*scaleY;
598
599
600  float a = normalVectorField[(xInt)][yInt].x;
601  float b = normalVectorField [(xInt)][yInt].z;
602  float c = normalVectorField [(xInt)][yInt].y;
603
604  PRINTF(0)("a: %f \n" ,a);
605  PRINTF(0)("b: %f \n" ,b);
606  PRINTF(0)("c: %f \n" ,c);
607
608  height -= ( (a/c)*(x) + (b/c)*(y));
609
610  PRINTF(0)("height: %f \n" ,height );
611  return (height + offsetZ);
612}
Note: See TracBrowser for help on using the repository browser.