''*****************************************************************************
'' 
''   2.5D Engine (Fixed Camera)
''   Relminator (Richard Eric M. Lope)
''    http://rel.phatcode.net
''
''    This is probably the easiest way to make a 3d game
''   Features:
''   * 1:1 ratio of 3D coords to Screen
''   * Collisions are in tilebased 2D
''   * Rendering can be camera based (uber fast)
''   * Any 2D tilebased game can be converted to this
''
''   Controls
''   
''   SPACE = Jump
''   ARROW KEYS = Move
''   F1 = Paper mario mode?
''   F2/F3 = Zoom
''
''*****************************************************************************

#include once "fbgfx.bi"
#include once "/gl/gl.bi" 
#include once "/gl/glu.bi"   


''*****************************************************************************
declare sub GL_Screen_Init(byval screen_wid as integer, byval screen_hei as integer, byval flags as integer = 0)

'' Drawing stuff
''*****************************************************************************
declare sub DrawCube( x as single, y as single, z as single, Size as single, Red as single, Green as single, Blue as single ) 
declare sub DrawMap()


''*****************************************************************************
const as integer SCREEN_WIDTH = 640
const as integer SCREEN_HEIGHT = 480

const as integer TILE_SIZE = 32


const as integer MAP_WID = SCREEN_WIDTH\TILE_SIZE      '' 20
const as integer MAP_HEI = SCREEN_HEIGHT\TILE_SIZE      '' 15

const as integer FALSE = 0
const as integer TRUE = not FALSE

const as single Gravity = 0.75
const as single JumpHeight = 15


''*****************************************************************************
dim shared as integer Map(MAP_WID-1,MAP_HEI-1) =>_
{{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},_  ''1
{1,0,0,1,0,0,0,0,0,0,1,0,0,0,1},_   ''2
{1,0,0,1,0,0,0,0,0,0,1,0,0,0,1},_   ''3
{1,0,0,1,0,0,0,0,0,0,1,0,0,0,1},_   ''4
{1,0,0,1,0,0,0,0,0,0,1,0,0,0,1},_   ''5
{1,0,0,0,1,0,0,0,0,0,0,0,0,0,1},_   ''6
{1,0,0,0,0,0,0,0,0,0,1,0,0,0,1},_   ''7
{1,0,0,0,0,0,0,1,0,0,1,0,0,0,1},_   ''8
{1,0,0,0,0,1,0,0,0,0,0,0,0,0,1},_   ''9
{1,0,0,0,0,0,0,0,0,0,0,0,1,0,1},_   ''10
{1,0,0,0,0,1,0,0,0,0,0,0,1,0,1},_   ''11
{1,0,0,0,0,1,0,0,0,0,0,0,1,0,1},_   ''12
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},_   ''13
{1,0,0,0,0,0,1,0,0,0,0,0,1,0,1},_   ''14
{1,0,0,0,0,0,0,0,0,0,0,1,0,0,1},_   ''15
{1,0,0,0,0,0,1,0,0,0,0,1,0,0,1},_   ''16
{1,0,0,0,0,1,0,0,0,0,0,0,1,0,1},_   ''17
{1,0,0,0,0,1,0,0,0,0,0,0,0,0,1},_   ''18
{1,0,0,0,1,0,0,0,0,0,0,0,0,0,1},_   ''19
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}}


''*****************************************************************************
''
''
''
''*****************************************************************************
type Player

   x      as single
   y      as single
   speed   as single
   xv      as single
   yv      as single
   wid      as integer
   hei      as integer
   
   CanJump as integer
   
   declare constructor()
   declare function CollideFloors(byval ix as integer, byval iy as integer, byref TileY as integer, Map() as integer ) as integer
   declare function CollideWalls(byval ix as integer, byval iy as integer, byref TileX as integer, Map() as integer ) as integer
   declare sub DoStuff()
   declare sub CollideOnMap( Map() as integer )
   declare sub Draw3D()
   
end type

constructor Player()
   x = 32 * 2 
   y = 32 * 5 
   speed = 4.5
   wid = 24
   hei = 48
   CanJump = false
End Constructor



function Player.CollideWalls(byval ix as integer, byval iy as integer, byref TileX as integer, Map() as integer ) as integer
   
   dim as integer TileYpixels = iy - (iy mod TILE_SIZE)   '' Pixel of the player's head snapped to map grid
   dim as integer TestEnd = iy + hei                  '' Foot of the player
   
   TileX = ix\TILE_SIZE                           '' Current X map coord the player is on + x-velocity(+ width when moving right)
   
   dim as integer TileY = TileYpixels\TILE_SIZE         '' Current Y map coord of the player's head
   
   '' Scan downwards from head to foot if we collided with a tile on the right or left
   while (TileYpixels <= TestEnd)
      if (Map(TileX, TileY))   then return true         '' Found a tile

      TileY += 1                                 '' Next tile downward
      TileYpixels +=TILE_SIZE                        '' Next tile pixel downward
      
   Wend
   
   return false
End Function

function Player.CollideFloors(byval ix as integer, byval iy as integer, byref TileY as integer, Map() as integer ) as integer
   
   dim as integer TileXpixels = ix - (ix mod TILE_SIZE)
   dim as integer TestEnd = ix + wid
   
   TileY = iy\TILE_SIZE
   
   dim as integer TileX = TileXpixels\TILE_SIZE
   
   while (TileXpixels <= TestEnd)
      if (Map(TileX, TileY))   then return true   

      TileX += 1
      TileXpixels +=TILE_SIZE
      
   Wend
   
   return false
End Function



sub Player.DoStuff()

   xv = 0
   
   if multikey(FB.SC_LEFT) then 
      xv = -speed
   EndIf
   
    if multikey(FB.SC_RIGHT) then 
       xv = speed
    EndIf
    
    if multikey(FB.SC_SPACE) then 
       if( CanJump ) then
          yv = -JumpHeight
          CanJump = false
       end if
    EndIf
    
    
    
End Sub

sub Player.CollideOnMap( Map() as integer )
   
   dim as Integer TileX, TileY

   if( xv > 0 ) then
      
      if( CollideWalls( x + xv + wid, y, TileX, Map() ) ) then
         x = TileX * TILE_SIZE - wid - 1
      else
         x += xv
      EndIf
         
   elseif( xv < 0 ) then
      
      if( CollideWalls( x + xv, y, TileX, Map() ) ) then
         x = ( TileX + 1 ) * TILE_SIZE + 1
      else
         x += xv
      EndIf
      
   EndIf
   
   if( yv < 0 ) then
      
      if( CollideFloors( x, y + yv, TileY, Map() ) ) then   '' hit the roof
         y = ( TileY + 1 ) * TILE_SIZE + 1
         yv = 0    
      else
         y += yv
         yv += Gravity
      EndIf
         
   else
      
      if( CollideFloors( x, y + yv + hei, TileY, Map() ) ) then
         y = ( TileY ) * TILE_SIZE - hei - 1
         yv = 1
         if not multikey(FB.SC_SPACE) then CanJump = true
      else
         y += yv
         yv += Gravity
         CanJump = false
      EndIf
      
   EndIf
   
   
End Sub


sub Player.Draw3D()

   '' Draws the player in 3D   
   dim as single SizeX = Wid/2.0
   dim as single SizeY = Hei/2.0
   
   
   glColor4f( 1, 0, 1, 1 )
   glPushMatrix()
      glTranslatef( x + Wid/2, y + Hei/2.0, 0) '' Offset by Size/2 so that we draw from top-left at (0,0)
      glBegin(GL_QUADS)
         glVertex3f(-SizeX, -SizeY,  0)
         glVertex3f( SizeX, -SizeY,  0)
         glVertex3f( SizeX,  SizeY,  0)
         glVertex3f(-SizeX,  SizeY,  0)         
      glEnd()
   glPopMatrix()
   
   
end sub

   
''*****************************************************************************
''
''
''
''*****************************************************************************

Type Vector2D

   x as Single
   y as Single
   z as single
   
end type

Type Camera
   
   
   declare Constructor()
   declare sub Follow( x as single, y as single )
   declare sub Look()
   declare sub Look( value as single )
   declare sub Zoom( value as single )

   
   
   Position as Vector2D 
   Target as Vector2D 
   Up as Vector2D 
   
   EyeDistanceFromScreen as integer

end type


constructor Camera()

   EyeDistanceFromScreen = TILE_SIZE * 18   '' Distance of player's eye from screen
   
   Position.x = 0
   Position.y = 0
   Position.z = EyeDistanceFromScreen       '' Yep we are drawing at z = 0 so pos should be positive
   
   Target.x = 0
   Target.y = 0
   Target.z = 0
   
   Up.x = 0
   Up.y = 1
   Up.z = 0
   
   
end constructor   

sub Camera.Follow( x as single, y as single )

   
   Position.x = x
   Position.y = y - TILE_SIZE * 1          '' offset a little above so we look a little down
   Position.z = EyeDistanceFromScreen      '' move zpos n units from screen to eye
   
   Target.x = x
   Target.y = y
   Target.z = 0
   
   Up.x = 0
   Up.y = 1
   Up.z = 0
   
end sub


sub Camera.Look()

   
   gluLookAt( Position.x, Position.y, Position.z,_    '' camera pos
            Target.x, Target.y, Target.z,_          '' camera target
               Up.x, Up.y, Up.z)

end sub



sub Camera.Zoom( value as single )

   EyeDistanceFromScreen +=value

end sub
   

''*****************************************************************************
sub main()
   
   
   dim as integer Frame = 0
    
   dim as Camera Cam
   dim as Player Mario
    
   GL_Screen_Init( SCREEN_WIDTH, SCREEN_HEIGHT )   ''Set up GL screen

   
   do
      
      Frame += 1

      Mario.DoStuff()     '' Do player movements
    
      Mario.CollideOnMap( Map() )      '' Do collisions in 2D
      
      
      '' Zoom if you want
      if( multikey(FB.SC_F2) ) then Cam.Zoom(1.62)
      if( multikey(FB.SC_F3) ) then Cam.Zoom(-1.62)
      
      
      
      glMatrixMode( GL_MODELVIEW )
      glLoadIdentity() 
      glPolygonMode( GL_FRONT, GL_FILL )
      glPolygonMode( GL_BACK, GL_FILL )
      glEnable( GL_DEPTH_TEST )
      glDepthFunc( GL_LEQUAL )
      
      glDisable( GL_TEXTURE_2D )
   
      
      glColor4ub( 255, 255, 255, 255 )

      '' Just fix the camera to the center of the screen
      Cam.Follow( SCREEN_WIDTH\2, SCREEN_HEIGHT\2 )
      
      '' reverse y direction for oldskool coords(Easy GL2D = happy)
      glScalef( 1, -1, 1 )
      
      '' Look
      Cam.Look()
      
      '' Paper mario?
      if( multikey(FB.SC_F1) ) then glRotatef( 20, 0, 1, 0 )
      
      '' Clear screen
      glClear(GL_COLOR_BUFFER_BIT OR GL_DEPTH_BUFFER_BIT)

      '' Draw 3D stuff
      glPushMatrix()   
               
         DrawMap()
         Mario.Draw3D()

      glPopMatrix()    
      
      '' limit FPS
      dim as single TimeStart = timer
      while( (timer - TimeStart) < (1.0/60.0) )
         sleep 1,1
      wend
      
      
      flip
      
   Loop until multikey(FB.SC_ESCAPE)

   
   
End Sub



main()


end








''*****************************************************************************
'' Temp way to draw our 32x32 tiles
sub DrawCube( x as single, y as single, z as single, Size as single, Red as single, Green as single, Blue as single )

   glColor4f( Red, Green, Blue, 1 )
   
   glPushMatrix()
      glTranslatef( x + Size, y + Size, z)  '' Offset by Size so that we draw from top-left at (0,0)
   
      glBegin(GL_QUADS)
         '' Front Face
         glColor3f( 1, 0, 0)
         glVertex3f(-Size, -Size,  Size)
         glColor3f( 0, 1, 0)
         glVertex3f( Size, -Size,  Size)
         glColor3f( 0, 0, 1)
         glVertex3f( Size,  Size,  Size)
         glColor3f( 1, 1, 1)
         glVertex3f(-Size,  Size,  Size)
         
         glColor4f( 1, 1, 0, 1 )
   
         '' Back Face
         glVertex3f(-Size, -Size, -Size)
         glVertex3f(-Size,  Size, -Size)
         glVertex3f( Size,  Size, -Size)
         glVertex3f( Size, -Size, -Size)
         '' Top Face
         glVertex3f(-Size,  Size, -Size)
         glVertex3f(-Size,  Size,  Size)
         glVertex3f( Size,  Size,  Size)
         glVertex3f( Size,  Size, -Size)
         '' Bottom Face
         glVertex3f(-Size, -Size, -Size)
         glVertex3f( Size, -Size, -Size)
         glVertex3f( Size, -Size,  Size)
         glVertex3f(-Size, -Size,  Size)
         '' Right face
         glVertex3f( Size, -Size, -Size)
         glVertex3f( Size,  Size, -Size)
         glVertex3f( Size,  Size,  Size)
         glVertex3f( Size, -Size,  Size)
         '' Left Face
         glVertex3f(-Size, -Size, -Size)
         glVertex3f(-Size, -Size,  Size)
         glVertex3f(-Size,  Size,  Size)
         glVertex3f(-Size,  Size, -Size)
      glEnd()
   
   glPopMatrix()
   
   glColor4f( 1, 1, 1, 1 )
   
   
end sub


'' Draws the map in 3D
sub DrawMap()

   for y as integer = 0 to (MAP_HEI - 1)
      for x as integer = 0 to (MAP_WID - 1)
         if( Map(x,y) > 0 ) then         
            DrawCube( x * TILE_SIZE, y * TILE_SIZE, 0, TILE_SIZE/2, 0, 1, 0 )
         end if
      next x   
   next y
   
end sub


''=============================================================================
''
''    Sets up OpenGL for 2d mode
''
''=============================================================================
sub GL_Screen_Init(byval screen_wid as integer, byval screen_hei as integer, byval flags as integer = 0)
   
   if flags then
      screenres screen_wid, screen_hei, 32, 2, FB.GFX_OPENGL or flags
   else
      screenres screen_wid, screen_hei, 32, 2, FB.GFX_OPENGL
   endif
   
   
   'screen information 
   dim w as integer, h as integer 
   'OpenGL params for gluerspective 
   dim FOVy as double            'Field of view angle in Y 
   dim Aspect as double          'Aspect of screen 
   dim znear as double           'z-near clip distance 
   dim zfar as double            'z-far clip distance 

   'using screen info w and h as params 
   glViewport(0, 0, screen_wid, screen_hei)
   
   'Set current Mode to projection(ie: 3d) 
   glMatrixMode(GL_PROJECTION) 
   
   'Load identity matrix to projection matrix 
   glLoadIdentity() 

   'Set gluPerspective params 
   FOVy = 90/2                                     '45 deg fovy 
   Aspect = screen_wid / screen_hei
   znear = 1                                       'Near clip 
   zfar = 1500                                     'far clip 
   
   'use glu Perspective to set our 3d frustum dimension up 
   gluPerspective(FOVy, aspect, znear, zfar) 
   
   'Modelview mode 
   'ie. Matrix that does things to anything we draw 
   'as in lines, points, tris, etc. 
   glMatrixMode(GL_MODELVIEW) 
   'load identity(clean) matrix to modelview 
   glLoadIdentity() 
   
   glShadeModel(GL_SMOOTH)                 'set shading to smooth(try GL_FLAT) 
   glClearColor(0.0, 0.0, 0.0, 1.0)        'set Clear color to BLACK 
   glClearDepth(1.0)                       'Set Depth buffer to 1(z-Buffer) 
   glEnable(GL_DEPTH_TEST)                 'Enable Depth Testing so that our z-buffer works 
   
   'compare each incoming pixel z value with the z value present in the depth buffer 
   'LEQUAL means than pixel is drawn if the incoming z value is less than 
   'or equal to the stored z value 
   glDepthFunc(GL_LEQUAL) 
   
   'have one or more material parameters track the current color 
   'Material is your 3d model 
   glEnable(GL_COLOR_MATERIAL) 


    'Enable Texturing 
    glEnable(GL_TEXTURE_2D) 
    

      'Tell openGL that we want the best possible perspective transform 
   glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) 
   
   'Disable Backface culling
   glDisable (GL_CULL_FACE)
   
   glPolygonMode(GL_FRONT, GL_FILL) 
   
   '' enable blending for transparency 
   glEnable(GL_BLEND)           
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
   
   glDisable( GL_DEPTH_TEST )
   
   glEnable( GL_ALPHA_TEST )
   glAlphaFunc(GL_GREATER, 0)


   glDisable(GL_STENCIL_TEST)
   glDisable(GL_TEXTURE_1D)
   glDisable(GL_LIGHTING)
   glDisable(GL_LOGIC_OP)
   glDisable(GL_DITHER)
   glDisable(GL_FOG)

   glHint(GL_POINT_SMOOTH_HINT, GL_FASTEST)
   glHint(GL_LINE_SMOOTH_HINT , GL_FASTEST)

   glPointSize( 1 )
   glLineWidth( 1 )
   
   
end sub
