Hello!
I just found about Nim some days ago and really like it, so to learn and do something cool with the language I decided to follow this incredible RayCasting tutorial https://lodev.org/cgtutor/raycasting.html
The code there is in C++ so I thought translating the code to Nim would be a good learning process.
But I'm a having a problem and a I think it's related to type differences between C++ and Nim, the problem is: When I rotate (press A or D) the camera plane is not perpendicular anymore to the direction vector, I think the cause of this problem is the way sin() and cos() function works in Nim but I could be wrong.
Example of what is happening:
You can take a look at the code I wrote and the original C++ code here: https://github.com/JustAntoRS/RayCasting-nim
I know this is not a normal question but I'm getting desperate :S Thank you for taking the time to read this :)
Have you done the transfer with c2nim tool? Seems that you did at least manual modifications:
time : uint32 = 0
oldTime : uint32 = 0
time = getTicks()
var diff : uint32 = time - oldTime
var frameTime : float = float(diff) / 1000.0
Time and oldTime is double in cpp code, such changes can make differences...
Nope, didn't use c2nim tool I did it by hand.
Yes, there are some modification like that, because getTicks() return a uint32 value :)
Thanks for the tip on using c2nim, I'm going to use it and see the result
c2nim can translate most basic C and even Cpp code, problems can be C macros and cpp classes, and somtimes it generates a div instead desired / for floating point division. But it save some work and avoids errors.
When you change datatypes, you have to be careful, as float and integer arithmetic is different. Also note that in C int is often 4 byte, but in Nim int is 8 byte on 64 bit systems. For compatibility we have type cint which is the C int type.
I am pretty sure Nim's sin() and cos() work exact same way as the C++ ones. I never had an issue with them working differently. I think error is some place else.
Some thing I noticed:
running your code i get: (61, 29) Error: type mismatch: got <array[0..3, int]> but expected 'array[0..3, int32]'
I switched to using .byte for color values. I switched to using w and h like C++ code.
I can't find the issue in you code, ... but I did my own C++ to nim conversion and it works without issues:
import sdl2
import math
var worldMap =[
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,2,2,2,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1],
[1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,3,0,0,0,3,0,0,0,1],
[1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,2,2,0,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,4,0,0,0,0,5,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,4,0,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
]
proc main() =
var
posX: float = 22 # x and y start position
posY: float = 12
dirX: float = -1 # initial direction vector
dirY: float = 0
planeX: float = 0 # the 2d raycaster version of camera plane
planeY: float = 0.66
time: float = 0 # time of current frame
oldTime: float = 0 # time of previous frame
w = 680
h = 420
window = createWindow("NIM RayCasting", 100,100,cint w,cint h, SDL_WINDOW_SHOWN or SDL_WINDOW_OPENGL)
render = createRenderer(window, -1, Renderer_Accelerated or Renderer_PresentVsync)
evt = sdl2.defaultEvent
runGame = true
while(runGame):
render.setDrawColor(0,0,0,255)
render.clear()
for x in 0 ..< w:
# calculate ray position and direction
var
cameraX = 2 * float(x) / float(w) - 1 # x-coordinate in camera space
rayDirX = dirX + planeX * cameraX
rayDirY = dirY + planeY * cameraX
# which box of the map we're in
mapX: int = int(floor(posX))
mapY: int = int(floor(posY))
# length of ray from current position to next x or y-side
sideDistX: float
sideDistY: float
# length of ray from one x or y-side to next x or y-side
deltaDistX = abs(1 / rayDirX)
deltaDistY = abs(1 / rayDirY)
perpWallDist: float
# what direction to step in x or y-direction (either +1 or -1)
stepX: int
stepY: int
hit = 0 # was there a wall hit?
side: int # was a NS or a EW wall hit?
# calculate step and initial sideDist
if rayDirX < 0:
stepX = -1
sideDistX = (posX - float(mapX)) * deltaDistX
else:
stepX = 1
sideDistX = (float(mapX) + 1.0 - posX) * deltaDistX
if rayDirY < 0:
stepY = -1
sideDistY = (posY - float(mapY)) * deltaDistY
else:
stepY = 1
sideDistY = (float(mapY) + 1.0 - posY) * deltaDistY
# perform DDA
while hit == 0:
# jump to next map square, OR in x-direction, OR in y-direction
if sideDistX < sideDistY:
sideDistX += deltaDistX
mapX += stepX
side = 0
else:
sideDistY += deltaDistY
mapY += stepY
side = 1
# Check if ray has hit a wall
if worldMap[mapX][mapY] > 0:
hit = 1
# Calculate distance projected on camera direction (Euclidean distance will give fisheye effect!)
if side == 0:
perpWallDist = (float(mapX) - posX + (1 - stepX) / 2) / rayDirX
else:
perpWallDist = (float(mapY) - posY + (1 - stepY) / 2) / rayDirY
# Calculate height of line to draw on screen
var lineHeight = (int)(float(h) / perpWallDist)
# calculate lowest and highest pixel to fill in current stripe
var drawStart = -lineHeight div 2 + h div 2
if drawStart < 0:
drawStart = 0
var drawEnd = lineHeight div 2 + h div 2
if drawEnd >= h:
drawEnd = h - 1
# choose wall color
var color: array[4, byte]
case(worldMap[mapX][mapY]):
of 1: color = [245.byte,66,66,255] # Rojo
of 2: color = [66.byte,255,95,255] # Verde
of 3: color = [66.byte,81,245,255] # Azul
of 4: color = [255.byte,255,255,255] # Blanco
else: color = [255.byte,255,255,255] # Naranja
# give x and y sides different brightness
if side == 1:
for i in 0..2:
color[i] = color[i] div 2
# draw the pixels of the stripe as a vertical line
render.setDrawColor(color[0], color[1], color[2], color[3])
render.drawLine(cint(x),cint(drawStart),cint(x),cint(drawEnd))
# timing for input and FPS counter
oldTime = time
time = float getTicks()
var frameTime = (time - oldTime) / 1000.0 # frameTime is the time this frame has taken, in seconds
render.present()
# speed modifiers
var moveSpeed = frameTime * 5.0 # the constant value is in squares/second
var rotSpeed = frameTime * 3.0 # the constant value is in radians/second
while pollEvent(evt):
if evt.kind == QuitEvent:
runGame = false
break
var state = getKeyboardState()
# move forward if no wall in front of you
if state[int SDL_SCANCODE_W] != 0:
if worldMap[int(posX + dirX * moveSpeed)][int(posY)] == 0:
posX += dirX * moveSpeed
if worldMap[int(posX)][int(posY + dirY * moveSpeed)] == 0:
posY += dirY * moveSpeed
# move backwards if no wall behind you
if state[int SDL_SCANCODE_S] != 0:
if worldMap[int(posX - dirX * moveSpeed)][int(posY)] == 0:
posX -= dirX * moveSpeed
if worldMap[int(posX)][int(posY - dirY * moveSpeed)] == 0:
posY -= dirY * moveSpeed
# rotate to the right
if state[int SDL_SCANCODE_D] != 0:
# both camera direction and camera plane must be rotated
var oldDirX = dirX
dirX = dirX * cos(-rotSpeed) - dirY * sin(-rotSpeed)
dirY = oldDirX * sin(-rotSpeed) + dirY * cos(-rotSpeed)
var oldPlaneX = planeX
planeX = planeX * cos(-rotSpeed) - planeY * sin(-rotSpeed)
planeY = oldPlaneX * sin(-rotSpeed) + planeY * cos(-rotSpeed)
# rotate to the left
if state[int SDL_SCANCODE_A] != 0:
# both camera direction and camera plane must be rotated
var oldDirX = dirX
dirX = dirX * cos(rotSpeed) - dirY * sin(rotSpeed)
dirY = oldDirX * sin(rotSpeed) + dirY * cos(rotSpeed)
var oldPlaneX = planeX
planeX = planeX * cos(rotSpeed) - planeY * sin(rotSpeed)
planeY = oldPlaneX * sin(rotSpeed) + planeY * cos(rotSpeed)
main()
Oh I diffed the two files and found the error:
https://dl3.pushbulletusercontent.com/XulFI2T1reCunTVRZEXwGlORRV28pdXi/image.png
You have one important "(" in the wrong place.
Here is your exact code fixed: https://gist.github.com/treeform/bb3f4618d24535590043e264825f350d
I recommend using getKeyboardState() like I did, other wise it moves only on key repeats.
Thank you so much for taking the time to do this, you helped me learn a lot (and solved the problem)
Thanks again :)