backrooms!!!!!
This commit is contained in:
parent
e966771f5b
commit
20fa445cdd
@ -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.")
|
||||
})
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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, 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 (
|
||||
|
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