initial commit
BIN
assets/barrel.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/bluestone.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
assets/colorstone.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
assets/eagle.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
assets/greenlight.png
Normal file
|
After Width: | Height: | Size: 393 B |
BIN
assets/greystone.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
assets/mossy.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
assets/pillar.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/purplestone.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
assets/redbrick.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
assets/wood.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module kisekinopureya.com.tr/go-raycasting
|
||||
|
||||
go 1.24.1
|
||||
|
||||
require github.com/veandco/go-sdl2 v0.4.40
|
||||
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
||||
github.com/veandco/go-sdl2 v0.4.40 h1:fZv6wC3zz1Xt167P09gazawnpa0KY5LM7JAvKpX9d/U=
|
||||
github.com/veandco/go-sdl2 v0.4.40/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
|
||||
210
main.go
Normal file
@ -0,0 +1,210 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
"unsafe"
|
||||
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
"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
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := range pixels {
|
||||
pixels[i] = 0x00000000 // Set background to black
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
// Render updated scene
|
||||
render(renderer, texture, pixels)
|
||||
}
|
||||
}
|
||||
113
worldMap/worldMap.go
Normal file
@ -0,0 +1,113 @@
|
||||
package worldMap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"os"
|
||||
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
const (
|
||||
mapWidth = 24
|
||||
mapHeight = 24
|
||||
texWidth = 64
|
||||
texHeight = 64
|
||||
)
|
||||
|
||||
var textures [8][]uint32
|
||||
|
||||
var level = [mapWidth][mapHeight]int{
|
||||
{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, 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},
|
||||
}
|
||||
|
||||
func loadImage(out *[]uint32, w uint32, h uint32, filename string) error {
|
||||
// Read the file content
|
||||
file, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading file:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode the PNG file
|
||||
img, err := png.Decode(bytes.NewReader(file))
|
||||
fmt.Println(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error decoding PNG:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the width and height
|
||||
w = uint32(img.Bounds().Dx())
|
||||
h = uint32(img.Bounds().Dy())
|
||||
|
||||
// Resize the output slice to hold the pixel data
|
||||
*out = make([]uint32, w*h)
|
||||
|
||||
// Convert the image to RGBA and extract pixel data
|
||||
rgbaImg, ok := img.(*image.RGBA)
|
||||
if !ok {
|
||||
fmt.Println("Image is not in RGBA format")
|
||||
return err
|
||||
}
|
||||
|
||||
// Process each pixel and store it in the output slice
|
||||
for i := range *out {
|
||||
r := rgbaImg.Pix[i*4+0]
|
||||
g := rgbaImg.Pix[i*4+1]
|
||||
b := rgbaImg.Pix[i*4+2]
|
||||
a := rgbaImg.Pix[i*4+3]
|
||||
|
||||
// Pack the ARGB values into a uint32
|
||||
(*out)[i] = uint32(a)<<24 | uint32(r)<<16 | uint32(g)<<8 | uint32(b)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetMap() [24][24]int {
|
||||
return level
|
||||
}
|
||||
|
||||
func LoadTextures(renderer *sdl.Renderer) ([][]uint32, error) {
|
||||
textureFiles := []string{
|
||||
"assets/eagle.png", "assets/redbrick.png", "assets/purplestone.png", "assets/greystone.png",
|
||||
"assets/bluestone.png", "assets/mossy.png", "assets/wood.png", "assets/colorstone.png",
|
||||
}
|
||||
|
||||
textures := make([][]uint32, 8)
|
||||
|
||||
for i, file := range textureFiles {
|
||||
var out []uint32
|
||||
if err := loadImage(&out, uint32(64), 64, file); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
textures[i] = out
|
||||
}
|
||||
|
||||
return textures, nil
|
||||
}
|
||||