now with floor and ceiling tiles, just make a new bmp 64x64 and call it grass1.bmp (i'm too lazy to upload my graphic at the moment.
Code: Select all
; Raycasting example: has variable image height and direct screen access.
; Some general stuff
Global mapWidth.l = 24
Global mapHeight.l = 24
Global screenWidth.l =320
Global screenHeight.l = 200
Global texWidth.l = 64
Global texHeight.l = 64
Global Dim PixelZ.d (screenWidth, screenHeight) ;Pixel Z buffer
Global *Screen.l ;Pointer to screen pixel data
Global BPP.b ;screen bytes per pixel
;Structure that holds Sprite info
Structure Structure_Graphics
Sprite.l ;pb sprite
*Pointer.l ;pointer to pixel data
Width.l ;Width of the graphic (not necessary as of yet, because all widths in this example are 64 pixels)
Height.l ;Height of the graphic (very necessary for variable heights)
EndStructure
Global Dim gfx.Structure_Graphics(0)
Global gfx_count.l = -1 ;# of items in our gfx structured array
Procedure.f FindAngle(X.f, Y.f)
If 0 < X.f
;Calculate the gradient of the line joining A and B.
gradient.f=(-Y.f)/(X.f)
;Calculate the angle in radians.
angleRads.f = ATan(gradient)
;Convert to degrees if required.
angleDegs.f = 180*angleRads/#PI
If angleDegs < 0 : angleDegs + 360 : EndIf
ProcedureReturn angleDegs
ElseIf 0 > X.f
;Calculate the gradient of the line joining A and B.
gradient.f=(Y.f)/(-X.f)
;Calculate the angle in radians.
angleRads.f = ATan(gradient)
;Convert to degrees if required.
angleDegs.f = (180*angleRads/#PI) + 180
If angleDegs < 0 : angleDegs + 360 : EndIf
ProcedureReturn angleDegs
Else ; X is 0
If 0 > Y.f
ProcedureReturn 90
Else
ProcedureReturn 270
EndIf
EndIf
EndProcedure
Procedure Set_Pixel_(*surface.l, w, x, y, color)
;sets pixel data in a buffer (*surface), assums that color is in the correct format
;(w) width of image is needed to determin correct position.
;Global BPP already needs to be set elsewhere.
PokeB(*surface + ( (y * w + x) * bpp ) + 0, PeekB(@color + 0) )
PokeB(*surface + ( (y * w + x) * bpp ) + 1, PeekB(@color + 1) )
PokeB(*surface + ( (y * w + x) * bpp ) + 2, PeekB(@color + 2) )
;PokeB(*surface +( (y * w + x) * bpp ) + 3, PeekB(@color + 3) ) ;not using alpha
EndProcedure
Procedure.l Get_Pixel_(*surface.l, w, x, y)
;returns a color (long) from pixel data
;*surface is the pointer to pixel data.
;(w) width of image is needed to determin correct position.
;Global BPP already needs to be set elsewhere.
retColor.l
PokeB(@retColor.l + 0, PeekB(*surface + ((y * w + x) * BPP) + 0))
PokeB(@retColor.l + 1, PeekB(*surface + ((y * w + x) * BPP) + 1))
PokeB(@retColor.l + 2, PeekB(*surface + ((y * w + x) * BPP) + 2))
;PokeB(@retColor.l + 3, PeekB(*surface + ((y * w + x) * BPP) + 3)) ;not using alpha
ProcedureReturn retColor
EndProcedure
Structure Structure_RGB
r.b
g.b
b.b
a.b ;alpha
EndStructure
Global RGB_fix.Structure_RGB
Procedure.l RGBf(r.b, g.b, b.b)
rgb_.l
PokeB(@rgb_ + RGB_fix\r, r)
PokeB(@rgb_ + RGB_fix\g, g)
PokeB(@rgb_ + RGB_fix\b, b)
ProcedureReturn rgb_
EndProcedure
Procedure.l LoadGraphic(FileName.s, Type.b)
gfx_count + 1
ReDim gfx.Structure_Graphics(gfx_count)
gfx(gfx_count)\Sprite = LoadSprite(#PB_Any, FileName.s)
StartDrawing(SpriteOutput(gfx(gfx_count)\Sprite))
gfx(gfx_count)\Pointer = DrawingBuffer() ;get the pointer to sprite data
StopDrawing()
gfx(gfx_count)\Width = SpriteWidth(gfx(gfx_count)\Sprite)
gfx(gfx_count)\Height = SpriteHeight(gfx(gfx_count)\Sprite)
EndProcedure
; Map container
Global Dim worldMap.b(mapWidth, mapHeight)
; Texture
Global Dim tex.l(texWidth,texWidth)
; Declare render funcs
Declare RenderTexturedWalls()
; Read map
Global x.l = 0
Global y.l = 0
For y.l = 0 To mapHeight-1
For x.l = 0 To mapWidth-1
Read.b worldMap(x, y)
Next x
Next y
InitKeyboard() : InitSprite() : InitMouse()
OpenWindow(0, 0, 0, screenWidth , screenHeight, "Raycast Example #2", #PB_Window_ScreenCentered | #PB_Window_MinimizeGadget | #PB_Window_SizeGadget)
OpenWindowedScreen(WindowID(0), 0, 0, screenWidth, screenHeight, 0, 0, 0)
;OpenScreen(screenWidth, screenHeight, 32, "")
;We only need to call StartDrawing(ScreenOutput()) once to grab the pointer to the drawing buffer.
; That address never changes while the program is running and we'll have it forever.
StartDrawing(ScreenOutput())
;grab a pointer to the screen data
*Screen = DrawingBuffer()
;fix endian problem and get ScreenBPP (on my computer the pixel format is BBGGRR, but incase yours is different...)
Select DrawingBufferPixelFormat()
Case #PB_PixelFormat_24Bits_RGB ; 3 bytes per pixel (RRGGBB)
RGB_fix\R = 0 : RGB_fix\G = 1 : RGB_fix\B = 2
BPP = 3
Case #PB_PixelFormat_24Bits_BGR ; 3 bytes per pixel (BBGGRR)
RGB_fix\B = 0 : RGB_fix\G = 1 : RGB_fix\R = 2
BPP = 3
Case #PB_PixelFormat_32Bits_RGB ; 4 bytes per pixel (RRGGBB)
RGB_fix\R = 0 : RGB_fix\G = 1 : RGB_fix\B = 2
BPP = 4
Case #PB_PixelFormat_32Bits_BGR ; 4 bytes per pixel (BBGGRR)
RGB_fix\B = 0 : RGB_fix\G = 1 : RGB_fix\R = 2
BPP = 4
EndSelect
StopDrawing()
;LOAD TEXTURES HERE
LoadGraphic("grass1.bmp", 0)
LoadGraphic("greystone.bmp", 0) ;1
LoadGraphic("gen2.bmp", 0)
LoadGraphic("greystone.bmp", 0)
LoadGraphic("greystone.bmp", 0)
LoadGraphic("gen1.bmp", 0) ;5 is a 64x128 image
; Start position
Global posX.f = 22
Global posY.f = 12
; Initial direction vector
Global dirX.f = -1
Global dirY.f = 0
; 2D camera plane
Global planeX.f = 0
Global planeY.f = 0.66
; Frame timing
Global time.f = 0
Global oldTime.f = 0
;my skybox images are each 320x100
sky1 = LoadSprite(#PB_Any, "sky1.bmp")
sky2 = LoadSprite(#PB_Any, "sky2.bmp")
sky3 = LoadSprite(#PB_Any, "sky3.bmp")
sky4 = LoadSprite(#PB_Any, "sky4.bmp")
; Main loop
Repeat
Dim PixelZ.d(screenWidth, screenHeight)
; Pump window events
;Global Event.l = WindowEvent()
ClearScreen(RGB(50, 50, 50))
skyoff = -(FindAngle(planeX*1000, planeY*1000) * 3.55)
DisplaySprite(sky4, skyoff - 320, 0)
DisplaySprite(sky1, skyoff , 0)
DisplaySprite(sky2, skyoff + 320, 0)
DisplaySprite(sky3, skyoff + 640, 0)
DisplaySprite(sky4, skyoff + 960, 0)
DisplaySprite(sky1, skyoff + 1280, 0)
; Render textured walls
RenderTexturedWalls()
; Flip buffers
FlipBuffers()
; Check keyboard + timed movement
ExamineKeyboard()
ExamineMouse()
; Calculate frame timing
oldTime = time;
time = ElapsedMilliseconds();
Global frameTime.f = (time - oldTime) / 1000.0
; Set FPS in title bar (wonky in PB. Dunno why. Works in C++/Flash version ;)
;SetWindowTitle(0, "Raycast Example #1 (FPS: " + Str(Int(1.0 / frameTime)) + ")")
; Calculate speed modifiers (constant values are squares/second and radians/second)
Global moveSpeed.f = frameTime * 5.0
;Global rotSpeed.f = frameTime * 3.0
Global rotSpeed.f = -MouseDeltaX() / 15 * frameTime
;some debug stuff
If KeyboardPushed(#PB_Key_Space)
;Debug "X: " + StrF(posX) + " Y: " + StrF(posY)
;Debug "pX: " + StrF(planeX) + " pY: " + StrF(planeY)
;Debug "A: " + StrF(FindAngle(planeX*1000, planeY*1000))
Debug MouseDeltaX()
EndIf
;step right?
If KeyboardPushed(#PB_Key_Right)
If worldMap(Int(posX + planeX * moveSpeed), Int(posY)) = 0
posX = posX + ( planeX * moveSpeed)
EndIf
If worldMap(Int(posX), Int(posY + planeY * moveSpeed)) = 0
posY = posY + ( planeY * moveSpeed)
EndIf
EndIf
;step left?
If KeyboardPushed(#PB_Key_Left)
If worldMap(Int(posX - planeX * moveSpeed),Int(posY)) = 0
posX = posX - (planeX * moveSpeed)
EndIf
If worldMap(Int(posX),Int(posY - planeY * moveSpeed)) = 0
posY = posY - (planeY * moveSpeed)
EndIf
EndIf
; Move forward?
If KeyboardPushed(#PB_Key_Up)
If worldMap(Int(posX + dirX * moveSpeed), Int(posY)) = 0
posX = posX + (dirX * moveSpeed)
EndIf
If worldMap(Int(posX), Int(posY + dirY * moveSpeed)) = 0
posY = posY + (dirY * moveSpeed)
EndIf
EndIf
; Move back?
If KeyboardPushed(#PB_Key_Down)
If worldMap(Int(posX - dirX * moveSpeed),Int(posY)) = 0
posX = posX - (dirX * moveSpeed)
EndIf
If worldMap(Int(posX),Int(posY - dirY * moveSpeed)) = 0
posY = posY - (dirY * moveSpeed)
EndIf
EndIf
; Rotate right? (Both camera and camera plane needs to be rotated)
;If KeyboardPushed(#PB_Key_Right)
If MouseDeltaX() <> 0
Global oldDirX.f = dirX
dirX = dirX * Cos(rotSpeed) - dirY * Sin(rotSpeed)
dirY = oldDirX * Sin(rotSpeed) + dirY * Cos(rotSpeed)
Global oldPlaneX.f = planeX
planeX = planeX * Cos(rotSpeed) - planeY * Sin(rotSpeed)
planeY = oldPlaneX * Sin(rotSpeed) + planeY * Cos(rotSpeed)
EndIf
Until KeyboardPushed(#PB_Key_Escape)
; Render textured walls
Procedure RenderTexturedWalls()
; Misc vars
Protected x.l = 0
; Draw some boxes
;Box(0,0,screenWidth, screenHeight/2, RGB(10,10,255))
; Render wall strips for each vertical screen row (640 in this case. 320 in good old Wolfenstein)
For x.l = 0 To screenWidth-1
; Calculate ray position and direction
Protected cameraX.f = 2 * x / screenWidth-1
Protected rayPosX.f = posX
Protected rayPosY.f = posY
Protected rayDirX.f = dirX + planeX * cameraX
Protected rayDirY.f = dirY + planeY * cameraX
; Determine map tile we're in
Protected mapX.l = Int(rayPosX)
Protected mapY.l = Int(rayPosY)
; Length of ray from current position to next x or y-side
Protected sideDistX.f = 0
Protected sideDistY.f = 0
; Length of ray from one x or y-side to next x or y-side
Protected deltaDistX.f = Sqr(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX))
Protected deltaDistY.f = Sqr(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY))
Protected perpWallDist.f
; What direction to step in x or y-direction (either +1 or -1)
Protected stepX.l
Protected stepY.l
; Was a wall hit, and if so which side
Protected hit.l = 0
Protected side.l
; Calculate step and initial sideDist
If rayDirX < 0
stepX = -1
sideDistX = (rayPosX - mapX) * deltaDistX
Else
stepX = 1
sideDistX = (mapX + 1.0 - rayPosX) * deltaDistX
EndIf
If (rayDirY < 0)
stepY = -1
sideDistY = (rayPosY - mapY) * deltaDistY
Else
stepY = 1
sideDistY = (mapY + 1.0 - rayPosY) * deltaDistY
EndIf
; Perform DDA algorithm for true hit detection
While hit = 0
; Jump to next map square depending on direction
If sideDistX < sideDistY
sideDistX = sideDistX + deltaDistX
mapX = mapX + stepX
side = 0
Else
sideDistY = sideDistY + deltaDistY
mapY = mapY + stepY
side = 1
EndIf
; See if ray has hit a wall
If MapX < 0 Or MapX > mapWidth Or MapY < 0 Or MapY > mapHeight
hit = 1
Else
If worldMap(mapX, mapY) > 0
;hit = 1
; Calculate distance projected onto camera direction (includes fisheye removal)
If side = 0
perpWallDist = Abs((mapX - rayPosX + (1 - stepX) / 2) / rayDirX)
Else
perpWallDist = Abs((mapY - rayPosY + (1 - stepY) / 2) / rayDirY)
EndIf
; Calculate height of line to draw onto screen
Protected lineHeight.l = Abs(Int(screenHeight / perpWallDist)) * (gfx(worldMap(mapX, mapY))\Height / 64)
Protected halves.d = gfx(worldMap(mapX, mapY))\Height / 32
; Calculate lowest and highest pixel for current line strip
Protected drawStart.l = (-lineHeight * ( (halves-1) / halves) + (screenHeight / 2) )
If drawStart < 0
drawStart = 0
EndIf
Protected drawEnd.l = (lineHeight * (1 / halves) + (screenHeight / 2) )
If drawEnd >= screenHeight
drawEnd = screenHeight - 1
EndIf
; Determine exact point where wall was hit for texture generation
Protected wallX.f
If side = 1
wallX = rayPosX + ((mapY - rayPosY + (1 - stepY) / 2) / rayDirY) * rayDirX
Else
wallX = rayPosY + ((mapX - rayPosX + (1 - stepX) / 2) / rayDirX) * rayDirY
EndIf
wallX = wallX - Int(wallX); What no floor? Also added abs because of this. Line should be 'wallX = wallX - Floor(wallX)'
wallX = Abs(wallX) ; Should not be here, but since we got no floor :)
; Calculate x coordinate on the texture
Protected texX.l = Int(wallX * texWidth);
If side = 0 And rayDirX > 0
texX = texWidth - texX - 1
EndIf
If side = 1 And rayDirY < 0
texX = texWidth - texX - 1
EndIf
; Render textured strip
Protected y.l = 0
For y = drawStart To drawEnd - 1
If PixelZ(x, y) > perpWallDist Or PixelZ(x, y) = 0
Protected d.l = y * 256 - screenHeight *128 + lineHeight * 128 ;Avoid floats
Protected texY.l = ((d * gfx(worldMap(mapX, mapY))\height) / (lineHeight)) / 256
color = Get_Pixel_(gfx(worldMap(mapX, mapY))\Pointer, gfx(worldMap(mapx, mapy))\width, texX, texY+((halves-2)*16))
If color <> 16711935
; Again shift to darken color depending on side
If side = 1
color = (color >> 1) & 8355711
EndIf
; Draw pixel
Light = ((perpWallDist*255)/20)
If Light < 0 : Light = 0 : EndIf
If Light > 255 : Light = 255 : EndIf
Factor.f = (255 - Light)/255
Red_ = Red(color) *Factor
Green_ = Green(color)*Factor
Blue_ = Blue(color)*Factor
color = RGB(red_, green_, blue_)
Set_Pixel_(*screen, screenWidth, x, y, color)
PixelZ(x, y) = perpWallDist
Else
If worldMap(mapX, mapY) <> 1
Hit = 0
EndIf
EndIf
EndIf
Next
; //FLOOR CASTING
Protected floorXWall.d, floorYWall.d ;x, y position of the floor textel at the bottom of the wall
;
; //4 different wall directions possible
If side = 0 And rayDirX > 0
floorXWall = mapX
floorYWall = mapY + wallX
ElseIf side = 0 And rayDirX < 0
floorXWall = mapX + 1.0
floorYWall = mapY + wallX
ElseIf side = 1 And rayDirY > 0
floorXWall = mapX + wallX
floorYWall = mapY
Else
floorXWall = mapX + wallX
floorYWall = mapY + 1.0
EndIf
Protected distWall.d, distPlayer.d, currentDist.d
distWall = perpWallDist
distPlayer = 0
If drawEnd < 0 : drawEnd = screenHeight : EndIf
For y = drawEnd + 1 To screenHeight
currentDist = screenHeight / (2 * y - screenHeight)
weight.d = (currentDist - distPlayer) / (distWall - distPlayer)
currentFloorX.d = weight * floorXWall + (1 - weight) * posX
currentFloorY.d = weight * floorYWall + (1 - weight) * posY
floorTexX.l = Int(currentFloorX * texWidth) % texWidth
floorTexY.l = Int(currentFloorY * texHeight) % texHeight
If PixelZ(x, y) = 0
color = Get_Pixel_(gfx(0)\Pointer, gfx(0)\Width, floorTexX, floorTexY)
Set_Pixel_(*screen, screenWidth, x, y, color)
;PixelZ(x, y) = 0.0001 ;set this to a low # so we don't draw the same pixel again.
EndIf
;I am not using ceilings here because of variable wall height, but if you'd like to:
;If PixelZ(x, screenHeight-y) = 0
; color = Get_Pixel_(gfx(0)\Pointer, gfx(0)\Width, floorTexX, floorTexY)
; Set_Pixel_(*screen, screenWidth, x, screenHeight - y, color)
; ;PixelZ(x, screenHeight-y) = 0.0001 ;set this to a low # so we don't draw the same pixel again.
;EndIf
Next
; End of floors casting
EndIf
EndIf
Wend
Next
EndProcedure
; Map data
DataSection
Data.b 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,2,2,2,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1
Data.b 1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,3,0,0,0,3,0,0,0,1
Data.b 1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,2,2,0,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,4,4,4,4,4,4,4,0,0,0,0,0,2,5,1,2,0,0,0,0,0,1
Data.b 1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,0,0,0,0,5,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,0,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
Data.b 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
EndDataSection