1 /*
2  * Modification History
3  *
4  * 2007-September-25   Jason Rohrer
5  * Created.
6  */
7 
8 #include "landscape.h"
9 
10 #include "minorGems/graphics/Image.h"
11 #include "minorGems/graphics/converters/TGAImageConverter.h"
12 
13 #include "minorGems/io/file/File.h"
14 
15 #include "minorGems/io/file/FileInputStream.h"
16 
17 // #include "minorGems/system/Thread.h"
18 
19 
20 #include <math.h>
21 #include <signal.h>
22 #include <stdlib.h>
23 #include <time.h>
24 
25 
26 // for memcpy
27 #include <string.h>
28 
29 
30 
31 // size of game image
32 int width = 100;
33 int height = 16;
34 
35 // size of screen
36 int screenWidth = 640;
37 int screenHeight = 480;
38 
39 // blow-up factor when projecting game onto screen
40 int blowUpFactor = 6;
41 //int blowUpFactor = 1;
42 
43 
44 char fullScreen = false;
45 
46 double timeDelta = -0.1;
47 //double timeDelta = -0.0;
48 
49 
50 int mainFunction();
51 
52 
53 
54 // must do this to prevent WinMain linker errors on win32
main()55 int main() {
56     mainFunction();
57     }
58 
59 
60 
61 #include <SDL/SDL.h>
62 
63 
64 // the joystick to read from, or NULL if there's no available joystick
65 SDL_Joystick *joystick;
66 
67 
68 
catch_int(int sig_num)69 void catch_int(int sig_num) {
70 	printf( "Quiting...\n" );
71     SDL_Quit();
72 	exit( 0 );
73 	signal( SIGINT, catch_int );
74 	}
75 
76 
77 
78 
blowupOntoScreen(Uint32 * inImage,int inWidth,int inHeight,int inBlowFactor,SDL_Surface * inScreen)79 void blowupOntoScreen( Uint32 *inImage, int inWidth, int inHeight,
80                        int inBlowFactor, SDL_Surface *inScreen ) {
81 
82     int newWidth = inBlowFactor * inWidth;
83     int newHeight =  inBlowFactor * inHeight;
84 
85     int yOffset = ( inScreen->h - newHeight ) / 2;
86     int xOffset = ( inScreen->w - newWidth ) / 2;
87 
88     int endY = yOffset + newHeight;
89     int endX = xOffset + newWidth;
90 
91     Uint32 *pixels = (Uint32 *)( inScreen->pixels );
92 
93     for( int y=yOffset; y<endY; y++ ) {
94         int imageY = ( y - yOffset ) / inBlowFactor;
95 
96         for( int x=xOffset; x<endX; x++ ) {
97             int imageX = ( x - xOffset ) / inBlowFactor;
98 
99             pixels[ y * inScreen->w + x ] =
100                 inImage[ imageY * inWidth + imageX ];
101             }
102 
103         }
104 
105     }
106 
107 // values in range 0..1
108 Image *tileImage;
109 int imageW, imageH;
110 
111 // dimensions of one tile.  TileImage contains 13 tiles, stacked vertically,
112 // with blank lines between tiles
113 int tileW = 8;
114 int tileH = 8;
115 
116 
117 int numTileSets;
118 
119 // how wide the swath of a world is that uses a given tile set
120 int tileSetWorldSpan = 200;
121 // overlap during tile set transition
122 int tileSetWorldOverlap = 50;
123 
124 
125 
126 // optimization (found with profiler)
127 // values in range 0..255
128 double *tileRed;
129 double *tileGreen;
130 double *tileBlue;
131 
132 
133 Image *spriteImage;
134 int spriteW = 8;
135 int spriteH = 8;
136 
137 double *spriteRed;
138 double *spriteGreen;
139 double *spriteBlue;
140 
141 int currentSpriteIndex = 2;
142 
143 
144 
145 double playerX, playerY;
146 int playerRadius = spriteW / 2;
147 
148 int seed = time( NULL );
149 
150 
151 
isBlocked(int inX,int inY)152 inline char isBlocked( int inX, int inY ) {
153     // reduce to grid coordinates
154     int gridX = inX / tileW;
155     int gridY = inY / tileH;
156 
157     // wall along far left and top
158     if( gridX <=0 || gridY <= 0 ) {
159         return true;
160         }
161 
162     // make a grid of empty spaces from which blocks can be
163     // removed below to make a maze
164     if( gridX % 2 !=0 && gridY % 2 != 0 ) {
165         return false;
166         }
167 
168 
169     // blocks get denser as y increases
170     double threshold = 1 - gridY / 20.0;
171 
172     double randValue = noise4d( gridX, gridY, seed, 0 );
173     return randValue > threshold;
174     }
175 
176 
isSpriteTransparent(int inSpriteIndex)177 char isSpriteTransparent( int inSpriteIndex ) {
178     // take transparent color from corner
179     return
180         spriteRed[ inSpriteIndex ] == spriteRed[ 0 ]
181         &&
182         spriteGreen[ inSpriteIndex ] == spriteGreen[ 0 ]
183         &&
184         spriteBlue[ inSpriteIndex ] == spriteBlue[ 0 ];
185     }
186 
187 
188 
sampleFromWorld(int inX,int inY,double inWeight=1.0)189 Uint32 sampleFromWorld( int inX, int inY, double inWeight = 1.0 ) {
190 
191     // consider sampling from sprite
192 
193     // player position centered on sprint left-to-right
194     int spriteX = (int)( inX - playerX + spriteW / 2 );
195     // player position at sprite's feet
196     int spriteY = (int)( inY - playerY + spriteH - 1);
197 
198 
199     if( spriteX >= 0 && spriteX < spriteW
200         &&
201         spriteY >= 0 && spriteY < spriteH ) {
202 
203 
204         int spriteIndex = spriteY * spriteW + spriteX;
205 
206         // skip to appropriate sprite fram
207         spriteIndex += currentSpriteIndex * spriteW * ( spriteH + 1 );
208 
209         if( !isSpriteTransparent( spriteIndex ) ) {
210 
211             unsigned char r =
212                 (unsigned char)(
213                     inWeight * spriteRed[ spriteIndex ] );
214 
215             unsigned char g =
216                 (unsigned char)(
217                     inWeight * spriteGreen[ spriteIndex ] );
218 
219             unsigned char b =
220                 (unsigned char)(
221                     inWeight * spriteBlue[ spriteIndex ] );
222 
223             return r << 16 | g << 8 | b;
224             }
225         }
226 
227 
228     int tileIndex;
229 
230     if( !isBlocked( inX, inY ) ) {
231         // empty tile
232         tileIndex = 0;
233         }
234     else {
235         int neighborsBlockedBinary = 0;
236 
237         if( isBlocked( inX, inY - tileH ) ) {
238             // top
239             neighborsBlockedBinary = neighborsBlockedBinary | 1;
240             }
241         if( isBlocked( inX + tileW, inY ) ) {
242             // right
243             neighborsBlockedBinary = neighborsBlockedBinary | 1 << 1;
244             }
245         if( isBlocked( inX, inY + tileH ) ) {
246             // bottom
247             neighborsBlockedBinary = neighborsBlockedBinary | 1 << 2;
248             }
249         if( isBlocked( inX - tileW, inY ) ) {
250             // left
251             neighborsBlockedBinary = neighborsBlockedBinary | 1 << 3;
252             }
253 
254         // skip empty tile, treat as tile index
255         neighborsBlockedBinary += 1;
256 
257         tileIndex = neighborsBlockedBinary;
258         }
259 
260 
261     // pick a tile set
262     int netWorldSpan = tileSetWorldSpan + tileSetWorldOverlap;
263 
264     int tileSet = inX / netWorldSpan;
265     int overhang = inX % netWorldSpan;
266     if( inX < 0 ) {
267         // fix to a constant tile set below 0
268         overhang = 0;
269         tileSet = 0;
270         }
271 
272     // is there blending with next tile set?
273     int blendTileSet = tileSet + 1;
274     double blendWeight = 0;
275 
276     if( overhang > tileSetWorldSpan ) {
277         blendWeight = ( overhang - tileSetWorldSpan ) /
278             (double) tileSetWorldOverlap;
279         }
280     // else 100% blend of our first tile set
281 
282 
283     tileSet = tileSet % numTileSets;
284     blendTileSet = blendTileSet % numTileSets;
285 
286     // make sure not negative
287     if( tileSet < 0 ) {
288         tileSet += numTileSets;
289         }
290     if( blendTileSet < 0 ) {
291         blendTileSet += numTileSets;
292         }
293 
294 
295 
296 
297     // sample from tile image
298     int imageY = inY % tileH;
299     int imageX = inX % tileW;
300 
301 
302     if( imageX < 0 ) {
303         imageX += tileW;
304         }
305     if( imageY < 0 ) {
306         imageY += tileH;
307         }
308 
309     // offset to top left corner of tile
310     int tileImageOffset = tileIndex * ( tileH + 1 ) * imageW
311         + tileSet * (tileW + 1);
312     int blendTileImageOffset = tileIndex * ( tileH + 1 ) * imageW
313         + blendTileSet * (tileW + 1);
314 
315 
316     int imageIndex =  tileImageOffset + imageY * imageW + imageX;
317     int blendImageIndex =  blendTileImageOffset + imageY * imageW + imageX;
318 
319     unsigned char r =
320         (unsigned char)(
321             inWeight * (
322                 (1-blendWeight) * tileRed[ imageIndex ] +
323                 blendWeight * tileRed[ blendImageIndex ] ) );
324 
325     unsigned char g =
326         (unsigned char)(
327             inWeight * (
328                 (1-blendWeight) * tileGreen[ imageIndex ] +
329                 blendWeight * tileGreen[ blendImageIndex ] ) );
330 
331     unsigned char b =
332         (unsigned char)(
333             inWeight * (
334                 (1-blendWeight) * tileBlue[ imageIndex ] +
335                 blendWeight * tileBlue[ blendImageIndex ] ) );
336 
337 
338     return r << 16 | g << 8 | b;
339     }
340 
341 
342 
getKeyDown(int inKeyCode)343 char getKeyDown( int inKeyCode ) {
344     SDL_PumpEvents();
345 	Uint8 *keys = SDL_GetKeyState( NULL );
346 	return keys[ inKeyCode ] == SDL_PRESSED;
347     }
348 
349 
350 
getHatDown(Uint8 inHatPosition)351 char getHatDown( Uint8 inHatPosition ) {
352     if( joystick == NULL ) {
353         return false;
354         }
355 
356     SDL_JoystickUpdate();
357 
358     Uint8 hatPosition = SDL_JoystickGetHat( joystick, 1 );
359 
360     if( hatPosition & inHatPosition ) {
361         return true;
362         }
363     else {
364         return false;
365         }
366     }
367 
368 
369 
readTGA(char * inFileName)370 Image *readTGA( char *inFileName ) {
371     File tileFile( NULL, inFileName );
372     FileInputStream tileStream( &tileFile );
373 
374     TGAImageConverter converter;
375 
376     return converter.deformatImage( &tileStream );
377     }
378 
379 
380 
381 
mainFunction()382 int mainFunction() {
383 
384     // let catch_int handle interrupt (^c)
385     signal( SIGINT, catch_int );
386 
387 
388     // read in map tile
389     tileImage =  readTGA( "tileSet.tga" );
390 
391     imageW = tileImage->getWidth();
392     imageH = tileImage->getHeight();
393 
394     numTileSets = (imageW + 1) / (tileW + 1);
395 
396 
397     int imagePixelCount = imageW * imageH;
398 
399     tileRed =  new double[ imagePixelCount ];
400     tileGreen =  new double[ imagePixelCount ];
401     tileBlue =  new double[ imagePixelCount ];
402 
403     for( int i=0; i<imagePixelCount; i++ ) {
404         tileRed[i] = 255 * tileImage->getChannel(0)[ i ];
405         tileGreen[i] = 255 * tileImage->getChannel(1)[ i ];
406         tileBlue[i] = 255 * tileImage->getChannel(2)[ i ];
407         }
408 
409 
410     // read in map tile
411     spriteImage =  readTGA( "characterSprite.tga" );
412 
413     imagePixelCount = spriteImage->getWidth() * spriteImage->getHeight();
414 
415     spriteRed =  new double[ imagePixelCount ];
416     spriteGreen =  new double[ imagePixelCount ];
417     spriteBlue =  new double[ imagePixelCount ];
418 
419     for( int i=0; i<imagePixelCount; i++ ) {
420         spriteRed[i] = 255 * spriteImage->getChannel(0)[ i ];
421         spriteGreen[i] = 255 * spriteImage->getChannel(1)[ i ];
422         spriteBlue[i] = 255 * spriteImage->getChannel(2)[ i ];
423         }
424 
425 
426 
427     if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_JOYSTICK |
428                   SDL_INIT_NOPARACHUTE ) < 0 ) {
429         printf( "Couldn't initialize SDL: %s\n", SDL_GetError() );
430         return -1;
431         }
432 
433     SDL_ShowCursor( SDL_DISABLE );
434 
435     Uint32 flags = SDL_HWSURFACE | SDL_DOUBLEBUF;
436     if( fullScreen ) {
437         flags = flags | SDL_FULLSCREEN;
438         }
439 
440 
441     SDL_Surface *screen = SDL_SetVideoMode( screenWidth, screenHeight,
442                                             32,
443                                             flags );
444 
445     if ( screen == NULL ) {
446         printf( "Couldn't set %dx%dx32 video mode: %s\n", screenWidth,
447                 screenHeight,
448                 SDL_GetError() );
449         return-1;
450         }
451 
452     // try to open joystick
453     int numJoysticks = SDL_NumJoysticks();
454     printf( "Found %d joysticks\n", numJoysticks );
455 
456     if( numJoysticks > 0 ) {
457         // open first one by default
458         joystick = SDL_JoystickOpen( 1 );
459 
460         if( joystick == NULL ) {
461 			printf( "Couldn't open joystick 1: %s\n", SDL_GetError() );
462             }
463         int numHats = SDL_JoystickNumHats( joystick );
464 
465         if( numHats <= 0 ) {
466             printf( "No d-pad found on joystick\n" );
467             SDL_JoystickClose( joystick );
468             joystick = NULL;
469             }
470         }
471     else {
472         joystick = NULL;
473         }
474 
475 
476 
477     Uint32 *pixels = (Uint32 *)( screen->pixels );
478 
479     Uint32 *gameImage = new Uint32[ width * height ];
480 
481 
482     // first, fill the whole thing with black
483     SDL_FillRect(screen, NULL, 0x00000000);
484 
485     // small area in center that we actually draw in, black around it
486     int yOffset = ( screenHeight - height * blowUpFactor ) / 2;
487     int xOffset = ( screenWidth - width * blowUpFactor ) / 2;
488 
489 
490     double dX = 0;
491     double dY = 0;
492 
493     int frameCount = 0;
494     unsigned long startTime = time( NULL );
495 
496     char done = false;
497 
498     double maxWorldX = 0;
499     double minWorldX = 0;
500 
501 
502     // start player position
503     playerX = tileW;
504     //playerX = 0;
505     dX = tileW;
506     playerY = 0 + height/2;
507     dY = 0;
508 
509     while( !done ) {
510 
511         char someBlocksDrawn = false;
512 
513         for( int y=0; y<height; y++ ) {
514             for( int x=0; x<width; x++ ) {
515 
516                 // offset into center
517                 int gameImageIndex = y * width + x;
518 
519 
520 
521                 //int worldX = (int)( pow( x / 2.0, 1.4 ) );
522 
523                 // compression based on distance of pixel column from player
524                 double trueDistanceFromPlayer = dX + x - playerX;
525                 double maxDistance = width - 1;
526 
527                 // cap it so that we don't force tan into infinity below
528                 double cappedDistanceFromPlayer = trueDistanceFromPlayer;
529                 if( cappedDistanceFromPlayer > maxDistance ) {
530                     cappedDistanceFromPlayer = maxDistance;
531                     }
532                 if( cappedDistanceFromPlayer < -maxDistance ) {
533                     cappedDistanceFromPlayer = -maxDistance;
534                     }
535 
536                 // the world position we will sample from
537                 double worldX = x;
538 
539                 // zone around player where no x compression happens
540                 int noCompressZone = 10;
541 
542 
543                 if( trueDistanceFromPlayer > noCompressZone ) {
544 
545                     worldX =
546                         x +
547                         //(width/8) *
548                         // use true distance as a factor so that compression
549                         // continues at constant rate after we pass capped
550                         // distance
551                         // otherwise, compression stops after capped distance
552                         // Still... constant rate looks weird, so avoid
553                         // it by not letting it pass capped distance
554                         trueDistanceFromPlayer / 2 *
555                         ( pow( tan( ( ( cappedDistanceFromPlayer -
556                                         noCompressZone ) /
557                                       (double)( width - noCompressZone ) ) *
558                                     M_PI / 2 ), 2) );
559                     /*
560                         trueDistanceFromPlayer / 2 *
561                         ( tan( ( cappedDistanceFromPlayer /
562                                  (double)( width - 0.5 ) ) * M_PI / 2 ) );
563                     */
564                         // simpler formula
565                     // actually, this does not approach 0 as
566                     // cappedDistanceFromPlayer approaches 0, so use tan
567                     // instead
568                     //    ( trueDistanceFromPlayer / 2 ) * 100
569                     //  / pow( ( width - cappedDistanceFromPlayer ), 1.6 );
570                     }
571                 else if( trueDistanceFromPlayer < - noCompressZone ) {
572                     worldX =
573                         x +
574                         //(width/8) *
575                         trueDistanceFromPlayer / 2 *
576                         ( pow( tan( ( ( - cappedDistanceFromPlayer -
577                                         noCompressZone ) /
578                                       (double)( width - noCompressZone ) ) *
579                                     M_PI / 2 ), 2) );
580                         /*
581                         trueDistanceFromPlayer / 2 *
582                         ( tan( ( - cappedDistanceFromPlayer /
583                                  (double)( width - 0.5 ) ) * M_PI / 2 ) );
584                         */
585                         //( trueDistanceFromPlayer / 2 ) * 50
586                         /// ( width + cappedDistanceFromPlayer );
587                     }
588                 else {
589                     // inside no-compresison zone
590                     worldX = x;
591                     }
592 
593 
594                 //int worldX = x;
595 
596                 worldX += dX;
597 
598                 if( worldX > maxWorldX ) {
599                     maxWorldX = worldX;
600                     }
601                 if( worldX < minWorldX ) {
602                     minWorldX = worldX;
603                     }
604 
605 
606                 int worldY = (int)floor( y + dY );
607 
608 
609                 // linear interpolation of two samples for worldX
610                 int intWorldX = (int)floor( worldX );
611 
612                 double bWeight = worldX - intWorldX;
613                 double aWeight = 1 - bWeight;
614 
615 
616                 Uint32 sampleA =
617                     sampleFromWorld( intWorldX, worldY, aWeight );
618                 Uint32 sampleB =
619                     sampleFromWorld( intWorldX + 1, worldY, bWeight );
620 
621 
622 
623                 Uint32 combined = sampleB + sampleA;
624 
625 
626                 gameImage[ y * width + x ] = combined;
627 
628                 }
629             }
630 
631 
632         // check if we need to lock the screen
633         if( SDL_MUSTLOCK( screen ) ) {
634             if( SDL_LockSurface( screen ) < 0 ) {
635                 printf( "Couldn't lock screen: %s\n", SDL_GetError() );
636                 }
637             }
638 
639 
640         blowupOntoScreen( gameImage, width, height, blowUpFactor, screen );
641 
642 
643         // unlock the screen if necessary
644         if ( SDL_MUSTLOCK(screen) ) {
645             SDL_UnlockSurface(screen);
646             }
647 
648         if( ( screen->flags & SDL_DOUBLEBUF ) == SDL_DOUBLEBUF ) {
649             // need to flip buffer
650             SDL_Flip( screen );
651             printf( "double\n" );
652 
653             }
654         else {
655             // just update center
656             //SDL_UpdateRect( screen, yOffset, xOffset, width, height );
657             SDL_Flip( screen );
658             }
659 
660         int moveDelta = 1;
661         if( getKeyDown( SDLK_LEFT ) || getHatDown( SDL_HAT_LEFT ) ) {
662             if( currentSpriteIndex == 6 ) {
663                 currentSpriteIndex = 7;
664                 }
665             else {
666                 currentSpriteIndex = 6;
667                 }
668 
669             if( !isBlocked( (int)( playerX - moveDelta ), (int)playerY ) ) {
670 
671                 playerX -= moveDelta;
672 
673                 if( playerX < 0 ) {
674                     // undo
675                     playerX += moveDelta;
676                     }
677                 else {
678                     // update screen position
679                     dX -= moveDelta;
680                     }
681                 }
682             }
683         else if( getKeyDown( SDLK_RIGHT ) || getHatDown( SDL_HAT_RIGHT )) {
684             if( currentSpriteIndex == 2 ) {
685                 currentSpriteIndex = 3;
686                 }
687             else {
688                 currentSpriteIndex = 2;
689                 }
690 
691             if( !isBlocked( (int)( playerX + moveDelta ), (int)playerY ) ) {
692 
693                 dX += moveDelta;
694 
695                 playerX += moveDelta;
696                 }
697             }
698         else if( getKeyDown( SDLK_UP ) || getHatDown( SDL_HAT_UP ) ) {
699             if( currentSpriteIndex == 0 ) {
700                 currentSpriteIndex = 1;
701                 }
702             else {
703                 currentSpriteIndex = 0;
704                 }
705 
706             if( !isBlocked( (int)playerX, (int)( playerY - moveDelta ) ) ) {
707 
708                 playerY -= moveDelta;
709 
710                 if( playerY < 0 ) {
711                     // undo
712                     playerY += moveDelta;
713                     }
714                 else {
715                     // update screen position
716                     dY -= moveDelta;
717                     }
718                 }
719             }
720         else if( getKeyDown( SDLK_DOWN )  || getHatDown( SDL_HAT_DOWN )) {
721             if( currentSpriteIndex == 4 ) {
722                 currentSpriteIndex = 5;
723                 }
724             else {
725                 currentSpriteIndex = 4;
726                 }
727 
728             if( !isBlocked( (int)playerX, (int)( playerY + moveDelta ) ) ) {
729 
730                 dY += moveDelta;
731 
732                 playerY += moveDelta;
733                 }
734             }
735 
736 
737         // check for events to quit
738         SDL_Event event;
739         while( SDL_PollEvent(&event) ) {
740             switch( event.type ) {
741                 case SDL_KEYDOWN:
742                     switch( event.key.keysym.sym ) {
743                         case SDLK_q:
744                             done = true;
745                             break;
746                         }
747                     break;
748                 case SDL_QUIT:
749                     done = true;
750                     break;
751                 default:
752                     break;
753                 }
754             }
755 
756         //t +=0.25;
757         frameCount ++;
758 
759         // player position on screen inches forward
760         dX += timeDelta;
761         //dX -= 1;
762         // stop after player has gone off right end of screen
763         if( playerX - dX > width ) {
764             dX = playerX - width;
765             }
766 
767 
768         }
769 
770     unsigned long netTime = time( NULL ) - startTime;
771     double frameRate = frameCount / (double)netTime;
772 
773     printf( "Max world x = %f\n", maxWorldX );
774     printf( "Min world x = %f\n", minWorldX );
775 
776     printf( "Frame rate = %f fps (%d frames)\n", frameRate, frameCount );
777     fflush( stdout );
778 
779 
780 
781     delete tileImage;
782     delete [] tileRed;
783     delete [] tileGreen;
784     delete [] tileBlue;
785 
786 
787     delete spriteImage;
788     delete [] spriteRed;
789     delete [] spriteGreen;
790     delete [] spriteBlue;
791 
792 
793     if( joystick != NULL ) {
794         SDL_JoystickClose( joystick );
795         }
796 
797     SDL_Quit();
798     }
799