From 20fa445cddaadc8d471169f0196c9b0d05383aef Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 21 Feb 2023 16:48:56 -0500 Subject: [PATCH] backrooms!!!!! --- examples/popups/main.go | 4 +- examples/raycaster/game.go | 4 +- examples/raycaster/main.go | 38 ++++++++++++------ examples/raycaster/ray.go | 44 +++++++++++---------- examples/raycaster/raycaster.go | 68 ++++++++++++++++++++++---------- examples/raycaster/texture.go | 43 ++++++++++++++++++++ examples/raycaster/wall.png | Bin 0 -> 1520 bytes 7 files changed, 144 insertions(+), 57 deletions(-) create mode 100644 examples/raycaster/texture.go create mode 100644 examples/raycaster/wall.png diff --git a/examples/popups/main.go b/examples/popups/main.go index 819ae7c..5a19c85 100644 --- a/examples/popups/main.go +++ b/examples/popups/main.go @@ -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.") }) diff --git a/examples/raycaster/game.go b/examples/raycaster/game.go index ec6eb46..a219fc2 100644 --- a/examples/raycaster/game.go +++ b/examples/raycaster/game.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) { diff --git a/examples/raycaster/main.go b/examples/raycaster/main.go index 79829dd..e05118c 100644 --- a/examples/raycaster/main.go +++ b/examples/raycaster/main.go @@ -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) diff --git a/examples/raycaster/ray.go b/examples/raycaster/ray.go index 0b4d6ae..d211d81 100644 --- a/examples/raycaster/ray.go +++ b/examples/raycaster/ray.go @@ -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()) } diff --git a/examples/raycaster/raycaster.go b/examples/raycaster/raycaster.go index b72af53..b1936e4 100644 --- a/examples/raycaster/raycaster.go +++ b/examples/raycaster/raycaster.go @@ -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 ( diff --git a/examples/raycaster/texture.go b/examples/raycaster/texture.go new file mode 100644 index 0000000..7906751 --- /dev/null +++ b/examples/raycaster/texture.go @@ -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 +} diff --git a/examples/raycaster/wall.png b/examples/raycaster/wall.png new file mode 100644 index 0000000000000000000000000000000000000000..ce40abc09e53d212275daa3d3dbef54e328b3032 GIT binary patch literal 1520 zcmVV_cXy*LCvommgf$$^HI8864?kLAE5HlmGnuiKu}WIg^~h7!wiU{_#K@ zdOy%qN{j0}!G-yC;W2|HlYVaiWECb#u43g$?+51}Px{fIippd}p{lS7F{iZy5g}?( z6k`l-_d6oOxGr29rGfyd5D^gYczmF$0uw9^$^E6MAY`G&a{c-2^aYs#X6DkU1UryaoR~~=;K>aY>j9kIW$;^vNXc$Mov0_pV zMWU+23?c&*Bnwa?gSi&!3V^7P6iL#0XGRc}jBx?Nipg=ikrj9pEDNe&d|qS~kK==? zfE20(QNeJEq$t*0bOI?QHCY5l??eP{PLfm=6o@K@@%;KC3!I5tLjs}_)r7+i#&uBw zX3n@SD41hVnFxxhvCc)4hN&{oi=UtWW?UzSA4pTo9YhdU%#B!!T8W5a;@s{B*BF#Q zXdzXo6vSF6`%TFLwo^;~Y}?k{)G$?I1>>Bwqw_q!iF2STs0yYA0%Kge6Tn&%Q9%%_ z8L{T8Cg^#coVm5R_d6fQol2mJ%%l{m3PT`w%S9CuA`-U)5yP~hZiowJyJISbsDObt z=XN}3?uZH+LRF$)pMO9W?cTtl5L#2LcbfY)XDmd8)(rw;F4Tnk?E%2DL=jXBbx;(d z5CreOJLg78(!JBXQvBA}Lr2x7&^5aYs=U*=yIflMla7H#8MZB{ZQ4UtgchuM1g)h$0HSCd9;u zTs+V3UwOjKH}nh+{a>E&$MK-9_`i8R=Xxto=-WJhH&49F^LO)vzRmMD^Mt?4b4=gL z6aFX9QxeOK+Fv}M3n}@<^L{g~Yv9d)@myYc;`{uf6ij!XXy(i@UKmzX*?B@6Xfe-= z&*zE0%ky{hgucu3ck_h4%ky{hgucu3ck_h*_K)AGT2#I!Il;}jo|FD)JiiC68EY-n zjd4v{Q`T6h8d}A*CcPg-}0ut42);$r48MrI%f|+rR;OjZJZOj1E zolpvd?S&PP5wvdmMkSOARiIk5rW`k=66OrHx{BGZVQLh~oUg-e%QNO=j$p0A1?Wu} zvxwO2e=R|p2_cHxmS?A+B3CdcWCqP4vX~<<71WXx#Z0zA6Ec!(2Fn#uC1OkGF8mj? W(<#f=qcu7J0000