/*  Raycaster
    by Brent P. Newhall
    Written: June 8, 1999

    This is a very simple Wolfenstein 3D-esque raycaster.  It's intended for
    educational purposes, so that people can see how a raycaster works, rather
    than trying to be the underlying engine for a full-fledged game.

    This program is PUBLIC DOMAIN.  Do whatever you want with it; just give
    credit where it's due.

    Instructions:
    Couldn't be simpler.  Just explore the world a bit.

    [UP]    == Move forward
    [BACK]  == Move backward
    [LEFT]  == Turn left
    [RIGHT] == Turn right
    [T]     == Teleport back to starting position
    [ESC]   == Quit

    Cool Things To Add:
    Color.
    Texture mapping.
    Objects to interact with.
    Put a loop in DrawWorld() so it keeps updating no matter what the player does
       (so a slavering monster can come around the corner without the player having
       to touch the keyboard).
    Autonomous creatures walking around.

    Performance issues:
    I was able to move as fast as KeyDown() would allow on my Pentium II 233 with
    32 MB of RAM.  There is noticeable flickering, though.
*/

#include <application .h="">
#include <window .h="">
#include <view .h="">


class RaycasterView : public BView
{
  public:
    RaycasterView(BRect frame);
    virtual void KeyDown( const char *bytes, int32 numBytes );
    virtual void Draw(BRect updateRect);
  private:
    void DrawWorld( void ); /* Draws the forward view */
    float player_x, player_y, player_angle; /* Player position */
    int wall_grid[12][12]; /* The level */
    float SineFunctions[360]; /* Cached sine and cosine values */
    float CosineFunctions[360];
};

RaycasterView :: RaycasterView(BRect frame)
: BView( frame, "HelloView", B_FOLLOW_ALL_SIDES, B_WILL_DRAW )
{
  int i;
  /* Brent's boring (yawn) table creation.
     This caches the sine and cosine functions so we don't have to
        actually calculate sin() and cos() constantly in the program. */
  for( i = 0; i &lt;= 360; i++ )
    SineFunctions[i] = sin((float)i * 0.0174) * 100;
  for( i = 0; i &lt;= 360; i++ )
    CosineFunctions[i] = cos((float)i * 0.0174) * 100;
  /* Set up level data */
  for( i = 0; i &lt;= 12; i++ ) /* The walls bordering the world */
  {
    wall_grid[i][0] = 1;
    wall_grid[i][12] = 1;
    wall_grid[0][i] = 1;
    wall_grid[12][i] = 1;
  }
  for( i = 4; i &lt;= 12; i++ ) /* Inner walls */
    wall_grid[i][4] = 1;
  for( i = 1; i &lt;= 10; i++ )
    wall_grid[i][7] = 1;
  /* Set up initial player position */
  player_x = 15;
  player_y = 35;
  player_angle = 0;
  /* Display the world */
  DrawWorld();
}

void RaycasterView :: KeyDown( const char *bytes, int32 numBytes )
{
  if( bytes[0] == B_RIGHT_ARROW )
  {
    /* Rotate the player three degrees to the right and redraw */
    player_angle = player_angle + 3;
    DrawWorld();
  }
  else if( bytes[0] == B_LEFT_ARROW )
  {
    /* Rotate the player three degrees to the left and redraw */
    player_angle = (int)(player_angle + 357) % 360;
    DrawWorld();
  }
  else if( bytes[0] == B_UP_ARROW )
  {
    /* Move the player forward and redraw, if possible */
    float old_player_x, old_player_y;
    /* Save current position */
    old_player_x = player_x;
    old_player_y = player_y;
    /* Calculate the player's new position */
    player_x = player_x + (SineFunctions[(int)(player_angle + 39) % 360] / 50);
    player_y = player_y + (CosineFunctions[(int)(player_angle + 39) % 360] / 50);
    /* Walking through a wall? */
    if( wall_grid[(int)(player_y / 10)][(int)(player_x / 10)] &gt; 0 )
    {
      /* If so, don't update position */
      player_x = old_player_x;
      player_y = old_player_y;
    }
    else {
      DrawWorld();
    }
  }
  else if( bytes[0] == B_DOWN_ARROW )
  {
    /* Move the player backward and redraw, if possible */
    float old_player_x, old_player_y;
    /* Save current position */
    old_player_x = player_x;
    old_player_y = player_y;
    /* Calculate the player's new position */
    player_x = player_x - (SineFunctions[(int)(player_angle + 39) % 360] / 50);
    player_y = player_y - (CosineFunctions[(int)(player_angle + 39) % 360] / 50);
    /* Walking through walls? */
    if( wall_grid[(int)(player_y / 10)][(int)(player_x / 10)] &gt; 0 )
    {
      /* If so, don't update position */
      player_x = old_player_x;
      player_y = old_player_y;
    }
    else {
      DrawWorld();
    }
  }
  else if( bytes[0] == 't' || bytes[0] == 'T' )
  {
    /* Teleport back to original position.  Silly, but eh. */
    player_x = 15;
    player_y = 35;
    player_angle = 0;
    DrawWorld();
  }
  else if( bytes[0] == B_ESCAPE )
  {
    /* Quit */
    exit( 0 );
  }
}

void RaycasterView :: Draw(BRect updateRect)
{
  /* Called when the view is first being displayed, or being redrawn. */
  BPoint start, end;
  /* Draw the main bit */
  DrawWorld();
  /* Now draw all the extra stuff */
  start.Set( 395, 0 );
  end.Set( 395, 200 ); /* Draw line on right */
  StrokeLine( start, end, B_SOLID_HIGH );
  /* Display silly text messages */
  start.Set( 415, 85 );
  DrawString( "Ray", start, NULL );
  start.Set( 407, 100 );
  DrawString( "caster", start, NULL );
  start.Set( 418, 155 );
  DrawString( "by", start, NULL );
  start.Set( 409, 170 );
  DrawString( "Brent", start, NULL );
  start.Set( 402, 185 );
  DrawString( "Newhall", start, NULL );
}

void RaycasterView :: DrawWorld( void )
{
  float ray_x, ray_y;
  int ray_angle, ray_length;
  float x_increment, y_increment, wall_height;
  int wall, X;
  BRect rect;
  /* Erase whatever was on the screen */
  rect.Set( 0, 0, 394, 200 );
  FillRect( rect, B_SOLID_LOW );
  /* Loop from player_angle, over an 80-degree swath */
  for( ray_angle = (int)player_angle; ray_angle &lt; (int)player_angle + 79; ray_angle++ )
  {
    /* Calculate movement based on current angle */
    x_increment = SineFunctions[ray_angle % 360] / 100;
    y_increment = CosineFunctions[ray_angle % 360] / 100;
    /* Ray starts at player's position, with a length of 0 */
    ray_x = player_x;
    ray_y = player_y;
    ray_length = 0;
    wall = 0;
    /* Loop until the ray hits something */
    while( wall == 0 )
    {
      /* Move the ray out */
      ray_x = ray_x + x_increment;
      ray_y = ray_y + y_increment;
      ray_length = ray_length + 1;
      /* Find out what it's hit, if anything, on the grid */
      wall = wall_grid[(int)(ray_y / 10)][(int)(ray_x / 10)];
    } /* end while no wall */
    /* Get a practical height for the wall segment */
    wall_height = 1000 / ray_length;
    /* Find the correct position on the screen */
    X = (ray_angle - (int)player_angle) * 5;
    rect.Set( X, 100 - wall_height, X + 4, 100 + wall_height ); /* Draw wall segment */
    FillRect( rect, B_SOLID_HIGH );
  } /* end for(ray_angle) */
}



class MainWindow : public BWindow
{
  public:
    MainWindow(BRect frame);
    virtual bool QuitRequested();
  private:
    RaycasterView *viewRaycaster;
};

MainWindow :: MainWindow(BRect frame)
: BWindow(frame, "Raycaster", B_TITLED_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE)
{
  /* Set up and display the view */
  viewRaycaster = new RaycasterView( Bounds() );
  AddChild( viewRaycaster );
  viewRaycaster-&gt;MakeFocus(); /* Lets the user interact with this view.  Must be BEFORE Show() ! */
  Show();
}

bool MainWindow::QuitRequested()
{
  be_app-&gt;PostMessage(B_QUIT_REQUESTED);
  return true;
}



class RaycasterApp : public BApplication
{
  public:
    RaycasterApp();
  private:
    MainWindow *winMain;
};

RaycasterApp :: RaycasterApp()
: BApplication( "application/x-vnd.Tutorial-Raycaster" )
{
  /* Set up and display the window */
  BRect windowRect;
  windowRect.Set(50,50,500,250);
  winMain = new MainWindow(windowRect);
}



int main( void )
{
  RaycasterApp *theApp; /* Pointer to our application object */
  theApp = new(RaycasterApp);
  theApp-&gt;Run();
  return 0;
}
