rove/pkg/atlas/atlas.go
Marc Di Luzio b116cdf291 Convert Atlas to infinite lazy growth
The atlas will now expand as needed for any query, but only initialise the chunk tile memory when requested

	While this may still be a pre-mature optimisation, it does simplify some code and ensures that our memory footprint stays small, for the most part
2020-06-27 14:48:21 +01:00

163 lines
4.3 KiB
Go

package atlas
import (
"log"
"math/rand"
"github.com/mdiluz/rove/pkg/maths"
"github.com/mdiluz/rove/pkg/objects"
"github.com/mdiluz/rove/pkg/vector"
)
// Chunk represents a fixed square grid of tiles
type Chunk struct {
// Tiles represents the tiles within the chunk
Tiles []byte `json:"tiles"`
}
// SpawnContent will create a chunk and fill it with spawned tiles
func (c *Chunk) SpawnContent(size int) {
c.Tiles = make([]byte, size*size)
for i := 0; i < len(c.Tiles); i++ {
c.Tiles[i] = objects.Empty
}
// For now, fill it randomly with objects
for i := range c.Tiles {
if rand.Intn(16) == 0 {
c.Tiles[i] = objects.LargeRock
} else if rand.Intn(32) == 0 {
c.Tiles[i] = objects.SmallRock
}
}
}
// Atlas represents a grid of Chunks
type Atlas struct {
// Chunks represents all chunks in the world
// This is intentionally not a 2D array so it can be expanded in all directions
Chunks []Chunk `json:"chunks"`
// CurrentSize is the current width/height of the given atlas
CurrentSize int `json:"currentSize"`
// ChunkSize is the dimensions of each chunk
ChunkSize int `json:"chunksize"`
}
// NewAtlas creates a new empty atlas
func NewAtlas(chunkSize int) Atlas {
return Atlas{
CurrentSize: 0,
Chunks: nil,
ChunkSize: chunkSize,
}
}
// SetTile sets an individual tile's kind
func (a *Atlas) SetTile(v vector.Vector, tile byte) {
// Get the chunk, expand, and spawn it if needed
c := a.toChunkWithGrow(v)
chunk := a.Chunks[c]
if chunk.Tiles == nil {
chunk.SpawnContent(a.ChunkSize)
}
local := a.toChunkLocal(v)
tileId := local.X + local.Y*a.ChunkSize
// Sanity check
if tileId >= len(chunk.Tiles) || tileId < 0 {
log.Fatalf("Local tileID is not in valid chunk, somehow, this means something is very wrong")
}
// Set the chunk back
chunk.Tiles[tileId] = tile
a.Chunks[c] = chunk
}
// GetTile will return an individual tile
func (a *Atlas) GetTile(v vector.Vector) byte {
// Get the chunk, expand, and spawn it if needed
c := a.toChunkWithGrow(v)
chunk := a.Chunks[c]
if chunk.Tiles == nil {
chunk.SpawnContent(a.ChunkSize)
}
local := a.toChunkLocal(v)
tileId := local.X + local.Y*a.ChunkSize
// Sanity check
if tileId >= len(chunk.Tiles) || tileId < 0 {
log.Fatalf("Local tileID is not in valid chunk, somehow, this means something is very wrong")
}
return chunk.Tiles[tileId]
}
// toChunkWithGrow will expand the atlas for a given tile, returns the new chunk
func (a *Atlas) toChunkWithGrow(v vector.Vector) int {
for {
// Get the chunk, and grow looping until we have a valid chunk
chunk := a.toChunk(v)
if chunk >= len(a.Chunks) || chunk < 0 {
a.grow()
} else {
return chunk
}
}
}
// toChunkLocal gets a chunk local coordinate for a tile
func (a *Atlas) toChunkLocal(v vector.Vector) vector.Vector {
return vector.Vector{X: maths.Pmod(v.X, a.ChunkSize), Y: maths.Pmod(v.Y, a.ChunkSize)}
}
// GetChunkID gets the current chunk ID for a position in the world
func (a *Atlas) toChunk(v vector.Vector) int {
local := a.toChunkLocal(v)
// Get the chunk origin itself
origin := v.Added(local.Negated())
// Divided it by the number of chunks
origin = origin.Divided(a.ChunkSize)
// Shift it by our size (our origin is in the middle)
origin = origin.Added(vector.Vector{X: a.CurrentSize / 2, Y: a.CurrentSize / 2})
// Get the ID based on the final values
return (a.CurrentSize * origin.Y) + origin.X
}
// chunkOrigin gets the chunk origin for a given chunk index
func (a *Atlas) chunkOrigin(chunk int) vector.Vector {
v := vector.Vector{
X: maths.Pmod(chunk, a.CurrentSize) - (a.CurrentSize / 2),
Y: (chunk / a.CurrentSize) - (a.CurrentSize / 2),
}
return v.Multiplied(a.ChunkSize)
}
// grow will expand the current atlas in all directions by one chunk
func (a *Atlas) grow() error {
// Create a new atlas
newAtlas := NewAtlas(a.ChunkSize)
// Expand by one on each axis
newAtlas.CurrentSize = a.CurrentSize + 2
// Allocate the new atlas chunks
// These chunks will have nil tile slices
newAtlas.Chunks = make([]Chunk, newAtlas.CurrentSize*newAtlas.CurrentSize)
// Copy all old chunks into the new atlas
for index, chunk := range a.Chunks {
// Calculate the new chunk location and copy over the data
newAtlas.Chunks[newAtlas.toChunk(a.chunkOrigin(index))] = chunk
}
// Copy the new atlas data into this one
*a = newAtlas
// Return the new atlas
return nil
}