Will also be adding in a RESTful endpoint to the server as well so it can consume both types
191 lines
4.8 KiB
Go
191 lines
4.8 KiB
Go
package atlas
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"math/rand"
|
|
|
|
"github.com/mdiluz/rove/pkg/maths"
|
|
"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"`
|
|
}
|
|
|
|
// 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"`
|
|
|
|
// size is the current width/height of the given atlas
|
|
Size int `json:"size"`
|
|
|
|
// ChunkSize is the dimensions of each chunk
|
|
ChunkSize int `json:"chunksize"`
|
|
}
|
|
|
|
// NewAtlas creates a new empty atlas
|
|
func NewAtlas(size, chunkSize int) Atlas {
|
|
if size%2 != 0 {
|
|
log.Fatal("atlas size must always be even")
|
|
}
|
|
|
|
a := Atlas{
|
|
Size: size,
|
|
Chunks: make([]Chunk, size*size),
|
|
ChunkSize: chunkSize,
|
|
}
|
|
|
|
// Initialise all the chunks
|
|
for i := range a.Chunks {
|
|
a.Chunks[i] = Chunk{
|
|
Tiles: make([]byte, chunkSize*chunkSize),
|
|
}
|
|
}
|
|
|
|
return a
|
|
}
|
|
|
|
// SpawnRocks peppers the world with rocks
|
|
func (a *Atlas) SpawnRocks() error {
|
|
extent := a.ChunkSize * (a.Size / 2)
|
|
|
|
// Pepper the current world with rocks
|
|
for i := -extent; i < extent; i++ {
|
|
for j := -extent; j < extent; j++ {
|
|
if rand.Intn(16) == 0 {
|
|
if err := a.SetTile(vector.Vector{X: i, Y: j}, TileRock); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SpawnWalls spawns the around the world
|
|
func (a *Atlas) SpawnWalls() error {
|
|
extent := a.ChunkSize * (a.Size / 2)
|
|
|
|
// Surround the atlas in walls
|
|
for i := -extent; i < extent; i++ {
|
|
|
|
if err := a.SetTile(vector.Vector{X: i, Y: extent - 1}, TileWall); err != nil { // N
|
|
return err
|
|
} else if err := a.SetTile(vector.Vector{X: extent - 1, Y: i}, TileWall); err != nil { // E
|
|
return err
|
|
} else if err := a.SetTile(vector.Vector{X: i, Y: -extent}, TileWall); err != nil { // S
|
|
return err
|
|
} else if err := a.SetTile(vector.Vector{X: -extent, Y: i}, TileWall); err != nil { // W
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetTile sets an individual tile's kind
|
|
func (a *Atlas) SetTile(v vector.Vector, tile byte) error {
|
|
chunk := a.toChunk(v)
|
|
if chunk >= len(a.Chunks) {
|
|
return fmt.Errorf("location outside of allocated atlas")
|
|
}
|
|
|
|
local := a.toChunkLocal(v)
|
|
tileId := local.X + local.Y*a.ChunkSize
|
|
if tileId >= len(a.Chunks[chunk].Tiles) {
|
|
return fmt.Errorf("location outside of allocated chunk")
|
|
}
|
|
a.Chunks[chunk].Tiles[tileId] = tile
|
|
return nil
|
|
}
|
|
|
|
// GetTile will return an individual tile
|
|
func (a *Atlas) GetTile(v vector.Vector) (byte, error) {
|
|
chunk := a.toChunk(v)
|
|
if chunk >= len(a.Chunks) {
|
|
return 0, fmt.Errorf("location outside of allocated atlas")
|
|
}
|
|
|
|
local := a.toChunkLocal(v)
|
|
tileId := local.X + local.Y*a.ChunkSize
|
|
if tileId >= len(a.Chunks[chunk].Tiles) {
|
|
return 0, fmt.Errorf("location outside of allocated chunk")
|
|
}
|
|
|
|
return a.Chunks[chunk].Tiles[tileId], nil
|
|
}
|
|
|
|
// 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 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.Size / 2, Y: a.Size / 2})
|
|
// Get the ID based on the final values
|
|
return (a.Size * 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.Size) - (a.Size / 2),
|
|
Y: (chunk / a.Size) - (a.Size / 2),
|
|
}
|
|
|
|
return v.Multiplied(a.ChunkSize)
|
|
}
|
|
|
|
// GetWorldExtent gets the min and max valid coordinates of world
|
|
func (a *Atlas) GetWorldExtents() (min, max vector.Vector) {
|
|
min = vector.Vector{
|
|
X: -(a.Size / 2) * a.ChunkSize,
|
|
Y: -(a.Size / 2) * a.ChunkSize,
|
|
}
|
|
max = vector.Vector{
|
|
X: -min.X - 1,
|
|
Y: -min.Y - 1,
|
|
}
|
|
return
|
|
}
|
|
|
|
// Grow will return a grown copy of the current atlas
|
|
func (a *Atlas) Grow(size int) error {
|
|
if size%2 != 0 {
|
|
return fmt.Errorf("atlas size must always be even")
|
|
}
|
|
delta := size - a.Size
|
|
if delta < 0 {
|
|
return fmt.Errorf("cannot shrink an atlas")
|
|
} else if delta == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Create a new atlas
|
|
newAtlas := NewAtlas(size, a.ChunkSize)
|
|
|
|
// Copy old chunks into new chunks
|
|
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
|
|
}
|