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