don't click here

outrun-style road generation tutorial

Discussion in 'Technical Discussion' started by Cooljerk, May 21, 2014.

  1. Cooljerk

    Cooljerk

    NotEqual Tech, Inc - VR & Game Dev Oldbie
    4,505
    201
    43
    've been working on building a sprite-based 3d engine (think galaxy force 2) and was looking around for some help with the math for my projection when I found this site:

    http://www.extentofthejam.com/pseudo/

    Did a quick search and didn't find it'd ever been posted here. It's a break down of the math and logic behind outrun style roads, going into detail about hardware specific methods of implementation. Really good read, it appears the author consulted the researchers behind cannonball to make sure a lot of their math is accurate.

    Might be helpful for anybody attempting to make a 3d engine without using any 3d hardware or libraries.
     
  2. Aerosol

    Aerosol

    Not here. Moderator
    11,163
    573
    93
    Not where I want to be.
    Sonic (?): Coming summer of 2055...?
    I saw that page a long time ago. I was a little disappointed that he had removed the source code.
     
  3. Cooljerk

    Cooljerk

    NotEqual Tech, Inc - VR & Game Dev Oldbie
    4,505
    201
    43
    Cannonball run is open source, and I would expect it behaves similar to the methods he describes.

    Regardless, here's some allegro code that works very similarly:

    Code (Text):
    1. #include "allegro.h"
    2. #include <math.h>
    3.  
    4. void DrawPerspective(BITMAP *t, BITMAP *tex, int y1, float z1, int y2, float z2, float x, float xmul, float ymul, float texoff)
    5. {
    6.     float curvdiv = z1/(3.141592654f*0.5f);
    7.     float zpos = 1.0f /z2;
    8.     z1 = 1.0f / z1;
    9.  
    10.  
    11.     /* sample 16 times for each scanline */
    12.     y1 <<= 4;
    13.     y2 <<= 4;
    14.     ymul *= 16.0f;
    15.  
    16.     int BottomY = y2;
    17.     float zadd = (z1 - zpos) / (float)(y2 - y1);
    18.     while(y2 > y1)
    19.     {
    20.         float z = 1.0f / zpos;
    21.  
    22.         /*
    23.             NOTE: z is the actual depth of this scanline, zpos is 1/z. The most common
    24.             perspective projection formula is used, I.e.
    25.  
    26.             transformed_x = (centre of screen) - (half width of screen)*(x / z)
    27.  
    28.             But instead of dividing by z we can multiply by zpos if we like.
    29.         */
    30.         float centx = x + xmul*(cos(z/curvdiv) - 1.0f);
    31.         int topy = y2 + (SCREEN_H >> 1)*(ymul*(cos(z/curvdiv) - 1.0f) / z);
    32.  
    33.         if((topy >> 4) < (BottomY >> 4)) /* question is: are we at least one pixel line above what was last drawn ? */
    34.         {
    35.             stretch_blit(tex, t, 0, (int)((z+texoff)*4.0f)&(tex->h-1), tex->w, 1, (SCREEN_W >> 1) + (SCREEN_W >> 1)*(centx-1.0f)*zpos, (topy) >> 4, (SCREEN_W >> 1)*(2.0f)*zpos, (BottomY >> 4) -(topy >> 4));
    36.             BottomY = topy;
    37.         }
    38.  
    39.         zpos += zadd;
    40.         y2--;
    41.     }
    42. }
    43.  
    44. int main(int argc, const char *argv[])
    45. {
    46.     allegro_init();
    47.     install_keyboard();
    48.  
    49.     if (set_gfx_mode(GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0)) {
    50.         allegro_message("Error setting 640x480 gfx mode:\n%s\n", allegro_error);
    51.         return -1;
    52.     }
    53.     BITMAP *back = create_bitmap(SCREEN_W, SCREEN_H);
    54.  
    55.     /* obviously it would be better to load a road texture here, but we'll just generate a chequered
    56.     pattern ... */
    57.     BITMAP *tex = create_bitmap(8, 2);
    58.     int xp, yp;
    59.     for(xp = 0; xp < tex->w; xp++)
    60.         for(yp = 0; yp < tex->h; yp++)
    61.             putpixel(tex, xp, yp, (xp+yp)&1 ? makecol(255, 255, 255) : makecol(0, 0, 0));
    62.  
    63.     /* seed some variables */
    64.     float x = 0, xmul = 0, ymul = 0, z = 0;
    65.  
    66.     while(!key[KEY_ESC])
    67.     {
    68.         clear_to_color(back, makecol(0, 0, 0));
    69.         DrawPerspective(back, tex, SCREEN_H >> 1, 20, SCREEN_H, 1, x, xmul, ymul, z);
    70.         blit(back, screen, 0, 0, 0, 0, back->w, back->h);
    71.        
    72.         if(key[KEY_LEFT]) x+= 0.05f;
    73.         if(key[KEY_RIGHT]) x-= 0.05f;
    74.         if(key[KEY_UP]) z+= 0.05f;
    75.         if(key[KEY_DOWN]) z-= 0.05f;
    76.  
    77.         if(key[KEY_P]) xmul-= 0.1f;
    78.         if(key[KEY_O]) xmul+= 0.1f;
    79.         if(key[KEY_A]) ymul-= 0.1f;
    80.         if(key[KEY_Q]) ymul+= 0.1f;
    81.     }
    82.  
    83.     destroy_bitmap(back);
    84.     destroy_bitmap(tex);
    85.     return 0;
    86. }
    87. END_OF_MAIN()
    produces this:

    [​IMG]

    Courtesy of Thomas Harte over at gamedev.net
     
  4. Billy

    Billy

    RIP Oderus Urungus Member
    2,119
    179
    43
    Colorado, USA
    Indie games
    Very interesting! I myself and pretty fascinated by pre-polygon 3D. I'm making a Wolfenstein 3D raycaster style game.
     
  5. Cooljerk

    Cooljerk

    NotEqual Tech, Inc - VR & Game Dev Oldbie
    4,505
    201
    43
    I've never actually made a raycaster game myself, although I've studied the logic behind them. Sounds like a neat project, do you maintain a blog or anything? I'd love to read your progress.
     
  6. Billy

    Billy

    RIP Oderus Urungus Member
    2,119
    179
    43
    Colorado, USA
    Indie games
    I post updates on twitter sometimes. To be fair, the actual raycasting part isn't really the hard part, the game logic is; my game runs really slow right now There's a really good tutorial on raycasting here.
     
  7. ICEknight

    ICEknight

    Researcher Researcher
    I'm surprised that the linked page didn't examine OutRun 2016. That game featured some really cool intertwined roads.