backrooms!!!!!

This commit is contained in:
Sasha Koshka 2023-02-21 16:48:56 -05:00
parent e966771f5b
commit 20fa445cdd
7 changed files with 144 additions and 57 deletions

View File

@ -44,7 +44,7 @@ func run () {
warningButton := basicElements.NewButton("popups.DialogKindWarning")
warningButton.OnClick (func () {
popups.NewDialog (
popups.DialogKindQuestion,
popups.DialogKindWarning,
"Warning",
"They are fast approaching.")
})
@ -53,7 +53,7 @@ func run () {
errorButton := basicElements.NewButton("popups.DialogKindError")
errorButton.OnClick (func () {
popups.NewDialog (
popups.DialogKindQuestion,
popups.DialogKindError,
"Error",
"There is nowhere left to go.")
})

View File

@ -13,9 +13,9 @@ type Game struct {
controlState ControlState
}
func NewGame (world World) (game *Game) {
func NewGame (world World, textures Textures) (game *Game) {
game = &Game {
Raycaster: NewRaycaster(world),
Raycaster: NewRaycaster(world, textures),
stopChan: make(chan bool),
}
game.Raycaster.OnControlStateChange (func (state ControlState) {

View File

@ -1,10 +1,16 @@
package main
import "bytes"
import _ "embed"
import _ "image/png"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
//go:embed wall.png
var wallTextureBytes []uint8
func main () {
tomo.Run(run)
}
@ -16,20 +22,28 @@ func run () {
container := basicElements.NewContainer(basicLayouts.Vertical { true, true })
window.Adopt(container)
game := NewGame (DefaultWorld {
wallTexture, _ := TextureFrom(bytes.NewReader(wallTextureBytes))
game := NewGame (World {
Data: []int {
1,1,1,1,1,1,1,1,1,1,
1,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,1,
1,0,0,1,1,0,1,0,0,1,
1,0,0,1,0,0,1,0,0,1,
1,0,0,1,0,0,1,0,0,1,
1,0,0,1,0,1,1,0,0,1,
1,0,0,0,0,0,0,0,0,1,
1,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,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,1,1,1,1,1,1,1,0,0,0,1,
1,0,0,0,0,0,0,0,1,1,1,0,1,
1,0,0,0,0,0,0,0,1,0,0,0,1,
1,0,0,0,0,0,0,0,1,0,1,1,1,
1,1,1,1,1,1,1,1,1,0,0,0,1,
1,0,0,0,0,0,0,0,1,1,0,1,1,
1,0,0,1,0,0,0,0,0,0,0,0,1,
1,0,1,1,1,0,0,0,0,0,0,0,1,
1,0,0,1,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,0,0,0,0,0,0,0,1,
1,0,0,0,0,1,0,0,0,0,0,0,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,
},
Stride: 10,
Stride: 13,
}, Textures {
wallTexture,
})
container.Adopt(basicElements.NewLabel("Explore a 3D world!", false), false)

View File

@ -3,16 +3,12 @@ package main
import "math"
import "image"
type World interface {
At (image.Point) int
}
type DefaultWorld struct {
type World struct {
Data []int
Stride int
}
func (world DefaultWorld) At (position image.Point) int {
func (world World) At (position image.Point) int {
if position.X < 0 { return 0 }
if position.Y < 0 { return 0 }
if position.X >= world.Stride { return 0 }
@ -98,21 +94,30 @@ type Ray struct {
Angle float64
}
func (ray *Ray) Cast (world World, max int) (distance float64, hit Vector) {
func (ray *Ray) Cast (
world World,
max int,
) (
distance float64,
hit Vector,
wall int,
horizontal bool,
) {
// return ray.castV(world, max)
if world.At(ray.Point()) > 0 {
return 0, Vector { }
cellAt := world.At(ray.Point())
if cellAt > 0 {
return 0, Vector { }, cellAt, false
}
hDistance, hPos := ray.castH(world, max)
vDistance, vPos := ray.castV(world, max)
hDistance, hPos, hWall := ray.castH(world, max)
vDistance, vPos, vWall := ray.castV(world, max)
if hDistance < vDistance {
return hDistance, hPos
return hDistance, hPos, hWall, true
} else {
return vDistance, vPos
return vDistance, vPos, vWall, false
}
}
func (ray *Ray) castH (world World, max int) (distance float64, hit Vector) {
func (ray *Ray) castH (world World, max int) (distance float64, hit Vector, wall int) {
var position Vector
var delta Vector
ray.Angle = math.Mod(ray.Angle, math.Pi * 2)
@ -130,7 +135,7 @@ func (ray *Ray) castH (world World, max int) (distance float64, hit Vector) {
delta.Y = 1
} else {
// facing straight left or right
return float64(max), Vector { }
return float64(max), Vector { }, 0
}
position.X = ray.X + (ray.Y - position.Y) / tan
delta.X = -delta.Y / tan
@ -144,10 +149,10 @@ func (ray *Ray) castH (world World, max int) (distance float64, hit Vector) {
steps ++
}
return position.Sub(ray.Vector).Hypot(), position
return position.Sub(ray.Vector).Hypot(), position, world.At(position.Point())
}
func (ray *Ray) castV (world World, max int) (distance float64, hit Vector) {
func (ray *Ray) castV (world World, max int) (distance float64, hit Vector, wall int) {
var position Vector
var delta Vector
tan := math.Tan(math.Pi - ray.Angle)
@ -162,7 +167,7 @@ func (ray *Ray) castV (world World, max int) (distance float64, hit Vector) {
delta.X = 1
} else {
// facing straight left or right
return float64(max), Vector { }
return float64(max), Vector { }, 0
}
position.Y = ray.Y + (ray.X - position.X) * tan
delta.Y = -delta.X * tan
@ -176,6 +181,5 @@ func (ray *Ray) castV (world World, max int) (distance float64, hit Vector) {
steps ++
}
return position.Sub(ray.Vector).Hypot(), position
return
return position.Sub(ray.Vector).Hypot(), position, world.At(position.Point())
}

View File

@ -28,20 +28,24 @@ type Raycaster struct {
Camera
controlState ControlState
world World
textures Textures
onControlStateChange func (ControlState)
renderDistance int
}
func NewRaycaster (world World) (element *Raycaster) {
func NewRaycaster (world World, textures Textures) (element *Raycaster) {
element = &Raycaster {
Camera: Camera {
Vector: Vector {
X: 1.5,
Y: 1.5,
X: 1,
Y: 1,
},
Angle: math.Pi / 3,
Fov: 1,
},
world: world,
textures: textures,
renderDistance: 8,
}
element.Core, element.core = core.NewCore(element.drawAll)
element.FocusableCore,
@ -113,22 +117,26 @@ func (element *Raycaster) drawAll () {
ray.X = element.Camera.X
ray.Y = element.Camera.Y
distance, _ := ray.Cast(element.world, 8)
distanceFac := float64(distance) / 8
distance *= math.Cos(ray.Angle - element.Camera.Angle)
distance, hitPoint, wall, horizontal := ray.Cast (
element.world, element.renderDistance)
distance *= math.Cos(ray.Angle - element.Camera.Angle)
textureX := math.Mod(hitPoint.X + hitPoint.Y, 1)
if textureX < 0 { textureX += 1 }
wallHeight := height
if distance > 0 {
wallHeight = int((float64(height) / 2.0) / float64(distance))
}
shade := 1.0
if horizontal {
shade *= 0.7
}
shade *= 1 - distance / float64(element.renderDistance)
if shade < 0 { shade = 0 }
ceilingColor := color.RGBA { 0x00, 0x00, 0x00, 0xFF }
wallColor := color.RGBA { 0xCC, 0x33, 0x22, 0xFF }
floorColor := color.RGBA { 0x11, 0x50, 0x22, 0xFF }
// fmt.Println(float64(distance) / 32)
wallColor = artist.LerpRGBA(wallColor, ceilingColor, distanceFac)
floorColor := color.RGBA { 0x39, 0x49, 0x25, 0xFF }
// draw
data, stride := element.core.Buffer()
@ -136,16 +144,26 @@ func (element *Raycaster) drawAll () {
wallEnd := height / 2 + wallHeight + bounds.Min.Y
if wallStart < 0 { wallStart = 0 }
if wallEnd > bounds.Max.Y { wallEnd = bounds.Max.Y }
for y := bounds.Min.Y; y < wallStart; y ++ {
data[y * stride + x + bounds.Min.X] = ceilingColor
}
slicePoint := 0.0
slicePointDelta := 1 / float64(wallEnd - wallStart)
for y := wallStart; y < wallEnd; y ++ {
wallColor := element.textures.At (wall, Vector {
textureX,
slicePoint,
})
wallColor = shadeColor(wallColor, shade)
data[y * stride + x + bounds.Min.X] = wallColor
slicePoint += slicePointDelta
}
for y := wallEnd; y < bounds.Max.Y; y ++ {
floorFac := float64(y - (height / 2)) / float64(height / 2)
data[y * stride + x + bounds.Min.X] =
artist.LerpRGBA(ceilingColor, floorColor, floorFac)
data[y * stride + x + bounds.Min.X] = floorColor
}
// increment angle
@ -155,11 +173,20 @@ func (element *Raycaster) drawAll () {
// element.drawMinimap()
}
func shadeColor (c color.RGBA, brightness float64) color.RGBA {
return color.RGBA {
uint8(float64(c.R) * brightness),
uint8(float64(c.G) * brightness),
uint8(float64(c.B) * brightness),
c.A,
}
}
func (element *Raycaster) drawMinimap () {
bounds := element.Bounds()
scale := 16
for y := 0; y < 10; y ++ {
for x := 0; x < 10; x ++ {
scale := 8
for y := 0; y < len(element.world.Data) / element.world.Stride; y ++ {
for x := 0; x < element.world.Stride; x ++ {
cellPt := image.Pt(x, y)
cell := element.world.At(cellPt)
cellBounds :=
@ -168,7 +195,7 @@ func (element *Raycaster) drawMinimap () {
cellPt.Add(image.Pt(1, 1)).Mul(scale),
}.Add(bounds.Min)
cellColor := color.RGBA { 0x22, 0x22, 0x22, 0xFF }
if cell == 1 {
if cell > 0 {
cellColor = color.RGBA { 0xFF, 0xFF, 0xFF, 0xFF }
}
artist.FillRectangle (
@ -182,9 +209,8 @@ func (element *Raycaster) drawMinimap () {
element.Camera.Add(element.Camera.Delta()).
Mul(float64(scale)).Point().Add(bounds.Min)
ray := Ray { Vector: element.Camera.Vector, Angle: element.Camera.Angle }
_, hit := ray.Cast(element.world, 8)
_, hit, _, _ := ray.Cast(element.world, 8)
hitPt := hit.Mul(float64(scale)).Point().Add(bounds.Min)
// fmt.Println(rayDistance)
playerBounds := image.Rectangle { playerPt, playerPt }.Inset(scale / -8)
artist.FillEllipse (

View File

@ -0,0 +1,43 @@
package main
import "io"
import "image"
import "image/color"
type Textures []Texture
type Texture struct {
Data []color.RGBA
Stride int
}
func (texture Textures) At (wall int, offset Vector) color.RGBA {
wall --
if wall < 0 || wall >= len(texture) { return color.RGBA { } }
image := texture[wall]
xOffset := int(offset.X * float64(image.Stride))
yOffset := int(offset.Y * float64(len(image.Data) / image.Stride))
return image.Data[xOffset + yOffset * image.Stride]
}
func TextureFrom (source io.Reader) (texture Texture, err error) {
sourceImage, _, err := image.Decode(source)
if err != nil { return }
bounds := sourceImage.Bounds()
texture.Stride = bounds.Dx()
texture.Data = make([]color.RGBA, bounds.Dx() * bounds.Dy())
index := 0
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
r, g, b, a := sourceImage.At(x, y).RGBA()
texture.Data[index] = color.RGBA {
R: uint8(r >> 8),
G: uint8(g >> 8),
B: uint8(b >> 8),
A: uint8(a >> 8),
}
index ++
}}
return texture, nil
}

BIN
examples/raycaster/wall.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB