This repository has been archived on 2023-08-08. You can view files and clone it, but cannot push or open issues or pull requests.
tomo-old/examples/raycaster/raycaster.go

240 lines
6.2 KiB
Go
Raw Normal View History

package main
// import "fmt"
import "math"
import "image"
import "image/color"
2023-04-15 20:23:08 -06:00
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
2023-04-15 20:23:08 -06:00
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/artist"
2023-02-26 20:20:17 -07:00
import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
2023-04-03 14:09:13 -06:00
import "git.tebibyte.media/sashakoshka/tomo/default/config"
type ControlState struct {
WalkForward bool
WalkBackward bool
StrafeLeft bool
StrafeRight bool
LookLeft bool
LookRight bool
2023-02-21 16:53:19 -07:00
Sprint bool
}
type Raycaster struct {
2023-04-15 20:23:08 -06:00
entity tomo.FocusableEntity
config config.Wrapped
Camera
controlState ControlState
world World
2023-02-21 14:48:56 -07:00
textures Textures
onControlStateChange func (ControlState)
2023-02-21 14:48:56 -07:00
renderDistance int
}
2023-02-21 14:48:56 -07:00
func NewRaycaster (world World, textures Textures) (element *Raycaster) {
element = &Raycaster {
Camera: Camera {
Vector: Vector {
2023-02-21 14:48:56 -07:00
X: 1,
Y: 1,
},
Angle: math.Pi / 3,
Fov: 1,
},
world: world,
2023-02-21 14:48:56 -07:00
textures: textures,
renderDistance: 8,
}
2023-04-15 20:23:08 -06:00
element.entity = tomo.NewEntity(element).(tomo.FocusableEntity)
element.entity.SetMinimumSize(64, 64)
return
}
2023-04-15 20:23:08 -06:00
func (element *Raycaster) Entity () tomo.Entity {
return element.entity
}
2023-04-15 20:23:08 -06:00
func (element *Raycaster) Draw (destination canvas.Canvas) {
bounds := element.entity.Bounds()
// artist.FillRectangle(element.core, artist.Uhex(0x000000FF), bounds)
width := bounds.Dx()
height := bounds.Dy()
halfway := bounds.Max.Y - height / 2
ray := Ray { Angle: element.Camera.Angle - element.Camera.Fov / 2 }
for x := 0; x < width; x ++ {
ray.X = element.Camera.X
ray.Y = element.Camera.Y
2023-02-21 14:48:56 -07:00
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))
}
2023-02-21 14:48:56 -07:00
shade := 1.0
if horizontal {
2023-02-21 15:57:52 -07:00
shade *= 0.8
2023-02-21 14:48:56 -07:00
}
shade *= 1 - distance / float64(element.renderDistance)
if shade < 0 { shade = 0 }
2023-02-21 14:48:56 -07:00
ceilingColor := color.RGBA { 0x00, 0x00, 0x00, 0xFF }
floorColor := color.RGBA { 0x39, 0x49, 0x25, 0xFF }
// draw
2023-04-15 20:23:08 -06:00
data, stride := destination.Buffer()
wallStart := halfway - wallHeight
wallEnd := halfway + wallHeight
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
switch {
case y < wallStart:
data[y * stride + x + bounds.Min.X] = ceilingColor
case y < wallEnd:
textureY :=
float64(y - halfway) /
float64(wallEnd - wallStart) + 0.5
// fmt.Println(textureY)
2023-02-21 14:48:56 -07:00
wallColor := element.textures.At (wall, Vector {
textureX,
textureY,
})
wallColor = shadeColor(wallColor, shade)
data[y * stride + x + bounds.Min.X] = wallColor
default:
data[y * stride + x + bounds.Min.X] = floorColor
}
}
// increment angle
ray.Angle += element.Camera.Fov / float64(width)
}
// element.drawMinimap()
}
2023-04-15 20:23:08 -06:00
func (element *Raycaster) Invalidate () {
element.entity.Invalidate()
}
func (element *Raycaster) OnControlStateChange (callback func (ControlState)) {
element.onControlStateChange = callback
}
func (element *Raycaster) Focus () {
element.entity.Focus()
}
func (element *Raycaster) Enabled () bool { return true }
func (element *Raycaster) HandleFocusChange () { }
func (element *Raycaster) HandleMouseDown (x, y int, button input.Button) {
element.entity.Focus()
}
func (element *Raycaster) HandleMouseUp (x, y int, button input.Button) { }
func (element *Raycaster) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
switch key {
case input.KeyLeft: element.controlState.LookLeft = true
case input.KeyRight: element.controlState.LookRight = true
case 'a', 'A': element.controlState.StrafeLeft = true
case 'd', 'D': element.controlState.StrafeRight = true
case 'w', 'W': element.controlState.WalkForward = true
case 's', 'S': element.controlState.WalkBackward = true
case input.KeyLeftControl: element.controlState.Sprint = true
default: return
}
if element.onControlStateChange != nil {
element.onControlStateChange(element.controlState)
}
}
func (element *Raycaster) HandleKeyUp(key input.Key, modifiers input.Modifiers) {
switch key {
case input.KeyLeft: element.controlState.LookLeft = false
case input.KeyRight: element.controlState.LookRight = false
case 'a', 'A': element.controlState.StrafeLeft = false
case 'd', 'D': element.controlState.StrafeRight = false
case 'w', 'W': element.controlState.WalkForward = false
case 's', 'S': element.controlState.WalkBackward = false
case input.KeyLeftControl: element.controlState.Sprint = false
default: return
}
if element.onControlStateChange != nil {
element.onControlStateChange(element.controlState)
}
}
2023-02-21 14:48:56 -07:00
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,
}
}
2023-04-15 20:23:08 -06:00
func (element *Raycaster) drawMinimap (destination canvas.Canvas) {
bounds := element.entity.Bounds()
2023-02-21 14:48:56 -07:00
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 :=
image.Rectangle {
cellPt.Mul(scale),
cellPt.Add(image.Pt(1, 1)).Mul(scale),
}.Add(bounds.Min)
cellColor := color.RGBA { 0x22, 0x22, 0x22, 0xFF }
2023-02-21 14:48:56 -07:00
if cell > 0 {
cellColor = color.RGBA { 0xFF, 0xFF, 0xFF, 0xFF }
}
2023-02-26 20:20:17 -07:00
shapes.FillColorRectangle (
2023-04-15 20:23:08 -06:00
destination,
2023-02-26 20:20:17 -07:00
cellColor,
cellBounds.Inset(1))
}}
playerPt := element.Camera.Mul(float64(scale)).Point().Add(bounds.Min)
playerAnglePt :=
element.Camera.Add(element.Camera.Delta()).
Mul(float64(scale)).Point().Add(bounds.Min)
ray := Ray { Vector: element.Camera.Vector, Angle: element.Camera.Angle }
2023-02-21 14:48:56 -07:00
_, hit, _, _ := ray.Cast(element.world, 8)
hitPt := hit.Mul(float64(scale)).Point().Add(bounds.Min)
playerBounds := image.Rectangle { playerPt, playerPt }.Inset(scale / -8)
2023-02-26 20:20:17 -07:00
shapes.FillColorEllipse (
2023-04-15 20:23:08 -06:00
destination,
2023-02-26 20:20:17 -07:00
artist.Hex(0xFFFFFFFF),
playerBounds)
2023-02-26 20:20:17 -07:00
shapes.ColorLine (
2023-04-15 20:23:08 -06:00
destination,
2023-02-26 20:20:17 -07:00
artist.Hex(0xFFFFFFFF), 1,
playerPt,
playerAnglePt)
2023-02-26 20:20:17 -07:00
shapes.ColorLine (
2023-04-15 20:23:08 -06:00
destination,
2023-02-26 20:20:17 -07:00
artist.Hex(0x00FF00FF), 1,
playerPt,
hitPt)
}