pixel-examples/community/raycaster/raycaster.go

551 lines
73 KiB
Go
Raw Normal View History

package main
import (
"bytes"
"flag"
"image"
"image/color"
"image/draw"
"image/png"
"math"
"time"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
)
const texSize = 64
var (
fullscreen = false
showMap = true
width = 320
height = 200
scale = 3.0
wallDistance = 8.0
as actionSquare
pos, dir, plane pixel.Vec
textures = loadTextures()
)
func setup() {
pos = pixel.V(12.0, 14.5)
dir = pixel.V(-1.0, 0.0)
plane = pixel.V(0.0, 0.66)
}
var world = [24][24]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, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 3, 0, 3, 0, 3, 0, 0, 0, 1},
{1, 0, 0, 0, 2, 7, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 2, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 7, 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, 2, 2, 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, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 6, 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, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 6, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 5, 0, 0, 0, 1},
{1, 0, 6, 0, 4, 0, 7, 0, 4, 0, 0, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 1},
{1, 4, 4, 4, 4, 4, 4, 0, 4, 0, 0, 0, 5, 5, 0, 5, 5, 5, 0, 5, 5, 0, 0, 1},
{1, 4, 0, 0, 0, 0, 0, 0, 4, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 1},
{1, 4, 0, 4, 0, 0, 0, 0, 4, 0, 0, 5, 0, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 1},
{1, 4, 0, 4, 4, 4, 4, 4, 4, 0, 0, 5, 0, 5, 0, 0, 0, 0, 0, 5, 0, 5, 0, 1},
{1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 0, 5, 5, 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 loadTextures() *image.RGBA {
p, err := png.Decode(bytes.NewReader(textureData))
if err != nil {
panic(err)
}
m := image.NewRGBA(p.Bounds())
draw.Draw(m, m.Bounds(), p, image.ZP, draw.Src)
return m
}
func getTexNum(x, y int) int {
return world[x][y]
}
func getColor(x, y int) color.RGBA {
switch world[x][y] {
case 0:
return color.RGBA{43, 30, 24, 255}
case 1:
return color.RGBA{100, 89, 73, 255}
case 2:
return color.RGBA{110, 23, 0, 255}
case 3:
return color.RGBA{45, 103, 171, 255}
case 4:
return color.RGBA{123, 84, 33, 255}
case 5:
return color.RGBA{158, 148, 130, 255}
case 6:
return color.RGBA{203, 161, 47, 255}
case 7:
return color.RGBA{255, 107, 0, 255}
case 9:
return color.RGBA{0, 0, 0, 0}
default:
return color.RGBA{255, 194, 32, 255}
}
}
func frame() *image.RGBA {
m := image.NewRGBA(image.Rect(0, 0, width, height))
for x := 0; x < width; x++ {
var (
step image.Point
sideDist pixel.Vec
perpWallDist float64
hit, side bool
rayPos, worldX, worldY = pos, int(pos.X), int(pos.Y)
cameraX = 2*float64(x)/float64(width) - 1
rayDir = pixel.V(
dir.X+plane.X*cameraX,
dir.Y+plane.Y*cameraX,
)
deltaDist = pixel.V(
math.Sqrt(1.0+(rayDir.Y*rayDir.Y)/(rayDir.X*rayDir.X)),
math.Sqrt(1.0+(rayDir.X*rayDir.X)/(rayDir.Y*rayDir.Y)),
)
)
if rayDir.X < 0 {
step.X = -1
sideDist.X = (rayPos.X - float64(worldX)) * deltaDist.X
} else {
step.X = 1
sideDist.X = (float64(worldX) + 1.0 - rayPos.X) * deltaDist.X
}
if rayDir.Y < 0 {
step.Y = -1
sideDist.Y = (rayPos.Y - float64(worldY)) * deltaDist.Y
} else {
step.Y = 1
sideDist.Y = (float64(worldY) + 1.0 - rayPos.Y) * deltaDist.Y
}
for !hit {
if sideDist.X < sideDist.Y {
sideDist.X += deltaDist.X
worldX += step.X
side = false
} else {
sideDist.Y += deltaDist.Y
worldY += step.Y
side = true
}
if world[worldX][worldY] > 0 {
hit = true
}
}
var wallX float64
if side {
perpWallDist = (float64(worldY) - rayPos.Y + (1-float64(step.Y))/2) / rayDir.Y
wallX = rayPos.X + perpWallDist*rayDir.X
} else {
perpWallDist = (float64(worldX) - rayPos.X + (1-float64(step.X))/2) / rayDir.X
wallX = rayPos.Y + perpWallDist*rayDir.Y
}
if x == width/2 {
wallDistance = perpWallDist
}
wallX -= math.Floor(wallX)
texX := int(wallX * float64(texSize))
lineHeight := int(float64(height) / perpWallDist)
if lineHeight < 1 {
lineHeight = 1
}
drawStart := -lineHeight/2 + height/2
if drawStart < 0 {
drawStart = 0
}
drawEnd := lineHeight/2 + height/2
if drawEnd >= height {
drawEnd = height - 1
}
if !side && rayDir.X > 0 {
texX = texSize - texX - 1
}
if side && rayDir.Y < 0 {
texX = texSize - texX - 1
}
texNum := getTexNum(worldX, worldY)
for y := drawStart; y < drawEnd+1; y++ {
d := y*256 - height*128 + lineHeight*128
texY := ((d * texSize) / lineHeight) / 256
c := textures.RGBAAt(
texX+texSize*(texNum),
texY%texSize,
)
if side {
c.R = c.R / 2
c.G = c.G / 2
c.B = c.B / 2
}
m.Set(x, y, c)
}
var floorWall pixel.Vec
if !side && rayDir.X > 0 {
floorWall.X = float64(worldX)
floorWall.Y = float64(worldY) + wallX
} else if !side && rayDir.X < 0 {
floorWall.X = float64(worldX) + 1.0
floorWall.Y = float64(worldY) + wallX
} else if side && rayDir.Y > 0 {
floorWall.X = float64(worldX) + wallX
floorWall.Y = float64(worldY)
} else {
floorWall.X = float64(worldX) + wallX
floorWall.Y = float64(worldY) + 1.0
}
distWall, distPlayer := perpWallDist, 0.0
for y := drawEnd + 1; y < height; y++ {
currentDist := float64(height) / (2.0*float64(y) - float64(height))
weight := (currentDist - distPlayer) / (distWall - distPlayer)
currentFloor := pixel.V(
weight*floorWall.X+(1.0-weight)*pos.X,
weight*floorWall.Y+(1.0-weight)*pos.Y,
)
fx := int(currentFloor.X*float64(texSize)) % texSize
fy := int(currentFloor.Y*float64(texSize)) % texSize
m.Set(x, y, textures.At(fx, fy))
m.Set(x, height-y-1, textures.At(fx+(4*texSize), fy))
m.Set(x, height-y, textures.At(fx+(4*texSize), fy))
}
}
return m
}
func minimap() *image.RGBA {
m := image.NewRGBA(image.Rect(0, 0, 24, 26))
for x, row := range world {
for y, _ := range row {
c := getColor(x, y)
if c.A == 255 {
c.A = 96
}
m.Set(x, y, c)
}
}
m.Set(int(pos.X), int(pos.Y), color.RGBA{255, 0, 0, 255})
if as.active {
m.Set(as.X, as.Y, color.RGBA{255, 255, 255, 255})
} else {
m.Set(as.X, as.Y, color.RGBA{64, 64, 64, 255})
}
return m
}
func getActionSquare() actionSquare {
pt := image.Pt(int(pos.X)+1, int(pos.Y))
a := dir.Angle()
switch {
case a > 2.8 || a < -2.8:
pt = image.Pt(int(pos.X)-1, int(pos.Y))
case a > -2.8 && a < -2.2:
pt = image.Pt(int(pos.X)-1, int(pos.Y)-1)
case a > -2.2 && a < -1.4:
pt = image.Pt(int(pos.X), int(pos.Y)-1)
case a > -1.4 && a < -0.7:
pt = image.Pt(int(pos.X)+1, int(pos.Y)-1)
case a > 0.4 && a < 1.0:
pt = image.Pt(int(pos.X)+1, int(pos.Y)+1)
case a > 1.0 && a < 1.7:
pt = image.Pt(int(pos.X), int(pos.Y)+1)
case a > 1.7:
pt = image.Pt(int(pos.X)-1, int(pos.Y)+1)
}
block := -1
active := pt.X > 0 && pt.X < 23 && pt.Y > 0 && pt.Y < 23
if active {
block = world[pt.X][pt.Y]
}
return actionSquare{
X: pt.X,
Y: pt.Y,
active: active,
block: block,
}
}
type actionSquare struct {
X int
Y int
block int
active bool
}
func (as actionSquare) toggle(n int) {
if as.active {
if world[as.X][as.Y] == 0 {
world[as.X][as.Y] = n
} else {
world[as.X][as.Y] = 0
}
}
}
func (as actionSquare) set(n int) {
if as.active {
world[as.X][as.Y] = n
}
}
func run() {
cfg := pixelgl.WindowConfig{
Bounds: pixel.R(0, 0, float64(width)*scale, float64(height)*scale),
VSync: true,
Undecorated: true,
}
if fullscreen {
cfg.Monitor = pixelgl.PrimaryMonitor()
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
c := win.Bounds().Center()
last := time.Now()
mapRot := -1.6683362599999894
for !win.Closed() {
if win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ) {
return
}
win.Clear(color.Black)
dt := time.Since(last).Seconds()
last = time.Now()
as = getActionSquare()
if win.Pressed(pixelgl.KeyUp) || win.Pressed(pixelgl.KeyW) {
moveForward(3.5 * dt)
}
if win.Pressed(pixelgl.KeyA) {
moveLeft(3.5 * dt)
}
if win.Pressed(pixelgl.KeyDown) || win.Pressed(pixelgl.KeyS) {
moveBackwards(3.5 * dt)
}
if win.Pressed(pixelgl.KeyD) {
moveRight(3.5 * dt)
}
if win.Pressed(pixelgl.KeyRight) {
turnRight(1.2 * dt)
}
if win.Pressed(pixelgl.KeyLeft) {
turnLeft(1.2 * dt)
}
if win.JustPressed(pixelgl.KeyM) {
showMap = !showMap
}
if win.JustPressed(pixelgl.Key1) {
as.set(1)
}
if win.JustPressed(pixelgl.Key2) {
as.set(2)
}
if win.JustPressed(pixelgl.Key3) {
as.set(3)
}
if win.JustPressed(pixelgl.Key4) {
as.set(4)
}
if win.JustPressed(pixelgl.Key5) {
as.set(5)
}
if win.JustPressed(pixelgl.Key6) {
as.set(6)
}
if win.JustPressed(pixelgl.Key7) {
as.set(7)
}
if win.JustPressed(pixelgl.Key0) {
as.set(0)
}
if win.JustPressed(pixelgl.KeySpace) {
as.toggle(3)
}
p := pixel.PictureDataFromImage(frame())
pixel.NewSprite(p, p.Bounds()).
Draw(win, pixel.IM.Moved(c).Scaled(c, scale))
if showMap {
m := pixel.PictureDataFromImage(minimap())
mc := m.Bounds().Min.Add(pixel.V(-m.Rect.W(), m.Rect.H()))
pixel.NewSprite(m, m.Bounds()).
Draw(win, pixel.IM.
Moved(mc).
Rotated(mc, mapRot).
ScaledXY(pixel.ZV, pixel.V(-scale*2, scale*2)))
}
win.Update()
}
}
func moveForward(s float64) {
if wallDistance > 0.3 {
if world[int(pos.X+dir.X*s)][int(pos.Y)] == 0 {
pos.X += dir.X * s
}
if world[int(pos.X)][int(pos.Y+dir.Y*s)] == 0 {
pos.Y += dir.Y * s
}
}
}
func moveLeft(s float64) {
if world[int(pos.X-plane.X*s)][int(pos.Y)] == 0 {
pos.X -= plane.X * s
}
if world[int(pos.X)][int(pos.Y-plane.Y*s)] == 0 {
pos.Y -= plane.Y * s
}
}
func moveBackwards(s float64) {
if world[int(pos.X-dir.X*s)][int(pos.Y)] == 0 {
pos.X -= dir.X * s
}
if world[int(pos.X)][int(pos.Y-dir.Y*s)] == 0 {
pos.Y -= dir.Y * s
}
}
func moveRight(s float64) {
if world[int(pos.X+plane.X*s)][int(pos.Y)] == 0 {
pos.X += plane.X * s
}
if world[int(pos.X)][int(pos.Y+plane.Y*s)] == 0 {
pos.Y += plane.Y * s
}
}
func turnRight(s float64) {
oldDirX := dir.X
dir.X = dir.X*math.Cos(-s) - dir.Y*math.Sin(-s)
dir.Y = oldDirX*math.Sin(-s) + dir.Y*math.Cos(-s)
oldPlaneX := plane.X
plane.X = plane.X*math.Cos(-s) - plane.Y*math.Sin(-s)
plane.Y = oldPlaneX*math.Sin(-s) + plane.Y*math.Cos(-s)
}
func turnLeft(s float64) {
oldDirX := dir.X
dir.X = dir.X*math.Cos(s) - dir.Y*math.Sin(s)
dir.Y = oldDirX*math.Sin(s) + dir.Y*math.Cos(s)
oldPlaneX := plane.X
plane.X = plane.X*math.Cos(s) - plane.Y*math.Sin(s)
plane.Y = oldPlaneX*math.Sin(s) + plane.Y*math.Cos(s)
}
func main() {
flag.BoolVar(&fullscreen, "f", fullscreen, "fullscreen")
flag.IntVar(&width, "w", width, "width")
flag.IntVar(&height, "h", height, "height")
flag.Float64Var(&scale, "s", scale, "scale")
flag.Parse()
setup()
pixelgl.Run(run)
}
var textureData = []byte{137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 2, 0, 0, 0, 0, 64, 8, 3, 0, 0, 0, 91, 97, 63, 141, 0, 0, 2, 100, 80, 76, 84, 69, 182, 182, 182, 48, 38, 29, 33, 23, 15, 191, 146, 37, 7, 3, 1, 72, 64, 49, 40, 28, 20, 37, 26, 19, 42, 31, 22, 110, 100, 84, 64, 54, 40, 55, 46, 32, 142, 131, 114, 74, 40, 10, 22, 14, 7, 97, 86, 69, 66, 35, 6, 99, 60, 20, 102, 91, 75, 16, 9, 3, 120, 27, 1, 27, 18, 11, 184, 140, 33, 83, 72, 57, 131, 119, 103, 114, 73, 27, 128, 87, 34, 48, 23, 2, 159, 149, 134, 185, 185, 185, 6, 37, 82, 68, 59, 45, 130, 188, 209, 111, 72, 8, 129, 32, 4, 134, 93, 38, 150, 139, 123, 46, 33, 27, 80, 68, 54, 199, 156, 43, 87, 75, 61, 43, 43, 43, 123, 111, 95, 60, 50, 38, 92, 55, 17, 119, 79, 31, 170, 159, 142, 82, 46, 14, 170, 125, 23, 146, 135, 118, 164, 119, 20, 189, 189, 189, 31, 80, 138, 115, 104, 89, 203, 162, 47, 1, 0, 0, 127, 115, 99, 87, 49, 15, 193, 193, 193, 1, 27, 64, 40, 96, 165, 83, 83, 83, 54, 42, 36, 42, 100, 171, 154, 143, 128, 118, 108, 92, 57, 57, 56, 109, 69, 25, 137, 127, 108, 206, 167, 50, 89, 80, 63, 224, 211, 196, 0, 9, 27, 255, 255, 255, 79, 44, 1, 3, 31, 71, 92, 82, 66, 71, 71, 71, 22, 61, 110, 19, 54, 100, 198, 198, 198, 189, 176, 161, 1, 22, 57, 117, 78, 9, 93, 19, 0, 29, 76, 132, 99, 22, 1, 111, 26, 2, 35, 87, 150, 27, 71, 126, 78, 69, 57, 127, 127, 127, 160, 114, 18, 0, 5, 16, 1, 18, 49, 0, 12, 38, 134, 124, 105, 178, 133, 28, 34, 33, 33, 105, 25, 2, 139, 123, 109, 135, 38, 7, 27, 67, 116, 105, 66, 24, 37, 91, 157, 72, 58, 46, 174, 129, 26, 59, 30, 5, 33, 83, 144, 132, 125, 112, 124, 83, 32, 148, 130, 114, 45, 104, 178, 78, 62, 51, 195, 152, 40, 23, 54, 90, 153, 137, 123, 81, 20, 2, 38, 15, 1, 184, 173, 157, 175, 163, 148, 9, 44, 95, 172, 119, 47, 28, 63, 102, 210, 171, 54, 95, 56, 3, 94, 84, 72, 83, 73, 66, 207, 195, 180, 201, 189, 173, 179, 168, 152, 28, 10, 0, 155, 155, 155, 97, 87, 80, 139, 100, 42, 152, 55, 18, 144, 46, 11, 164, 155, 138, 123, 123, 123, 55, 27, 4, 203, 203, 203, 214, 203, 187, 87, 86, 86, 164, 112, 46, 155, 103, 39, 50, 116, 197, 106, 106, 106, 57, 133, 225, 44, 107, 183, 52, 121, 205, 86, 49, 3, 54, 126, 214, 147, 103, 16, 147, 95, 36, 68, 16, 1, 140, 98, 16, 153, 109, 18, 145, 102, 45, 169, 150, 136, 194, 181, 166, 48, 112, 190, 169, 73, 36, 84, 7, 0, 19, 47, 78, 169, 120, 61, 114, 114, 114, 131, 91, 14, 123, 84, 13, 169, 170, 170, 64, 63, 62, 137, 137, 137, 100, 64, 7, 56, 11, 0, 100, 100, 100, 209, 209, 209, 178, 130, 76, 50, 50, 50, 43, 87, 171, 95, 95, 95, 9, 51, 110, 87, 79, 70, 110, 159, 225, 105, 146, 167, 15, 39, 62, 13, 31, 46, 174, 175, 175, 213, 144, 111, 95, 143, 212, 189, 90, 48, 164, 164, 163, 66, 148, 245, 70, 114, 191, 116, 160, 179, 141, 143, 144, 220, 155, 122, 222, 224, 225, 154, 198, 214, 60, 139, 234, 125, 53, 24, 237, 242, 244, 140, 175, 189, 188, 217, 227, 191, 150, 127, 218, 172, 148, 46, 0, 54, 145, 0, 0, 51, 110, 73, 68, 65, 84, 120, 218, 164, 89, 251, 83, 27, 85, 20, 206, 46, 44, 201, 238, 38, 89, 8, 132, 141, 36, 146, 9, 133, 52, 36, 72, 0, 27, 11, 104, 90, 94, 166, 140, 6, 104, 10, 82, 26, 106, 11, 229, 209, 2, 125, 88, 30, 133, 14, 211, 240, 40, 200, 163, 128, 14, 227, 116, 176, 160, 72, 145, 74, 45, 48, 90, 113, 208, 81, 199, 225, 23, 255, 46, 191, 187, 187, 233, 110, 124, 160, 213, 143, 189, 143, 239, 158, 123, 218, 41, 223, 185, 231, 62, 170, 243, 165, 228, 229, 81, 255, 21, 167, 242, 104, 49, 192, 222, 200, 189, 113, 195, 27, 53, 68, 121, 171, 143, 97, 246, 99, 99, 226, 24, 195, 39, 235, 157, 252, 254, 244, 244, 62, 195, 241, 62, 234, 20, 179, 63, 54, 54, 166, 181, 171, 94, 86, 43, 173, 167, 173, 86, 243, 149, 83, 85, 81, 96, 63, 202, 36, 96, 76, 237, 70, 85, 251, 55, 249, 87, 136, 159, 222, 106, 77, 206, 126, 73, 188, 171, 224, 174, 140, 236, 251, 5, 5, 5, 254, 50, 2, 211, 253, 234, 172, 99, 159, 2, 31, 43, 64, 55, 1, 202, 208, 177, 172, 106, 141, 143, 115, 194, 49, 60, 62, 49, 190, 120, 102, 113, 124, 124, 98, 113, 157, 61, 179, 56, 49, 49, 62, 129, 114, 102, 241, 204, 153, 51, 109, 158, 197, 197, 241, 137, 245, 8, 161, 139, 139, 145, 145, 54, 46, 178, 56, 62, 57, 82, 56, 17, 89, 95, 9