backrooms!!!!!
This commit is contained in:
parent
e966771f5b
commit
20fa445cdd
@ -44,7 +44,7 @@ func run () {
|
|||||||
warningButton := basicElements.NewButton("popups.DialogKindWarning")
|
warningButton := basicElements.NewButton("popups.DialogKindWarning")
|
||||||
warningButton.OnClick (func () {
|
warningButton.OnClick (func () {
|
||||||
popups.NewDialog (
|
popups.NewDialog (
|
||||||
popups.DialogKindQuestion,
|
popups.DialogKindWarning,
|
||||||
"Warning",
|
"Warning",
|
||||||
"They are fast approaching.")
|
"They are fast approaching.")
|
||||||
})
|
})
|
||||||
@ -53,7 +53,7 @@ func run () {
|
|||||||
errorButton := basicElements.NewButton("popups.DialogKindError")
|
errorButton := basicElements.NewButton("popups.DialogKindError")
|
||||||
errorButton.OnClick (func () {
|
errorButton.OnClick (func () {
|
||||||
popups.NewDialog (
|
popups.NewDialog (
|
||||||
popups.DialogKindQuestion,
|
popups.DialogKindError,
|
||||||
"Error",
|
"Error",
|
||||||
"There is nowhere left to go.")
|
"There is nowhere left to go.")
|
||||||
})
|
})
|
||||||
|
@ -13,9 +13,9 @@ type Game struct {
|
|||||||
controlState ControlState
|
controlState ControlState
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGame (world World) (game *Game) {
|
func NewGame (world World, textures Textures) (game *Game) {
|
||||||
game = &Game {
|
game = &Game {
|
||||||
Raycaster: NewRaycaster(world),
|
Raycaster: NewRaycaster(world, textures),
|
||||||
stopChan: make(chan bool),
|
stopChan: make(chan bool),
|
||||||
}
|
}
|
||||||
game.Raycaster.OnControlStateChange (func (state ControlState) {
|
game.Raycaster.OnControlStateChange (func (state ControlState) {
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import "bytes"
|
||||||
|
import _ "embed"
|
||||||
|
import _ "image/png"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo"
|
import "git.tebibyte.media/sashakoshka/tomo"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
|
import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
|
import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
|
||||||
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
|
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
|
||||||
|
|
||||||
|
//go:embed wall.png
|
||||||
|
var wallTextureBytes []uint8
|
||||||
|
|
||||||
func main () {
|
func main () {
|
||||||
tomo.Run(run)
|
tomo.Run(run)
|
||||||
}
|
}
|
||||||
@ -16,20 +22,28 @@ func run () {
|
|||||||
container := basicElements.NewContainer(basicLayouts.Vertical { true, true })
|
container := basicElements.NewContainer(basicLayouts.Vertical { true, true })
|
||||||
window.Adopt(container)
|
window.Adopt(container)
|
||||||
|
|
||||||
game := NewGame (DefaultWorld {
|
wallTexture, _ := TextureFrom(bytes.NewReader(wallTextureBytes))
|
||||||
|
|
||||||
|
game := NewGame (World {
|
||||||
Data: []int {
|
Data: []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,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,0,0,0,0,1,
|
1,0,1,1,1,1,1,1,1,0,0,0,1,
|
||||||
1,0,0,1,1,0,1,0,0,1,
|
1,0,0,0,0,0,0,0,1,1,1,0,1,
|
||||||
1,0,0,1,0,0,1,0,0,1,
|
1,0,0,0,0,0,0,0,1,0,0,0,1,
|
||||||
1,0,0,1,0,0,1,0,0,1,
|
1,0,0,0,0,0,0,0,1,0,1,1,1,
|
||||||
1,0,0,1,0,1,1,0,0,1,
|
1,1,1,1,1,1,1,1,1,0,0,0,1,
|
||||||
1,0,0,0,0,0,0,0,0,1,
|
1,0,0,0,0,0,0,0,1,1,0,1,1,
|
||||||
1,0,0,0,0,0,0,0,0,1,
|
1,0,0,1,0,0,0,0,0,0,0,0,1,
|
||||||
1,1,1,1,1,1,1,1,1,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)
|
container.Adopt(basicElements.NewLabel("Explore a 3D world!", false), false)
|
||||||
|
@ -3,16 +3,12 @@ package main
|
|||||||
import "math"
|
import "math"
|
||||||
import "image"
|
import "image"
|
||||||
|
|
||||||
type World interface {
|
type World struct {
|
||||||
At (image.Point) int
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultWorld struct {
|
|
||||||
Data []int
|
Data []int
|
||||||
Stride 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.X < 0 { return 0 }
|
||||||
if position.Y < 0 { return 0 }
|
if position.Y < 0 { return 0 }
|
||||||
if position.X >= world.Stride { return 0 }
|
if position.X >= world.Stride { return 0 }
|
||||||
@ -98,21 +94,30 @@ type Ray struct {
|
|||||||
Angle float64
|
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)
|
// return ray.castV(world, max)
|
||||||
if world.At(ray.Point()) > 0 {
|
cellAt := world.At(ray.Point())
|
||||||
return 0, Vector { }
|
if cellAt > 0 {
|
||||||
|
return 0, Vector { }, cellAt, false
|
||||||
}
|
}
|
||||||
hDistance, hPos := ray.castH(world, max)
|
hDistance, hPos, hWall := ray.castH(world, max)
|
||||||
vDistance, vPos := ray.castV(world, max)
|
vDistance, vPos, vWall := ray.castV(world, max)
|
||||||
if hDistance < vDistance {
|
if hDistance < vDistance {
|
||||||
return hDistance, hPos
|
return hDistance, hPos, hWall, true
|
||||||
} else {
|
} 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 position Vector
|
||||||
var delta Vector
|
var delta Vector
|
||||||
ray.Angle = math.Mod(ray.Angle, math.Pi * 2)
|
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
|
delta.Y = 1
|
||||||
} else {
|
} else {
|
||||||
// facing straight left or right
|
// facing straight left or right
|
||||||
return float64(max), Vector { }
|
return float64(max), Vector { }, 0
|
||||||
}
|
}
|
||||||
position.X = ray.X + (ray.Y - position.Y) / tan
|
position.X = ray.X + (ray.Y - position.Y) / tan
|
||||||
delta.X = -delta.Y / tan
|
delta.X = -delta.Y / tan
|
||||||
@ -144,10 +149,10 @@ func (ray *Ray) castH (world World, max int) (distance float64, hit Vector) {
|
|||||||
steps ++
|
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 position Vector
|
||||||
var delta Vector
|
var delta Vector
|
||||||
tan := math.Tan(math.Pi - ray.Angle)
|
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
|
delta.X = 1
|
||||||
} else {
|
} else {
|
||||||
// facing straight left or right
|
// facing straight left or right
|
||||||
return float64(max), Vector { }
|
return float64(max), Vector { }, 0
|
||||||
}
|
}
|
||||||
position.Y = ray.Y + (ray.X - position.X) * tan
|
position.Y = ray.Y + (ray.X - position.X) * tan
|
||||||
delta.Y = -delta.X * tan
|
delta.Y = -delta.X * tan
|
||||||
@ -176,6 +181,5 @@ func (ray *Ray) castV (world World, max int) (distance float64, hit Vector) {
|
|||||||
steps ++
|
steps ++
|
||||||
}
|
}
|
||||||
|
|
||||||
return position.Sub(ray.Vector).Hypot(), position
|
return position.Sub(ray.Vector).Hypot(), position, world.At(position.Point())
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
@ -28,20 +28,24 @@ type Raycaster struct {
|
|||||||
Camera
|
Camera
|
||||||
controlState ControlState
|
controlState ControlState
|
||||||
world World
|
world World
|
||||||
|
textures Textures
|
||||||
onControlStateChange func (ControlState)
|
onControlStateChange func (ControlState)
|
||||||
|
renderDistance int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRaycaster (world World) (element *Raycaster) {
|
func NewRaycaster (world World, textures Textures) (element *Raycaster) {
|
||||||
element = &Raycaster {
|
element = &Raycaster {
|
||||||
Camera: Camera {
|
Camera: Camera {
|
||||||
Vector: Vector {
|
Vector: Vector {
|
||||||
X: 1.5,
|
X: 1,
|
||||||
Y: 1.5,
|
Y: 1,
|
||||||
},
|
},
|
||||||
Angle: math.Pi / 3,
|
Angle: math.Pi / 3,
|
||||||
Fov: 1,
|
Fov: 1,
|
||||||
},
|
},
|
||||||
world: world,
|
world: world,
|
||||||
|
textures: textures,
|
||||||
|
renderDistance: 8,
|
||||||
}
|
}
|
||||||
element.Core, element.core = core.NewCore(element.drawAll)
|
element.Core, element.core = core.NewCore(element.drawAll)
|
||||||
element.FocusableCore,
|
element.FocusableCore,
|
||||||
@ -113,22 +117,26 @@ func (element *Raycaster) drawAll () {
|
|||||||
ray.X = element.Camera.X
|
ray.X = element.Camera.X
|
||||||
ray.Y = element.Camera.Y
|
ray.Y = element.Camera.Y
|
||||||
|
|
||||||
distance, _ := ray.Cast(element.world, 8)
|
distance, hitPoint, wall, horizontal := ray.Cast (
|
||||||
distanceFac := float64(distance) / 8
|
element.world, element.renderDistance)
|
||||||
distance *= math.Cos(ray.Angle - element.Camera.Angle)
|
distance *= math.Cos(ray.Angle - element.Camera.Angle)
|
||||||
|
textureX := math.Mod(hitPoint.X + hitPoint.Y, 1)
|
||||||
|
if textureX < 0 { textureX += 1 }
|
||||||
|
|
||||||
wallHeight := height
|
wallHeight := height
|
||||||
if distance > 0 {
|
if distance > 0 {
|
||||||
wallHeight = int((float64(height) / 2.0) / float64(distance))
|
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 }
|
ceilingColor := color.RGBA { 0x00, 0x00, 0x00, 0xFF }
|
||||||
wallColor := color.RGBA { 0xCC, 0x33, 0x22, 0xFF }
|
floorColor := color.RGBA { 0x39, 0x49, 0x25, 0xFF }
|
||||||
floorColor := color.RGBA { 0x11, 0x50, 0x22, 0xFF }
|
|
||||||
|
|
||||||
// fmt.Println(float64(distance) / 32)
|
|
||||||
|
|
||||||
wallColor = artist.LerpRGBA(wallColor, ceilingColor, distanceFac)
|
|
||||||
|
|
||||||
// draw
|
// draw
|
||||||
data, stride := element.core.Buffer()
|
data, stride := element.core.Buffer()
|
||||||
@ -136,16 +144,26 @@ func (element *Raycaster) drawAll () {
|
|||||||
wallEnd := height / 2 + wallHeight + bounds.Min.Y
|
wallEnd := height / 2 + wallHeight + bounds.Min.Y
|
||||||
if wallStart < 0 { wallStart = 0 }
|
if wallStart < 0 { wallStart = 0 }
|
||||||
if wallEnd > bounds.Max.Y { wallEnd = bounds.Max.Y }
|
if wallEnd > bounds.Max.Y { wallEnd = bounds.Max.Y }
|
||||||
|
|
||||||
for y := bounds.Min.Y; y < wallStart; y ++ {
|
for y := bounds.Min.Y; y < wallStart; y ++ {
|
||||||
data[y * stride + x + bounds.Min.X] = ceilingColor
|
data[y * stride + x + bounds.Min.X] = ceilingColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
slicePoint := 0.0
|
||||||
|
slicePointDelta := 1 / float64(wallEnd - wallStart)
|
||||||
for y := wallStart; y < wallEnd; y ++ {
|
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
|
data[y * stride + x + bounds.Min.X] = wallColor
|
||||||
|
|
||||||
|
slicePoint += slicePointDelta
|
||||||
}
|
}
|
||||||
|
|
||||||
for y := wallEnd; y < bounds.Max.Y; y ++ {
|
for y := wallEnd; y < bounds.Max.Y; y ++ {
|
||||||
floorFac := float64(y - (height / 2)) / float64(height / 2)
|
data[y * stride + x + bounds.Min.X] = floorColor
|
||||||
data[y * stride + x + bounds.Min.X] =
|
|
||||||
artist.LerpRGBA(ceilingColor, floorColor, floorFac)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// increment angle
|
// increment angle
|
||||||
@ -155,11 +173,20 @@ func (element *Raycaster) drawAll () {
|
|||||||
// element.drawMinimap()
|
// 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 () {
|
func (element *Raycaster) drawMinimap () {
|
||||||
bounds := element.Bounds()
|
bounds := element.Bounds()
|
||||||
scale := 16
|
scale := 8
|
||||||
for y := 0; y < 10; y ++ {
|
for y := 0; y < len(element.world.Data) / element.world.Stride; y ++ {
|
||||||
for x := 0; x < 10; x ++ {
|
for x := 0; x < element.world.Stride; x ++ {
|
||||||
cellPt := image.Pt(x, y)
|
cellPt := image.Pt(x, y)
|
||||||
cell := element.world.At(cellPt)
|
cell := element.world.At(cellPt)
|
||||||
cellBounds :=
|
cellBounds :=
|
||||||
@ -168,7 +195,7 @@ func (element *Raycaster) drawMinimap () {
|
|||||||
cellPt.Add(image.Pt(1, 1)).Mul(scale),
|
cellPt.Add(image.Pt(1, 1)).Mul(scale),
|
||||||
}.Add(bounds.Min)
|
}.Add(bounds.Min)
|
||||||
cellColor := color.RGBA { 0x22, 0x22, 0x22, 0xFF }
|
cellColor := color.RGBA { 0x22, 0x22, 0x22, 0xFF }
|
||||||
if cell == 1 {
|
if cell > 0 {
|
||||||
cellColor = color.RGBA { 0xFF, 0xFF, 0xFF, 0xFF }
|
cellColor = color.RGBA { 0xFF, 0xFF, 0xFF, 0xFF }
|
||||||
}
|
}
|
||||||
artist.FillRectangle (
|
artist.FillRectangle (
|
||||||
@ -182,9 +209,8 @@ func (element *Raycaster) drawMinimap () {
|
|||||||
element.Camera.Add(element.Camera.Delta()).
|
element.Camera.Add(element.Camera.Delta()).
|
||||||
Mul(float64(scale)).Point().Add(bounds.Min)
|
Mul(float64(scale)).Point().Add(bounds.Min)
|
||||||
ray := Ray { Vector: element.Camera.Vector, Angle: element.Camera.Angle }
|
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)
|
hitPt := hit.Mul(float64(scale)).Point().Add(bounds.Min)
|
||||||
// fmt.Println(rayDistance)
|
|
||||||
|
|
||||||
playerBounds := image.Rectangle { playerPt, playerPt }.Inset(scale / -8)
|
playerBounds := image.Rectangle { playerPt, playerPt }.Inset(scale / -8)
|
||||||
artist.FillEllipse (
|
artist.FillEllipse (
|
||||||
|
43
examples/raycaster/texture.go
Normal file
43
examples/raycaster/texture.go
Normal 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
BIN
examples/raycaster/wall.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
Reference in New Issue
Block a user