Make Atlas grow in X and Y dimensions independently
Fixes exponential growth
This commit is contained in:
parent
b116cdf291
commit
9bb91920c9
3 changed files with 163 additions and 80 deletions
|
@ -38,32 +38,40 @@ type Atlas struct {
|
||||||
// This is intentionally not a 2D array so it can be expanded in all directions
|
// This is intentionally not a 2D array so it can be expanded in all directions
|
||||||
Chunks []Chunk `json:"chunks"`
|
Chunks []Chunk `json:"chunks"`
|
||||||
|
|
||||||
// CurrentSize is the current width/height of the given atlas
|
// CurrentSizeInChunks is the current width/height of the atlas in chunks
|
||||||
CurrentSize int `json:"currentSize"`
|
CurrentSizeInChunks vector.Vector `json:"currentSizeInChunks"`
|
||||||
|
|
||||||
// ChunkSize is the dimensions of each chunk
|
// WorldOriginInChunkSpace represents the location of [0,0] in chunk space
|
||||||
|
WorldOriginInChunkSpace vector.Vector `json:"worldOriginInChunkSpace"`
|
||||||
|
|
||||||
|
// ChunkSize is the x/y dimensions of each square chunk
|
||||||
ChunkSize int `json:"chunksize"`
|
ChunkSize int `json:"chunksize"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAtlas creates a new empty atlas
|
// NewAtlas creates a new empty atlas
|
||||||
func NewAtlas(chunkSize int) Atlas {
|
func NewAtlas(chunkSize int) Atlas {
|
||||||
return Atlas{
|
// Start up with one chunk
|
||||||
CurrentSize: 0,
|
a := Atlas{
|
||||||
Chunks: nil,
|
|
||||||
ChunkSize: chunkSize,
|
ChunkSize: chunkSize,
|
||||||
|
Chunks: make([]Chunk, 1),
|
||||||
|
CurrentSizeInChunks: vector.Vector{X: 1, Y: 1},
|
||||||
|
WorldOriginInChunkSpace: vector.Vector{X: 0, Y: 0},
|
||||||
}
|
}
|
||||||
|
// Initialise the first chunk
|
||||||
|
a.Chunks[0].SpawnContent(chunkSize)
|
||||||
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTile sets an individual tile's kind
|
// SetTile sets an individual tile's kind
|
||||||
func (a *Atlas) SetTile(v vector.Vector, tile byte) {
|
func (a *Atlas) SetTile(v vector.Vector, tile byte) {
|
||||||
// Get the chunk, expand, and spawn it if needed
|
// Get the chunk
|
||||||
c := a.toChunkWithGrow(v)
|
c := a.worldSpaceToChunkWithGrow(v)
|
||||||
chunk := a.Chunks[c]
|
chunk := a.Chunks[c]
|
||||||
if chunk.Tiles == nil {
|
if chunk.Tiles == nil {
|
||||||
chunk.SpawnContent(a.ChunkSize)
|
chunk.SpawnContent(a.ChunkSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
local := a.toChunkLocal(v)
|
local := a.worldSpaceToChunkLocal(v)
|
||||||
tileId := local.X + local.Y*a.ChunkSize
|
tileId := local.X + local.Y*a.ChunkSize
|
||||||
|
|
||||||
// Sanity check
|
// Sanity check
|
||||||
|
@ -78,14 +86,14 @@ func (a *Atlas) SetTile(v vector.Vector, tile byte) {
|
||||||
|
|
||||||
// GetTile will return an individual tile
|
// GetTile will return an individual tile
|
||||||
func (a *Atlas) GetTile(v vector.Vector) byte {
|
func (a *Atlas) GetTile(v vector.Vector) byte {
|
||||||
// Get the chunk, expand, and spawn it if needed
|
// Get the chunk
|
||||||
c := a.toChunkWithGrow(v)
|
c := a.worldSpaceToChunkWithGrow(v)
|
||||||
chunk := a.Chunks[c]
|
chunk := a.Chunks[c]
|
||||||
if chunk.Tiles == nil {
|
if chunk.Tiles == nil {
|
||||||
chunk.SpawnContent(a.ChunkSize)
|
chunk.SpawnContent(a.ChunkSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
local := a.toChunkLocal(v)
|
local := a.worldSpaceToChunkLocal(v)
|
||||||
tileId := local.X + local.Y*a.ChunkSize
|
tileId := local.X + local.Y*a.ChunkSize
|
||||||
|
|
||||||
// Sanity check
|
// Sanity check
|
||||||
|
@ -96,68 +104,134 @@ func (a *Atlas) GetTile(v vector.Vector) byte {
|
||||||
return chunk.Tiles[tileId]
|
return chunk.Tiles[tileId]
|
||||||
}
|
}
|
||||||
|
|
||||||
// toChunkWithGrow will expand the atlas for a given tile, returns the new chunk
|
// worldSpaceToChunkLocal gets a chunk local coordinate for a tile
|
||||||
func (a *Atlas) toChunkWithGrow(v vector.Vector) int {
|
func (a *Atlas) worldSpaceToChunkLocal(v vector.Vector) vector.Vector {
|
||||||
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)}
|
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
|
// worldSpaceToChunk gets the current chunk ID for a position in the world
|
||||||
func (a *Atlas) toChunk(v vector.Vector) int {
|
func (a *Atlas) worldSpaceToChunk(v vector.Vector) int {
|
||||||
local := a.toChunkLocal(v)
|
// First convert to chunk space
|
||||||
// Get the chunk origin itself
|
chunkSpace := a.worldSpaceToChunkSpace(v)
|
||||||
origin := v.Added(local.Negated())
|
|
||||||
// Divided it by the number of chunks
|
// Then return the ID
|
||||||
origin = origin.Divided(a.ChunkSize)
|
return a.chunkSpaceToChunk(chunkSpace)
|
||||||
// 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
|
// worldSpaceToChunkSpace converts from world space to chunk space
|
||||||
func (a *Atlas) chunkOrigin(chunk int) vector.Vector {
|
func (a *Atlas) worldSpaceToChunkSpace(v vector.Vector) vector.Vector {
|
||||||
v := vector.Vector{
|
// Remove the chunk local part
|
||||||
X: maths.Pmod(chunk, a.CurrentSize) - (a.CurrentSize / 2),
|
chunkOrigin := v.Added(a.worldSpaceToChunkLocal(v).Negated())
|
||||||
Y: (chunk / a.CurrentSize) - (a.CurrentSize / 2),
|
// Convert to chunk space coordinate
|
||||||
|
chunkSpaceOrigin := chunkOrigin.Divided(a.ChunkSize)
|
||||||
|
// Shift it by our current chunk origin
|
||||||
|
chunkIndexOrigin := chunkSpaceOrigin.Added(a.WorldOriginInChunkSpace)
|
||||||
|
|
||||||
|
return chunkIndexOrigin
|
||||||
}
|
}
|
||||||
|
|
||||||
return v.Multiplied(a.ChunkSize)
|
// chunkSpaceToWorldSpace vonverts from chunk space to world space
|
||||||
|
func (a *Atlas) chunkSpaceToWorldSpace(v vector.Vector) vector.Vector {
|
||||||
|
|
||||||
|
// Shift it by the current chunk origin
|
||||||
|
shifted := v.Added(a.WorldOriginInChunkSpace.Negated())
|
||||||
|
|
||||||
|
// Multiply out by chunk size
|
||||||
|
return shifted.Multiplied(a.ChunkSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// grow will expand the current atlas in all directions by one chunk
|
// chunkOriginInChunkSpace Gets the chunk origin in chunk space
|
||||||
func (a *Atlas) grow() error {
|
func (a *Atlas) chunkOriginInChunkSpace(chunk int) vector.Vector {
|
||||||
// Create a new atlas
|
// convert the chunk to chunk space
|
||||||
newAtlas := NewAtlas(a.ChunkSize)
|
chunkOrigin := a.chunkToChunkSpace(chunk)
|
||||||
|
|
||||||
// Expand by one on each axis
|
// Shift it by the current chunk origin
|
||||||
newAtlas.CurrentSize = a.CurrentSize + 2
|
return chunkOrigin.Added(a.WorldOriginInChunkSpace.Negated())
|
||||||
|
}
|
||||||
|
|
||||||
// Allocate the new atlas chunks
|
// chunkOriginInWorldSpace gets the chunk origin for a given chunk index
|
||||||
// These chunks will have nil tile slices
|
func (a *Atlas) chunkOriginInWorldSpace(chunk int) vector.Vector {
|
||||||
newAtlas.Chunks = make([]Chunk, newAtlas.CurrentSize*newAtlas.CurrentSize)
|
// convert the chunk to chunk space
|
||||||
|
chunkSpace := a.chunkToChunkSpace(chunk)
|
||||||
|
|
||||||
|
// Convert to world space
|
||||||
|
return a.chunkSpaceToWorldSpace(chunkSpace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// chunkSpaceToChunk converts from chunk space to the chunk
|
||||||
|
func (a *Atlas) chunkSpaceToChunk(v vector.Vector) int {
|
||||||
|
// Along the coridor and up the stair
|
||||||
|
return (v.Y * a.CurrentSizeInChunks.X) + v.X
|
||||||
|
}
|
||||||
|
|
||||||
|
// chunkToChunkSpace returns the chunk space coord for the chunk
|
||||||
|
func (a *Atlas) chunkToChunkSpace(chunk int) vector.Vector {
|
||||||
|
return vector.Vector{
|
||||||
|
X: maths.Pmod(chunk, a.CurrentSizeInChunks.Y),
|
||||||
|
Y: (chunk / a.CurrentSizeInChunks.X),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Atlas) getExtents() (min vector.Vector, max vector.Vector) {
|
||||||
|
min = a.WorldOriginInChunkSpace.Negated()
|
||||||
|
max = min.Added(a.CurrentSizeInChunks)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// worldSpaceToTrunkWithGrow will expand the current atlas for a given world space position if needed
|
||||||
|
func (a *Atlas) worldSpaceToChunkWithGrow(v vector.Vector) int {
|
||||||
|
min, max := a.getExtents()
|
||||||
|
|
||||||
|
// Divide by the chunk size to bring into chunk space
|
||||||
|
v = v.Divided(a.ChunkSize)
|
||||||
|
|
||||||
|
// Check we're within the current extents and bail early
|
||||||
|
if v.X >= min.X && v.Y >= min.Y && v.X < max.X && v.Y < max.Y {
|
||||||
|
return a.worldSpaceToChunk(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the new origin and the new size
|
||||||
|
origin := min
|
||||||
|
size := a.CurrentSizeInChunks
|
||||||
|
|
||||||
|
// If we need to shift the origin back
|
||||||
|
originDiff := origin.Added(v.Negated())
|
||||||
|
if originDiff.X > 0 {
|
||||||
|
origin.X -= originDiff.X
|
||||||
|
size.X += originDiff.X
|
||||||
|
}
|
||||||
|
if originDiff.Y > 0 {
|
||||||
|
origin.Y -= originDiff.Y
|
||||||
|
size.Y += originDiff.Y
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we need to expand the size
|
||||||
|
maxDiff := v.Added(max.Negated())
|
||||||
|
if maxDiff.X > 0 {
|
||||||
|
size.X += maxDiff.X
|
||||||
|
}
|
||||||
|
if maxDiff.Y > 0 {
|
||||||
|
size.Y += maxDiff.Y
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the new size and origin
|
||||||
|
newAtlas := Atlas{
|
||||||
|
ChunkSize: a.ChunkSize,
|
||||||
|
WorldOriginInChunkSpace: origin.Negated(),
|
||||||
|
CurrentSizeInChunks: size,
|
||||||
|
Chunks: make([]Chunk, size.X*size.Y),
|
||||||
|
}
|
||||||
|
|
||||||
// Copy all old chunks into the new atlas
|
// Copy all old chunks into the new atlas
|
||||||
for index, chunk := range a.Chunks {
|
for chunk, chunkData := range a.Chunks {
|
||||||
// Calculate the new chunk location and copy over the data
|
// Calculate the new chunk location and copy over the data
|
||||||
newAtlas.Chunks[newAtlas.toChunk(a.chunkOrigin(index))] = chunk
|
newChunk := newAtlas.worldSpaceToChunk(a.chunkOriginInWorldSpace(chunk))
|
||||||
|
// Copy over the old chunk to the new atlas
|
||||||
|
newAtlas.Chunks[newChunk] = chunkData
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the new atlas data into this one
|
// Copy the new atlas data into this one
|
||||||
*a = newAtlas
|
*a = newAtlas
|
||||||
|
|
||||||
// Return the new atlas
|
return a.worldSpaceToChunk(v)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,54 +11,53 @@ func TestAtlas_NewAtlas(t *testing.T) {
|
||||||
a := NewAtlas(1)
|
a := NewAtlas(1)
|
||||||
assert.NotNil(t, a)
|
assert.NotNil(t, a)
|
||||||
assert.Equal(t, 1, a.ChunkSize)
|
assert.Equal(t, 1, a.ChunkSize)
|
||||||
assert.Equal(t, 0, len(a.Chunks)) // Should start empty
|
assert.Equal(t, 1, len(a.Chunks)) // Should start empty
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAtlas_toChunk(t *testing.T) {
|
func TestAtlas_toChunk(t *testing.T) {
|
||||||
a := NewAtlas(1)
|
a := NewAtlas(1)
|
||||||
assert.NotNil(t, a)
|
assert.NotNil(t, a)
|
||||||
|
|
||||||
// We start empty so we'll look like this
|
|
||||||
chunkID := a.toChunk(vector.Vector{X: 0, Y: 0})
|
|
||||||
assert.Equal(t, 0, chunkID)
|
|
||||||
|
|
||||||
// Get a tile to spawn the chunks
|
// Get a tile to spawn the chunks
|
||||||
a.GetTile(vector.Vector{})
|
a.GetTile(vector.Vector{X: -1, Y: -1})
|
||||||
|
a.GetTile(vector.Vector{X: 0, Y: 0})
|
||||||
|
|
||||||
// Chunks should look like:
|
// Chunks should look like:
|
||||||
// 2 | 3
|
// 2 | 3
|
||||||
// -----
|
// -----
|
||||||
// 0 | 1
|
// 0 | 1
|
||||||
chunkID = a.toChunk(vector.Vector{X: 0, Y: 0})
|
chunkID := a.worldSpaceToChunk(vector.Vector{X: 0, Y: 0})
|
||||||
assert.Equal(t, 3, chunkID)
|
assert.Equal(t, 3, chunkID)
|
||||||
chunkID = a.toChunk(vector.Vector{X: 0, Y: -1})
|
chunkID = a.worldSpaceToChunk(vector.Vector{X: 0, Y: -1})
|
||||||
assert.Equal(t, 1, chunkID)
|
assert.Equal(t, 1, chunkID)
|
||||||
chunkID = a.toChunk(vector.Vector{X: -1, Y: -1})
|
chunkID = a.worldSpaceToChunk(vector.Vector{X: -1, Y: -1})
|
||||||
assert.Equal(t, 0, chunkID)
|
assert.Equal(t, 0, chunkID)
|
||||||
chunkID = a.toChunk(vector.Vector{X: -1, Y: 0})
|
chunkID = a.worldSpaceToChunk(vector.Vector{X: -1, Y: 0})
|
||||||
assert.Equal(t, 2, chunkID)
|
assert.Equal(t, 2, chunkID)
|
||||||
|
|
||||||
a = NewAtlas(2)
|
a = NewAtlas(2)
|
||||||
assert.NotNil(t, a)
|
assert.NotNil(t, a)
|
||||||
// Get a tile to spawn the chunks
|
// Get a tile to spawn the chunks
|
||||||
a.GetTile(vector.Vector{})
|
a.GetTile(vector.Vector{X: -2, Y: -2})
|
||||||
|
a.GetTile(vector.Vector{X: 1, Y: 1})
|
||||||
// Chunks should look like:
|
// Chunks should look like:
|
||||||
// 2 | 3
|
// 2 | 3
|
||||||
// -----
|
// -----
|
||||||
// 0 | 1
|
// 0 | 1
|
||||||
chunkID = a.toChunk(vector.Vector{X: 1, Y: 1})
|
chunkID = a.worldSpaceToChunk(vector.Vector{X: 1, Y: 1})
|
||||||
assert.Equal(t, 3, chunkID)
|
assert.Equal(t, 3, chunkID)
|
||||||
chunkID = a.toChunk(vector.Vector{X: 1, Y: -2})
|
chunkID = a.worldSpaceToChunk(vector.Vector{X: 1, Y: -2})
|
||||||
assert.Equal(t, 1, chunkID)
|
assert.Equal(t, 1, chunkID)
|
||||||
chunkID = a.toChunk(vector.Vector{X: -2, Y: -2})
|
chunkID = a.worldSpaceToChunk(vector.Vector{X: -2, Y: -2})
|
||||||
assert.Equal(t, 0, chunkID)
|
assert.Equal(t, 0, chunkID)
|
||||||
chunkID = a.toChunk(vector.Vector{X: -2, Y: 1})
|
chunkID = a.worldSpaceToChunk(vector.Vector{X: -2, Y: 1})
|
||||||
assert.Equal(t, 2, chunkID)
|
assert.Equal(t, 2, chunkID)
|
||||||
|
|
||||||
a = NewAtlas(2)
|
a = NewAtlas(2)
|
||||||
assert.NotNil(t, a)
|
assert.NotNil(t, a)
|
||||||
// Get a tile to spawn the chunks
|
// Get a tile to spawn the chunks
|
||||||
a.GetTile(vector.Vector{X: 0, Y: 3})
|
a.GetTile(vector.Vector{X: 5, Y: 5})
|
||||||
|
a.GetTile(vector.Vector{X: -5, Y: -5})
|
||||||
// Chunks should look like:
|
// Chunks should look like:
|
||||||
// 12| 13|| 14| 15
|
// 12| 13|| 14| 15
|
||||||
// ----------------
|
// ----------------
|
||||||
|
@ -67,13 +66,13 @@ func TestAtlas_toChunk(t *testing.T) {
|
||||||
// 4 | 5 || 6 | 7
|
// 4 | 5 || 6 | 7
|
||||||
// ----------------
|
// ----------------
|
||||||
// 0 | 1 || 2 | 3
|
// 0 | 1 || 2 | 3
|
||||||
chunkID = a.toChunk(vector.Vector{X: 1, Y: 3})
|
chunkID = a.worldSpaceToChunk(vector.Vector{X: 1, Y: 3})
|
||||||
assert.Equal(t, 14, chunkID)
|
assert.Equal(t, 14, chunkID)
|
||||||
chunkID = a.toChunk(vector.Vector{X: 1, Y: -3})
|
chunkID = a.worldSpaceToChunk(vector.Vector{X: 1, Y: -3})
|
||||||
assert.Equal(t, 2, chunkID)
|
assert.Equal(t, 2, chunkID)
|
||||||
chunkID = a.toChunk(vector.Vector{X: -1, Y: -1})
|
chunkID = a.worldSpaceToChunk(vector.Vector{X: -1, Y: -1})
|
||||||
assert.Equal(t, 5, chunkID)
|
assert.Equal(t, 5, chunkID)
|
||||||
chunkID = a.toChunk(vector.Vector{X: -2, Y: 2})
|
chunkID = a.worldSpaceToChunk(vector.Vector{X: -2, Y: 2})
|
||||||
assert.Equal(t, 13, chunkID)
|
assert.Equal(t, 13, chunkID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +95,7 @@ func TestAtlas_Grown(t *testing.T) {
|
||||||
// Start with a small example
|
// Start with a small example
|
||||||
a := NewAtlas(2)
|
a := NewAtlas(2)
|
||||||
assert.NotNil(t, a)
|
assert.NotNil(t, a)
|
||||||
assert.Equal(t, 0, len(a.Chunks))
|
assert.Equal(t, 1, len(a.Chunks))
|
||||||
|
|
||||||
// Set a few tiles to values
|
// Set a few tiles to values
|
||||||
a.SetTile(vector.Vector{X: 0, Y: 0}, 1)
|
a.SetTile(vector.Vector{X: 0, Y: 0}, 1)
|
||||||
|
|
|
@ -54,3 +54,13 @@ func (v Vector) Divided(val int) Vector {
|
||||||
func (v Vector) Abs() Vector {
|
func (v Vector) Abs() Vector {
|
||||||
return Vector{maths.Abs(v.X), maths.Abs(v.Y)}
|
return Vector{maths.Abs(v.X), maths.Abs(v.Y)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Min returns the minimum values in both vectors
|
||||||
|
func Min(v1 Vector, v2 Vector) Vector {
|
||||||
|
return Vector{maths.Min(v1.X, v2.X), maths.Min(v1.Y, v2.Y)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Min returns the max values in both vectors
|
||||||
|
func Max(v1 Vector, v2 Vector) Vector {
|
||||||
|
return Vector{maths.Max(v1.X, v2.X), maths.Max(v1.Y, v2.Y)}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue