package main import ( "log" "math" "unsafe" "github.com/veandco/go-sdl2/sdl" "kisekinopureya.com.tr/go-raycasting/tools" "kisekinopureya.com.tr/go-raycasting/worldMap" ) const ( screenWidth = 640 screenHeight = 480 screenScale = 1 texWidth = 64 texHeight = 64 mapWidth = 24 mapHeight = 24 ) var posX, posY, dirX, dirY, planeX, planeY float64 = 2, 2, -1, 0, 0, 0.66 var renderFps = false func render(renderer *sdl.Renderer, texture *sdl.Texture, pixels []uint32) { err := texture.Update(nil, unsafe.Pointer(&pixels[0]), screenWidth*4) if err != nil { log.Printf("Texture update failed: %v", err) } // Clear renderer, copy texture, present frame renderer.Clear() renderer.Copy(texture, nil, nil) renderer.Present() } func main() { level := worldMap.GetMap() if err := sdl.Init(sdl.INIT_VIDEO); err != nil { log.Fatalf("SDL Initialization failed: %v", err) } defer sdl.Quit() window, err := sdl.CreateWindow("Raycaster", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, screenWidth, screenHeight, sdl.WINDOW_SHOWN) if err != nil { log.Fatalf("Window creation failed: %v", err) } defer window.Destroy() renderer, err := sdl.CreateRenderer(window, -1, sdl.RENDERER_ACCELERATED|sdl.RENDERER_PRESENTVSYNC) if err != nil { log.Fatalf("Renderer creation failed: %v", err) } defer renderer.Destroy() texture, err := renderer.CreateTexture(sdl.PIXELFORMAT_ARGB8888, sdl.TEXTUREACCESS_STREAMING, screenWidth, screenHeight) if err != nil { log.Fatalf("Texture creation failed: %v", err) } defer texture.Destroy() textures, err := worldMap.LoadTextures(renderer) if err != nil { log.Fatalf("Texture creation failed: %v", err) } tools.InitFont("assets/DejaVuSans.ttf", 24) pixels := make([]uint32, screenWidth*screenHeight) running := true for running { for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { switch e := event.(type) { case *sdl.QuitEvent: running = false case *sdl.KeyboardEvent: if e.Type == sdl.KEYDOWN { moveSpeed := 0.1 rotSpeed := 0.05 switch e.Keysym.Sym { case sdl.K_w: if level[int(posX+dirX*moveSpeed)][int(posY)] == 0 { posX += dirX * moveSpeed } if level[int(posX)][int(posY+dirY*moveSpeed)] == 0 { posY += dirY * moveSpeed } case sdl.K_s: if level[int(posX-dirX*moveSpeed)][int(posY)] == 0 { posX -= dirX * moveSpeed } if level[int(posX)][int(posY-dirY*moveSpeed)] == 0 { posY -= dirY * moveSpeed } case sdl.K_d: oldDirX := dirX dirX = dirX*math.Cos(-rotSpeed) - dirY*math.Sin(-rotSpeed) dirY = oldDirX*math.Sin(-rotSpeed) + dirY*math.Cos(-rotSpeed) oldPlaneX := planeX planeX = planeX*math.Cos(-rotSpeed) - planeY*math.Sin(-rotSpeed) planeY = oldPlaneX*math.Sin(-rotSpeed) + planeY*math.Cos(-rotSpeed) case sdl.K_a: oldDirX := dirX dirX = dirX*math.Cos(rotSpeed) - dirY*math.Sin(rotSpeed) dirY = oldDirX*math.Sin(rotSpeed) + dirY*math.Cos(rotSpeed) oldPlaneX := planeX planeX = planeX*math.Cos(rotSpeed) - planeY*math.Sin(rotSpeed) planeY = oldPlaneX*math.Sin(rotSpeed) + planeY*math.Cos(rotSpeed) case sdl.K_F1: if renderFps { renderFps = false } else { renderFps = true } } } } } // Floorcasting logic for y := 0; y < screenHeight; y += screenScale { rayDirX0 := dirX - planeX rayDirY0 := dirY - planeY rayDirX1 := dirX + planeX rayDirY1 := dirY + planeY p := y - screenHeight/2 if p == 0 { continue // avoid division by zero } posZ := 0.5 * float64(screenHeight) rowDistance := posZ / float64(p) floorStepX := rowDistance * (rayDirX1 - rayDirX0) / float64(screenWidth) floorStepY := rowDistance * (rayDirY1 - rayDirY0) / float64(screenWidth) floorX := posX + rowDistance*rayDirX0 floorY := posY + rowDistance*rayDirY0 for x := 0; x < screenWidth; x++ { cellX := int(floorX) cellY := int(floorY) tx := int(float64(texWidth)*(floorX-float64(cellX))) & (texWidth - 1) ty := int(float64(texHeight)*(floorY-float64(cellY))) & (texHeight - 1) floorX += floorStepX floorY += floorStepY floorTexture := 3 ceilingTexture := 6 var color uint32 = textures[floorTexture][texWidth*ty+tx] color = (color >> 1) & 0x7F7F7F // darken pixels[y*screenWidth+x] = color color = textures[ceilingTexture][texWidth*ty+tx] color = (color >> 1) & 0x7F7F7F pixels[(screenHeight-y-1)*screenWidth+x] = color } } // Raycasting logic for x := 0; x < screenWidth; x += screenScale { rayDirX := dirX + planeX*(2*float64(x)/float64(screenWidth)-1) rayDirY := dirY + planeY*(2*float64(x)/float64(screenWidth)-1) mapX, mapY := int(posX), int(posY) deltaDistX := math.Abs(1 / rayDirX) deltaDistY := math.Abs(1 / rayDirY) var sideDistX, sideDistY float64 var stepX, stepY int if rayDirX < 0 { stepX = -1 sideDistX = (posX - float64(mapX)) * deltaDistX } else { stepX = 1 sideDistX = (float64(mapX) + 1.0 - posX) * deltaDistX } if rayDirY < 0 { stepY = -1 sideDistY = (posY - float64(mapY)) * deltaDistY } else { stepY = 1 sideDistY = (float64(mapY) + 1.0 - posY) * deltaDistY } var perpWallDist float64 hit := false var side int for !hit { if sideDistX < sideDistY { mapX += stepX perpWallDist = sideDistX sideDistX += deltaDistX side = 0 } else { mapY += stepY perpWallDist = sideDistY sideDistY += deltaDistY side = 1 } if level[mapX][mapY] > 0 { hit = true } } if hit { lineHeight := int(screenHeight / perpWallDist) drawStart := -lineHeight/2 + screenHeight/2 if drawStart < 0 { drawStart = 0 } drawEnd := lineHeight/2 + screenHeight/2 if drawEnd >= screenHeight { drawEnd = screenHeight - 1 } wallType := level[mapX][mapY] - 1 var wallX float64 //where exactly the wall was hit if side == 0 { wallX = posY + perpWallDist*rayDirY } else { wallX = posX + perpWallDist*rayDirX } wallX -= math.Floor(wallX) //x coordinate on the texture var texX int = int(wallX * float64(texWidth)) if side == 0 && rayDirX > 0 { texX = texWidth - texX - 1 } if side == 1 && rayDirY < 0 { texX = texWidth - texX - 1 } var step float64 = 1.0 * float64(texHeight) / float64(lineHeight) // Starting texture coordinate var texPos float64 = float64(drawStart-screenHeight/2+lineHeight/2) * step // Draw vertical line for y := drawStart; y < drawEnd; y++ { var texY int = int(texPos) & (texHeight - 1) texPos += step var color uint32 = textures[wallType][texHeight*texY+texX] if side == 1 { color = (color >> 1) & 8355711 } pixels[y*screenWidth+x] = color } } } if renderFps { tools.FpsCounter(pixels, screenWidth, screenHeight) } // Render updated scene render(renderer, texture, pixels) } }