Attachment 'ss5.c'

Download

   1 // ss5.c
   2 // gcc -o ss5 ss5.c -lm -lgd
   3 //
   4 //  The perspective sucks
   5 //  The light pressure of tilted thrusters is treated here as constant. 
   6 //  The actual light pressure is proportional to cos^2( sun angle )
   7 //  This assumes a 4:3 ratio, with gauges down the right side
   8 
   9 #define GIFOUT "ss5.gif"
  10 #define FNT    "DejaVuMonoSans"
  11 #define SCALE  0.09              // size of serversat
  12 
  13 #define FSZ     20               // this scales the font and the whole drawing
  14 
  15 #define Z0    25.0               // depth scaling for cheesy perspective
  16 #define ACC      8               // acceleration in degrees/minute^2
  17 #define TSTART   0
  18 #define TIME1  150               // acceleration time in seconds
  19 #define TEND   300
  20 #define TDELTA  15               // display timestep
  21 #define ASTEP0 400               // starting and ending hold times
  22 #define ASTEP1  80               // image rate (1.25 images/second )
  23 #define NSTARS 500               // background stars (this shows transparency)
  24 #define MOVE   0.3               // how much the background stars move
  25 
  26 #define LINETHICK 1
  27 #define XSIZE   40*FSZ
  28 #define YSIZE   30*FSZ
  29 #define MINUTE  60
  30 #define HOUR  3600
  31 
  32 #include "gd.h"
  33 #include <math.h>
  34 #include <string.h>
  35 #include <stdlib.h>
  36 
  37 #define PI 3.14159265359
  38 #define DEG2RAD(x) ((x)*PI/180.)
  39 
  40 // #define WIRING 1              // show solar cell wires
  41 
  42 gdImagePtr im0, im1, im;
  43 int        black, white, gray, dgray, trans ;
  44 int        xgauge    = ( XSIZE + YSIZE ) / 2 ;     // down the right edge
  45 double     scale     = SCALE * YSIZE ;             // scale serversat in pixels
  46 
  47 // starting values
  48 double     anglerad  = 0.0 ;
  49 double     time      = 0.0 ;
  50 double     angle_vel = 0.0 ;
  51 double     angle_acc = 0.0 ;
  52 double     angle     = 0.0 ;
  53 
  54 double     min_vel   = 0.0 ;
  55 double     max_vel   = ACC * TIME1 / MINUTE ;
  56 double     angle_min = 0.0 ;
  57 double     angle_max = ACC * TIME1 * TIME1 / ( MINUTE * MINUTE ) ;
  58 double     min_acc   = -ACC ;
  59 double     max_acc   = ACC ;
  60 
  61 //--------------------------------------------------------------------
  62 // fixed rectangle subroutines, swap order if needed 
  63 //  gdImageRectangle silently fails unless it gets left, bottom, right, top
  64 
  65 void RectE( gdImagePtr imm,
  66             double rx1, double ry1, double rx2, double ry2, int rcolor ) {
  67    if( rx1 < rx2 ) {
  68       if( ry1 < ry2 ) {
  69          gdImageRectangle( imm, rx1, ry1, rx2, ry2, rcolor );
  70       } else {
  71          gdImageRectangle( imm, rx1, ry2, rx2, ry1, rcolor );
  72       }
  73    } else {
  74       if( ry1 < ry2 ) {
  75          gdImageRectangle( imm, rx2, ry1, rx1, ry2, rcolor );
  76       } else {
  77          gdImageRectangle( imm, rx2, ry2, rx1, ry1, rcolor );
  78       }
  79    }
  80 }
  81 
  82    
  83 void RectF( gdImagePtr imm,
  84             double rx1, double ry1, double rx2, double ry2, int rcolor ) {
  85    if( rx1 < rx2 ) {
  86       if( ry1 < ry2 ) {
  87          gdImageFilledRectangle( imm, rx1, ry1, rx2, ry2, rcolor );
  88       } else {
  89          gdImageFilledRectangle( imm, rx1, ry2, rx2, ry1, rcolor );
  90       }
  91    } else {
  92       if( ry1 < ry2 ) {
  93          gdImageFilledRectangle( imm, rx2, ry1, rx1, ry2, rcolor );
  94       } else {
  95          gdImageFilledRectangle( imm, rx2, ry2, rx1, ry1, rcolor );
  96       }
  97    }
  98 }
  99 
 100    
 101 //--------------------------------------------------------------------
 102 // rotation,  transformation, scaling --------
 103 //
 104 // This is very weak.  I would love to be able to make multiple
 105 // rotations of a serversat along multiple axes.
 106 
 107 gdPoint sr( double inx, double iny, double inz ) {
 108    gdPoint retvar ;
 109 
 110    // rotate, IN ONE PLANE ONLY
 111    // z is into the plane, away from observer
 112    double  rx  = inx ;
 113    double  ry  = iny * cos( anglerad ) - inz * sin( anglerad ) ;
 114    double  rz  = iny * sin( anglerad ) + inz * cos( anglerad ) ;
 115 
 116    // depth transform - THIS IS PROBABLY INCORRECT
 117    double  trx = rx*(1.0+rz/Z0) ;
 118    double  try = ry*(1.0+rz/Z0) ;
 119    
 120    retvar.x = (int) ( 0.5*YSIZE + scale * trx ) ;
 121    retvar.y = (int) ( 0.5*YSIZE - scale * try ) ;
 122 
 123    return retvar;
 124 }
 125 
 126 // draw random star field and move it with time  ----------------------------
 127 // uses all globals
 128 
 129 // star array, overlaps right edge, unscaled
 130 double  starx[NSTARS], stary[NSTARS]; 
 131 
 132 void
 133 makestars() {
 134    int nstar ;
 135    for( nstar = 0 ; nstar < NSTARS ; nstar++ ) {
 136       starx[nstar] = drand48() * ( XSIZE + MOVE*TEND ) ;
 137       stary[nstar] = drand48() * YSIZE  ;
 138    }
 139 }
 140 
 141 
 142 void
 143 drawstars() {
 144    int nstar, xs, ys ;
 145    for( nstar = 0 ; nstar < NSTARS ; nstar++ ) {
 146       xs = (int) ( starx[nstar] - MOVE*time )  ;
 147       ys = (int)   stary[nstar] ;
 148       if ( xs < YSIZE ) {
 149          gdImageSetPixel( im, xs, ys, white);
 150       }
 151    }
 152 }
 153 
 154 // draw gauges ---------------------------------
 155 // uses globals
 156 
 157 #define  YCLOCK   (4*FSZ)
 158 #define  YACC     (10*FSZ)
 159 #define  YVEL     (16*FSZ)
 160 #define  YANG     (22*FSZ)
 161 #define  F2       (4*FSZ/5)
 162 #define  F4       (FSZ/4)
 163 
 164 #define  RCLOCK    (3*FSZ+8)
 165 #define  SECHAND   (3*FSZ)
 166 #define  MINHAND1  (2*FSZ)
 167 #define  MINHAND2  (2*FSZ+5)
 168 #define  MINWID    40
 169 
 170 int      gleft  = YSIZE + 2*FSZ ;
 171 int      gright = XSIZE - 2*FSZ ;
 172 
 173 
 174 int  gscale( double gvar, double gmin, double gmax ) {
 175     return ( (int) ( gleft + (gvar-gmin)
 176                            * ( (double)(gright-gleft))/(gmax-gmin) ) ) ;
 177 }
 178 
 179 void drawgauges()  {
 180 
 181    double wangle ;   // working angle
 182    int    cx1, cy1, cx2, cy2 ;
 183    char   s[80];
 184    int    ic ;
 185 
 186    // clock at xgauge, 100 -----------------------------
 187 
 188    gdImageArc(  im, xgauge, YCLOCK, 2*RCLOCK, 2*RCLOCK, 0, 360, white );
 189 
 190    // clock / 60
 191    for( ic = 0;  ic < 60 ; ic += 1 ) {
 192       wangle  = PI * ic / 30 ;
 193       if ( ic%15 == 0 ) {
 194          cx1     = xgauge + (int)(0.5 + (RCLOCK-6)*sin(wangle)) ;
 195          cy1     = YCLOCK - (int)(0.5 + (RCLOCK-6)*cos(wangle)) ;
 196       }
 197       else if( ic%5 == 0 ) {
 198          cx1     = xgauge + (int)(0.5 + (RCLOCK-4)*sin(wangle)) ;
 199          cy1     = YCLOCK - (int)(0.5 + (RCLOCK-4)*cos(wangle)) ;
 200       }
 201       else {
 202          cx1     = xgauge + (int)(0.5 + (RCLOCK-2)*sin(wangle)) ;
 203          cy1     = YCLOCK - (int)(0.5 + (RCLOCK-2)*cos(wangle)) ;
 204       }
 205       cx2     = xgauge +  (int)(0.5 + RCLOCK   *sin(wangle) ) ;
 206       cy2     = YCLOCK -  (int)(0.5 + RCLOCK   *cos(wangle) ) ;
 207       gdImageLine( im, cx1, cy1, cx2, cy2, white ) ;
 208    }
 209 
 210    // second hand
 211    wangle  = 2.0 * PI * time / MINUTE ;
 212    cx1     = xgauge+(int)(0.5 + SECHAND*sin(wangle) ) ;
 213    cy1     = YCLOCK-(int)(0.5 + SECHAND*cos(wangle) ) ;
 214    gdImageLine( im, xgauge, YCLOCK, cx1, cy1, white ) ;
 215 
 216 
 217    // minute hand (two strokes )
 218    wangle  = 2.0 * PI * (time + MINWID) / HOUR ;
 219    cx1     = xgauge+(int)(0.5 + MINHAND1*sin(wangle) ) ;
 220    cy1     = YCLOCK-(int)(0.5 + MINHAND1*cos(wangle) ) ;
 221 
 222    wangle  = 2.0 * PI * (time         ) / HOUR ;
 223    cx2     = xgauge+(int)(0.5 + MINHAND2*sin(wangle) ) ;
 224    cy2     = YCLOCK-(int)(0.5 + MINHAND2*cos(wangle) ) ;
 225    gdImageLine( im, xgauge, YCLOCK, cx1, cy1, white ) ;
 226    gdImageLine( im,    cx2,    cy2, cx1, cy1, white );
 227    
 228    wangle  = 2.0 * PI * (time - MINWID) / HOUR ;
 229    cx1     = xgauge+(int)(0.5 + MINHAND1*sin(wangle) ) ;
 230    cy1     = YCLOCK-(int)(0.5 + MINHAND1*cos(wangle) ) ;
 231    gdImageLine( im,    cx2,    cy2, cx1, cy1, white );
 232    gdImageLine( im, xgauge, YCLOCK, cx1, cy1, white ) ;
 233 
 234    // text clock
 235 
 236    sprintf( s, "%3.0f secs", time );
 237    gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0, xgauge-2*FSZ, 9*FSZ, s );
 238 
 239    // angular acceleration gauge ----------------
 240 
 241    gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0,
 242                               YSIZE, YACC+2*FSZ-F4, "acceleration" );
 243    gdImageStringFT( im, NULL, white, FNT, F2, 0.0,
 244                               YSIZE, YACC+5*FSZ, "degrees/min^2" );
 245 
 246 
 247    sprintf( s, "%2.0f", min_acc );
 248    gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0, YSIZE,  YACC+4*FSZ, s );
 249 
 250    sprintf( s, "%2.0f", max_acc );
 251    gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0, gright+F4, YACC+4*FSZ, s );
 252    
 253    cx1 =  gscale( angle_acc, min_acc, max_acc ) ;
 254    cx2 =  gscale(         0, min_acc, max_acc ) ;
 255    RectE( im, gleft, YACC+2*FSZ, gright, YACC+4*FSZ, white );
 256    RectF( im,   cx1, YACC+2*FSZ,    cx2, YACC+4*FSZ, white );
 257 
 258    // angular velocity gauge ---------------------
 259 
 260    gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0,
 261                               YSIZE, YVEL+2*FSZ-F4, "velocity" );
 262    gdImageStringFT( im, NULL, white, FNT, F2, 0.0,
 263                               YSIZE, YVEL+5*FSZ, "degrees/min" );
 264 
 265    sprintf( s, "%2.0f", min_vel );
 266    gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0, YSIZE,  YVEL+4*FSZ, s );
 267 
 268    sprintf( s, "%2.0f", max_vel );
 269    gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0, gright+F4, YVEL+4*FSZ, s );
 270    
 271    cx1 =  gscale( angle_vel, min_vel, max_vel ) ;
 272    cx2 =  gscale(         0, min_vel, max_vel ) ;
 273    RectE( im, gleft, YVEL+2*FSZ, gright, YVEL+4*FSZ, white );
 274    RectF( im,   cx1, YVEL+2*FSZ,    cx2, YVEL+4*FSZ, white );
 275 
 276    //  angle gauge -------------------------------
 277 
 278    gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0,
 279                               YSIZE, YANG+2*FSZ-F4, "angle" );
 280    gdImageStringFT( im, NULL, white, FNT, F2, 0.0,
 281                               YSIZE, YANG+5*FSZ, "degrees" );
 282 
 283    sprintf( s, "%2.0f", angle_min );
 284    gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0, YSIZE,  YANG+4*FSZ, s );
 285 
 286    sprintf( s, "%2.0f", angle_max );
 287    gdImageStringFT( im, NULL, white, FNT, FSZ, 0.0, gright+F4, YANG+4*FSZ, s );
 288    
 289    cx1 =  gscale(     angle, angle_min, angle_max ) ;
 290    cx2 =  gscale(         0, angle_min, angle_max ) ;
 291    RectE( im, gleft, YANG+2*FSZ, gright, YANG+4*FSZ, white );
 292    RectF( im,   cx1, YANG+2*FSZ,    cx2, YANG+4*FSZ, white );
 293 }
 294 
 295 
 296 //  draw circle ---------------------------------
 297 // uses some globals
 298 // fill inner circle with area factor "fill"
 299 // draw outer circle with full radius
 300 // used for main solar cell, and thrusters
 301 
 302 #define     NPTS    100 
 303 gdPoint     pointsf[NPTS];               // filled circle points
 304 gdPoint     pointse[NPTS];               // edge   circle points
 305 
 306 void drawcircle( int  color, double  fill,
 307                  double centerx, double centery, double radius ) {
 308 
 309    int     cntr ;
 310    double  dangle  = (2.0 * PI)/( NPTS-1 );
 311    double  sradius = radius*sqrt(fill) ;
 312    double  c, s;
 313 
 314    for( cntr=0 ; cntr < NPTS ; cntr++ ) {
 315       c = cos( dangle * cntr ) ;
 316       s = sin( dangle * cntr ) ;
 317 
 318       pointsf[cntr] = sr( centerx + sradius*c , centery + sradius*s, 0.0 ) ;
 319       pointse[cntr] = sr( centerx +  radius*c , centery +  radius*s, 0.0 ) ;
 320    }
 321 
 322    gdImageFilledPolygon(  im, pointsf, NPTS, color );
 323    gdImagePolygon(        im, pointse, NPTS, color );
 324 }
 325 
 326 // ----------------------------------------------------------------------------
 327 // perpendicular arrow
 328 void arrow( int acolor, double acenterx, double acentery, double alength ) {
 329    gdPoint   pointsa[8];
 330    double    xwid  = 0.05*alength ;
 331    double    xhead = 0.15*alength ;
 332    double    zhead = 0.3*alength ;
 333 
 334    pointsa[0] = sr( acenterx,       acentery, alength       );
 335    pointsa[1] = sr( acenterx+xhead, acentery, alength-zhead );
 336    pointsa[2] = sr( acenterx+xwid,  acentery, alength-zhead );
 337    pointsa[3] = sr( acenterx+xwid,  acentery, 0.0           );
 338    pointsa[4] = sr( acenterx-xwid,  acentery, 0.0           );
 339    pointsa[5] = sr( acenterx-xwid,  acentery, alength-zhead );
 340    pointsa[6] = sr( acenterx-xhead, acentery, alength-zhead );
 341    pointsa[7] = sr( acenterx, acentery, alength );
 342    gdImageFilledPolygon(  im, pointsa, 8, acolor );
 343 }
 344 
 345 // ----------------------------------------------------------------------------
 346 //  cell wiring stuff
 347 
 348 //  draw scalable rectangle
 349 void  RectS( double scolor, double sx1, double sy1, double sx2, double sy2) {
 350    gdPoint   pointss[5];               
 351 
 352    pointss[0] = sr( sx1, sy1, 0.0 );
 353    pointss[1] = sr( sx2, sy1, 0.0 );
 354    pointss[2] = sr( sx2, sy2, 0.0 );
 355    pointss[3] = sr( sx1, sy2, 0.0 );
 356    pointss[4] = sr( sx1, sy1, 0.0 );
 357    gdImageFilledPolygon(  im, pointss, 5, scolor );
 358 }
 359 
 360 //  draw cell wiring ---------------------------------
 361 //  This draws a cheesy wiring grid on the front of the cell
 362 
 363 void drawcellwire( int  wcolor, double  widthspace,
 364                     double centerx, double centery, double wsize ) {
 365 
 366    int    icnt ;
 367    int    nlines = (int) ( 0.6666666*wsize/widthspace-1.0 ) ;
 368    double vsize  = widthspace*( 1.5*nlines );
 369 
 370    // center bar
 371    RectS( wcolor,  centerx-widthspace, centery-vsize,  
 372                    centerx+widthspace, centery+vsize );
 373 
 374    // horizontal lines
 375    for( icnt=0 ; icnt<=nlines ; icnt++ ) {
 376      RectS(  wcolor,  -wsize+centerx, (3.0*icnt    )*widthspace+centery-vsize,
 377                        wsize+centerx, (3.0*icnt+1.0)*widthspace+centery-vsize );
 378    }
 379 }
 380 
 381 //=====================================================================
 382 
 383 int main () {
 384    FILE   *gifout;
 385    char   comment[80];
 386    double timem           ;            // time in minutes
 387    double t1, t2, t3      ;            // thrust value for each cell
 388    int    astep           ;            // time step in hundredths of seconds
 389    
 390    im1   = gdImageCreate (XSIZE, YSIZE);
 391    white = gdImageColorAllocate (im1, 255, 255, 255);
 392    black = gdImageColorAllocate (im1,   0,   0,   0);
 393    gray  = gdImageColorAllocate (im1, 127, 127, 127);
 394    dgray = gdImageColorAllocate (im1,  64,  64,  64);
 395    trans = gdImageColorAllocate (im1,   1,   1,   1);
 396 
 397    makestars();
 398 
 399    gifout = fopen ( GIFOUT , "wb");
 400    // gdImageGifAnimBegin( im1, gifout, 1, -1 ) ; // no repeat
 401    // gdImageGifAnimBegin( im1, gifout, 1,  4 ) ; // repeat 4 times
 402     gdImageGifAnimBegin( im1, gifout, 1,  0 ) ; // continuous repeat
 403 
 404    for( time = TSTART ; time <= TEND+0.1 ; time += TDELTA ) {
 405 
 406       //  start out stopped, display for a while
 407       if(   time <=   0.0 ) {
 408         angle_acc =  0.0 ;
 409         angle_vel =  0.0 ;
 410         angle     =  0.0 ;
 411         t1        =  0.5 ;
 412         t2        =  0.5 ;
 413         t3        =  0.5 ;
 414         astep     =  ASTEP0 ;
 415         strcpy( comment, "stopped" );
 416       }
 417       //  accelerate between   0 seconds and TIME1 seconds 
 418       else if( time < 1.0*TIME1 ) {
 419         timem     = time/ MINUTE ;
 420         angle_acc =  ACC ;
 421         angle_vel =  ACC * timem ;
 422         angle     =  ACC * 0.5 * timem * timem ;
 423         t1        =  0.0 ;
 424         t2        =  0.0 ;
 425         t3        =  1.0 ;
 426         astep     =  ASTEP1 ;
 427         strcpy( comment, "accelerating" );
 428       }
 429       //  decelerate between TIME1 seconds and 2*TIME1 seconds 
 430       else if( time < 2.0*TIME1 ) {
 431         timem     = (2.0*TIME1-time)/ MINUTE ;
 432         angle_acc = -ACC ;
 433         angle_vel =  ACC * timem ;
 434         angle     =  angle_max - ( 0.5 * ACC * timem * timem );
 435         t1        =  1.0 ;
 436         t2        =  1.0 ;
 437         t3        =  0.0 ;
 438         astep     =  ASTEP1 ;
 439         strcpy( comment, "decelerating" );
 440       }
 441       //  end up stopped, display for a while
 442       else {
 443         angle_acc = 0.0 ;
 444         angle_vel = 0.0 ;
 445         angle     =  angle_max;
 446         t1        =  0.5 ;
 447         t2        =  0.5 ;
 448         t3        =  0.5 ;
 449         astep     =  ASTEP0 ;
 450         strcpy( comment, "stopped" );
 451       }
 452 
 453       anglerad    = DEG2RAD( angle );
 454 
 455       im   = gdImageCreate( XSIZE, YSIZE ) ;
 456       gdImagePaletteCopy (  im, im1 ) ;
 457 
 458       drawstars();                   // draw stars     
 459       drawgauges();                  // draw gauges
 460 
 461       // draw serversat -----------------------------------------------
 462 
 463       gdImageSetThickness(  im, LINETHICK );
 464 
 465       drawcircle( gray,  1.0,  0.0000,  0.0000, 3.0000 );
 466       drawcircle( white,  t1, -3.4641,  2.0000, 1.0000 );
 467       drawcircle( white,  t2,  3.4641,  2.0000, 1.0000 );
 468       drawcircle( white,  t3,  0.0000, -4.0000, 1.0000 );
 469 
 470 #ifdef WIRING
 471       drawcellwire( gray, 0.10, 0.0000, 0.0000, 2.0000 );
 472 #endif // WIRING
 473 
 474       // arrow and status text
 475       
 476       RectS( white, -4.5000, -0.0001, 4.5000, 0.0001 ) ;
 477       arrow( white, -4.0000, 0.0000, 2.0000 ) ;
 478       arrow( white,  4.0000, 0.0000, 2.0000 ) ;
 479 
 480       gdImageStringFT( im, NULL, white, FNT, 2*FSZ, 0.0, FSZ, 4*FSZ, comment );
 481       
 482       // add frame to animation -------------------------------------
 483 
 484       gdImageGifAnimAdd  ( im, gifout, 0, 0, 2, astep,
 485                         gdDisposalRestoreBackground, im1 );
 486 
 487       im0 = im1 ;
 488       im1 = im  ;
 489       gdImageDestroy (im0);
 490   }
 491 
 492   gdImageGifAnimEnd(gifout);
 493   fclose (gifout);
 494   gdImageDestroy (im1);
 495   return 0;
 496 }

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2009-03-04 09:32:24, 668.4 KB) [[attachment:light-shift.png]]
  • [get | view] (2009-10-19 16:53:01, 18.4 KB) [[attachment:light-shift1.png]]
  • [get | view] (2009-03-04 00:18:21, 809.8 KB) [[attachment:nav_eastwest.png]]
  • [get | view] (2009-03-10 06:01:57, 10.4 KB) [[attachment:reflect01.png]]
  • [get | view] (2009-03-10 06:08:37, 348.6 KB) [[attachment:reflection_thrust.png]]
  • [get | view] (2009-04-19 01:27:56, 11.8 KB) [[attachment:ss4.c]]
  • [get | view] (2009-04-19 01:28:29, 669.8 KB) [[attachment:ss4.gif]]
  • [get | view] (2009-04-20 19:17:20, 16.0 KB) [[attachment:ss5.c]]
  • [get | view] (2009-04-20 19:17:38, 211.8 KB) [[attachment:ss5.gif]]
  • [get | view] (2009-06-04 04:48:18, 16.5 KB) [[attachment:ss5a.c]]
  • [get | view] (2022-03-15 23:58:04, 223.3 KB) [[attachment:ss5a.png]]
  • [get | view] (2009-06-04 04:48:43, 3085.2 KB) [[attachment:ss5a.swf]]
  • [get | view] (2009-03-04 00:18:47, 549.4 KB) [[attachment:tidal1.png]]
  • [get | view] (2009-03-03 03:31:55, 902.0 KB) [[attachment:tidal1a.png]]
 All files | Selected Files: delete move to page

You are not allowed to attach a file to this page.