last_light/game/world/generate_bsp_map.go

291 lines
7.3 KiB
Go
Raw Normal View History

2024-05-06 18:59:14 +03:00
package world
2024-05-03 13:46:32 +03:00
import (
"math/rand"
2024-05-06 21:19:08 +03:00
"mvvasilev/last_light/engine"
2024-05-03 13:46:32 +03:00
)
type splitDirection bool
const (
splitDirectionVertical splitDirection = true
splitDirectionHorizontal splitDirection = false
)
type bspNode struct {
2024-05-06 21:19:08 +03:00
origin engine.Position
size engine.Size
2024-05-03 13:46:32 +03:00
2024-05-06 21:19:08 +03:00
room engine.BoundingBox
2024-05-03 13:46:32 +03:00
hasRoom bool
left *bspNode
right *bspNode
splitDir splitDirection
}
2024-05-06 18:59:14 +03:00
func CreateBSPDungeonMap(width, height int, numSplits int) *BSPDungeonMap {
2024-05-03 13:46:32 +03:00
root := new(bspNode)
2024-05-06 21:19:08 +03:00
root.origin = engine.PositionAt(0, 0)
root.size = engine.SizeOf(width, height)
2024-05-03 13:46:32 +03:00
split(root, numSplits)
tiles := make([][]Tile, height)
for h := range height {
tiles[h] = make([]Tile, width)
}
2024-05-12 23:22:39 +03:00
rooms := make([]engine.BoundingBox, 0, numSplits*numSplits)
2024-05-03 13:46:32 +03:00
iterateBspLeaves(root, func(leaf *bspNode) {
2024-05-06 21:19:08 +03:00
x := engine.RandInt(leaf.origin.X(), leaf.origin.X()+leaf.size.Width()/4)
y := engine.RandInt(leaf.origin.Y(), leaf.origin.Y()+leaf.size.Height()/4)
w := engine.RandInt(3, leaf.size.Width()-1)
h := engine.RandInt(3, leaf.size.Height()-1)
2024-05-03 13:46:32 +03:00
if x+w >= width {
w = w - (x + w - width) - 1
}
if y+h >= height {
h = h - (y + h - height) - 1
}
2024-05-06 21:19:08 +03:00
room := engine.BoundingBox{
Positioned: engine.WithPosition(engine.PositionAt(x, y)),
Sized: engine.WithSize(engine.SizeOf(w, h)),
2024-05-03 13:46:32 +03:00
}
rooms = append(rooms, room)
makeRoom(tiles, room)
leaf.room = room
leaf.hasRoom = true
})
iterateBspParents(root, func(parent *bspNode) {
roomLeft := findRoom(parent.left)
roomRight := findRoom(parent.right)
zCorridor(
tiles,
2024-05-06 21:19:08 +03:00
engine.PositionAt(
2024-05-06 18:59:14 +03:00
roomLeft.Position().X()+roomLeft.Size().Width()/2,
roomLeft.Position().Y()+roomLeft.Size().Height()/2,
2024-05-03 13:46:32 +03:00
),
2024-05-06 21:19:08 +03:00
engine.PositionAt(
2024-05-06 18:59:14 +03:00
roomRight.Position().X()+roomRight.Size().Width()/2,
roomRight.Position().Y()+roomRight.Size().Height()/2,
2024-05-03 13:46:32 +03:00
),
parent.splitDir,
)
})
2024-05-06 18:59:14 +03:00
bsp := new(BSPDungeonMap)
2024-05-03 13:46:32 +03:00
spawnRoom := findRoom(root.left)
2024-05-12 23:22:39 +03:00
staircaseRoom := findRoom(root.right)
2024-05-03 13:46:32 +03:00
bsp.rooms = rooms
bsp.level = CreateBasicMap(tiles)
2024-05-12 23:22:39 +03:00
2024-05-06 21:19:08 +03:00
bsp.playerSpawnPoint = engine.PositionAt(
2024-05-06 18:59:14 +03:00
spawnRoom.Position().X()+spawnRoom.Size().Width()/2,
spawnRoom.Position().Y()+spawnRoom.Size().Height()/2,
2024-05-03 13:46:32 +03:00
)
2024-05-12 23:22:39 +03:00
bsp.nextLevelStaircase = engine.PositionAt(
staircaseRoom.Position().X()+staircaseRoom.Size().Width()/2,
staircaseRoom.Position().Y()+staircaseRoom.Size().Height()/2,
)
bsp.level.SetTileAt(bsp.nextLevelStaircase.X(), bsp.nextLevelStaircase.Y(), CreateStaticTile(bsp.nextLevelStaircase.X(), bsp.nextLevelStaircase.Y(), TileTypeStaircaseDown()))
bsp.level.SetTileAt(bsp.playerSpawnPoint.X(), bsp.playerSpawnPoint.Y(), CreateStaticTile(bsp.playerSpawnPoint.X(), bsp.playerSpawnPoint.Y(), TileTypeStaircaseUp()))
2024-05-03 13:46:32 +03:00
return bsp
}
2024-05-06 21:19:08 +03:00
func findRoom(parent *bspNode) engine.BoundingBox {
2024-05-03 13:46:32 +03:00
if parent.hasRoom {
return parent.room
}
if rand.Float32() > 0.5 {
return findRoom(parent.left)
} else {
return findRoom(parent.right)
}
}
2024-05-06 21:19:08 +03:00
func zCorridor(tiles [][]Tile, from engine.Position, to engine.Position, direction splitDirection) {
2024-05-03 13:46:32 +03:00
switch direction {
case splitDirectionHorizontal:
xMidPoint := (from.X() + to.X()) / 2
horizontalTunnel(tiles, from.X(), xMidPoint, from.Y())
horizontalTunnel(tiles, to.X(), xMidPoint, to.Y())
verticalTunnel(tiles, from.Y(), to.Y(), xMidPoint)
case splitDirectionVertical:
yMidPoint := (from.Y() + to.Y()) / 2
verticalTunnel(tiles, from.Y(), yMidPoint, from.X())
verticalTunnel(tiles, to.Y(), yMidPoint, to.X())
horizontalTunnel(tiles, from.X(), to.X(), yMidPoint)
}
}
func iterateBspParents(parent *bspNode, iter func(parent *bspNode)) {
if parent.left != nil && parent.right != nil {
iter(parent)
}
if parent.left != nil {
iterateBspParents(parent.left, iter)
}
if parent.right != nil {
iterateBspParents(parent.right, iter)
}
}
func iterateBspLeaves(parent *bspNode, iter func(leaf *bspNode)) {
if parent.left == nil && parent.right == nil {
iter(parent)
return
}
if parent.left != nil {
iterateBspLeaves(parent.left, iter)
}
if parent.right != nil {
iterateBspLeaves(parent.right, iter)
}
}
func split(parent *bspNode, numSplits int) {
if numSplits <= 0 {
return
}
// split vertically
if parent.size.Width() > parent.size.Height() {
// New splits will be between 45% and 65% of the parent's width
2024-05-06 21:19:08 +03:00
leftSplitWidth := engine.RandInt(int(float32(parent.size.Width())*0.45), int(float32(parent.size.Width())*0.65))
2024-05-03 13:46:32 +03:00
parent.splitDir = splitDirectionVertical
parent.left = new(bspNode)
parent.left.origin = parent.origin
2024-05-06 21:19:08 +03:00
parent.left.size = engine.SizeOf(leftSplitWidth, parent.size.Height())
2024-05-03 13:46:32 +03:00
parent.right = new(bspNode)
parent.right.origin = parent.origin.WithOffset(leftSplitWidth, 0)
2024-05-06 21:19:08 +03:00
parent.right.size = engine.SizeOf(parent.size.Width()-leftSplitWidth, parent.size.Height())
2024-05-03 13:46:32 +03:00
} else { // split horizontally
// New splits will be between 45% and 65% of the parent's height
2024-05-06 21:19:08 +03:00
leftSplitHeight := engine.RandInt(int(float32(parent.size.Height())*0.45), int(float32(parent.size.Height())*0.65))
2024-05-03 13:46:32 +03:00
parent.splitDir = splitDirectionHorizontal
parent.left = new(bspNode)
parent.left.origin = parent.origin
2024-05-06 21:19:08 +03:00
parent.left.size = engine.SizeOf(parent.size.Width(), leftSplitHeight)
2024-05-03 13:46:32 +03:00
parent.right = new(bspNode)
parent.right.origin = parent.origin.WithOffset(0, leftSplitHeight)
2024-05-06 21:19:08 +03:00
parent.right.size = engine.SizeOf(parent.size.Width(), parent.size.Height()-leftSplitHeight)
2024-05-03 13:46:32 +03:00
}
split(parent.left, numSplits-1)
split(parent.right, numSplits-1)
}
func horizontalTunnel(tiles [][]Tile, x1, x2, y int) {
if x1 > x2 {
tx := x2
x2 = x1
x1 = tx
}
placeWallAtIfNotPassable(tiles, x1, y-1)
placeWallAtIfNotPassable(tiles, x1, y)
placeWallAtIfNotPassable(tiles, x1, y+1)
for x := x1; x <= x2; x++ {
if tiles[y][x] != nil && tiles[y][x].Passable() {
continue
}
tiles[y][x] = CreateStaticTile(x, y, TileTypeGround())
placeWallAtIfNotPassable(tiles, x, y-1)
placeWallAtIfNotPassable(tiles, x, y+1)
}
placeWallAtIfNotPassable(tiles, x2, y-1)
placeWallAtIfNotPassable(tiles, x2, y)
placeWallAtIfNotPassable(tiles, x2, y+1)
}
func verticalTunnel(tiles [][]Tile, y1, y2, x int) {
if y1 > y2 {
ty := y2
y2 = y1
y1 = ty
}
placeWallAtIfNotPassable(tiles, x-1, y1)
placeWallAtIfNotPassable(tiles, x, y1)
placeWallAtIfNotPassable(tiles, x+1, y1)
for y := y1; y <= y2; y++ {
if tiles[y][x] != nil && tiles[y][x].Passable() {
continue
}
tiles[y][x] = CreateStaticTile(x, y, TileTypeGround())
placeWallAtIfNotPassable(tiles, x-1, y)
placeWallAtIfNotPassable(tiles, x+1, y)
}
placeWallAtIfNotPassable(tiles, x-1, y2)
placeWallAtIfNotPassable(tiles, x, y2)
placeWallAtIfNotPassable(tiles, x+1, y2)
}
func placeWallAtIfNotPassable(tiles [][]Tile, x, y int) {
if tiles[y][x] != nil && tiles[y][x].Passable() {
return
}
tiles[y][x] = CreateStaticTile(x, y, TileTypeWall())
}
2024-05-06 21:19:08 +03:00
func makeRoom(tiles [][]Tile, room engine.BoundingBox) {
2024-05-06 18:59:14 +03:00
width := room.Size().Width()
height := room.Size().Height()
x := room.Position().X()
y := room.Position().Y()
2024-05-03 13:46:32 +03:00
for w := x; w < x+width+1; w++ {
tiles[y][w] = CreateStaticTile(w, y, TileTypeWall())
tiles[y+height][w] = CreateStaticTile(w, y+height, TileTypeWall())
}
for h := y; h < y+height+1; h++ {
tiles[h][x] = CreateStaticTile(x, h, TileTypeWall())
tiles[h][x+width] = CreateStaticTile(x+width, h, TileTypeWall())
}
for h := y + 1; h < y+height; h++ {
for w := x + 1; w < x+width; w++ {
tiles[h][w] = CreateStaticTile(w, h, TileTypeGround())
}
}
}