From 10959ef7266018ff3bee284c97f82318fc4e9be1 Mon Sep 17 00:00:00 2001 From: Marc Di Luzio <marc.diluzio@gmail.com> Date: Wed, 8 Jul 2020 19:40:15 +0100 Subject: [PATCH 1/5] Refactor populate to be an Atlas function This simplifies usage greatly --- pkg/atlas/atlas.go | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/pkg/atlas/atlas.go b/pkg/atlas/atlas.go index 1c194af..209d4b7 100644 --- a/pkg/atlas/atlas.go +++ b/pkg/atlas/atlas.go @@ -59,7 +59,7 @@ func NewAtlas(chunkSize int) Atlas { UpperBound: vector.Vector{X: chunkSize, Y: chunkSize}, } // Initialise the first chunk - a.Chunks[0].populate(chunkSize) + a.populate(0) return a } @@ -81,11 +81,8 @@ func (a *Atlas) SetObject(v vector.Vector, obj objects.Object) { func (a *Atlas) QueryPosition(v vector.Vector) (byte, objects.Object) { c := a.worldSpaceToChunkWithGrow(v) local := a.worldSpaceToChunkLocal(v) + a.populate(c) chunk := a.Chunks[c] - if chunk.Tiles == nil { - chunk.populate(a.ChunkSize) - a.Chunks[c] = chunk - } i := a.chunkTileIndex(local) return chunk.Tiles[i], chunk.Objects[i] } @@ -96,8 +93,13 @@ func (a *Atlas) chunkTileIndex(local vector.Vector) int { } // populate will fill a chunk with data -func (c *Chunk) populate(size int) { - c.Tiles = make([]byte, size*size) +func (a *Atlas) populate(chunk int) { + c := a.Chunks[chunk] + if c.Tiles != nil { + return + } + + c.Tiles = make([]byte, a.ChunkSize*a.ChunkSize) c.Objects = make(map[int]objects.Object) // Set up the tiles @@ -117,26 +119,23 @@ func (c *Chunk) populate(size int) { c.Objects[i] = objects.Object{Type: objects.SmallRock} } } + + a.Chunks[chunk] = c } // setTile sets a tile in a specific chunk func (a *Atlas) setTile(chunk int, local vector.Vector, tile byte) { + a.populate(chunk) c := a.Chunks[chunk] - if c.Tiles == nil { - c.populate(a.ChunkSize) - } - c.Tiles[a.chunkTileIndex(local)] = tile a.Chunks[chunk] = c } // setObject sets an object in a specific chunk func (a *Atlas) setObject(chunk int, local vector.Vector, object objects.Object) { - c := a.Chunks[chunk] - if c.Tiles == nil { - c.populate(a.ChunkSize) - } + a.populate(chunk) + c := a.Chunks[chunk] i := a.chunkTileIndex(local) if object.Type != objects.None { c.Objects[i] = object From ed9ecef80a8c6d834d9bf779fc6d53f2298bc2c5 Mon Sep 17 00:00:00 2001 From: Marc Di Luzio <marc.diluzio@gmail.com> Date: Wed, 8 Jul 2020 20:10:28 +0100 Subject: [PATCH 2/5] Add perlin based generation for the terrain tiles --- go.mod | 1 + go.sum | 2 ++ pkg/atlas/atlas.go | 29 +++++++++++++++++++++++------ pkg/atlas/atlas_test.go | 24 ++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 2165ade..6c4320a 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/mdiluz/rove go 1.14 require ( + github.com/aquilax/go-perlin v0.0.0-20191229124216-0af9ce917c28 github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.4.2 github.com/google/uuid v1.1.1 diff --git a/go.sum b/go.sum index 029b75f..e579bf8 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/aquilax/go-perlin v0.0.0-20191229124216-0af9ce917c28 h1:iQUvYFmTKLXaDf3N0YfsJG5vgVtA1La82fHFDkpX5y4= +github.com/aquilax/go-perlin v0.0.0-20191229124216-0af9ce917c28/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU= diff --git a/pkg/atlas/atlas.go b/pkg/atlas/atlas.go index 209d4b7..c6bcc37 100644 --- a/pkg/atlas/atlas.go +++ b/pkg/atlas/atlas.go @@ -4,6 +4,7 @@ import ( "log" "math/rand" + "github.com/aquilax/go-perlin" "github.com/mdiluz/rove/pkg/maths" "github.com/mdiluz/rove/pkg/objects" "github.com/mdiluz/rove/pkg/vector" @@ -47,6 +48,9 @@ type Atlas struct { // ChunkSize is the x/y dimensions of each square chunk ChunkSize int `json:"chunksize"` + + // perlin is the current perlin noise generator + perlin *perlin.Perlin } // NewAtlas creates a new empty atlas @@ -57,6 +61,7 @@ func NewAtlas(chunkSize int) Atlas { Chunks: make([]Chunk, 1), LowerBound: vector.Vector{X: 0, Y: 0}, UpperBound: vector.Vector{X: chunkSize, Y: chunkSize}, + perlin: perlin.NewPerlin(2, 2, 3, 100), } // Initialise the first chunk a.populate(0) @@ -102,12 +107,23 @@ func (a *Atlas) populate(chunk int) { c.Tiles = make([]byte, a.ChunkSize*a.ChunkSize) c.Objects = make(map[int]objects.Object) - // Set up the tiles - for i := 0; i < len(c.Tiles); i++ { - if rand.Intn(3) == 0 { - c.Tiles[i] = byte(TileRock) - } else { - c.Tiles[i] = byte(TileSand) + origin := a.chunkOriginInWorldSpace(chunk) + for i := 0; i < a.ChunkSize; i++ { + for j := 0; j < a.ChunkSize; j++ { + + // Get the perlin noise value for this location + pl := a.perlin.Noise2D(float64(origin.X+i)/10, float64(origin.Y+j)/10) + + // Choose a tile based on the perlin noise value + var tile Tile + switch { + case pl > 0.1: + tile = TileSand + default: + tile = TileRock + } + + c.Tiles[j*a.ChunkSize+i] = byte(tile) } } @@ -217,6 +233,7 @@ func (a *Atlas) worldSpaceToChunkWithGrow(v vector.Vector) int { LowerBound: lower, UpperBound: upper, Chunks: make([]Chunk, size.X*size.Y), + perlin: a.perlin, } // Log that we're resizing diff --git a/pkg/atlas/atlas_test.go b/pkg/atlas/atlas_test.go index 60c3922..dedf4b1 100644 --- a/pkg/atlas/atlas_test.go +++ b/pkg/atlas/atlas_test.go @@ -1,6 +1,7 @@ package atlas import ( + "fmt" "testing" "github.com/mdiluz/rove/pkg/objects" @@ -248,3 +249,26 @@ func TestAtlas_GetSetCorrect(t *testing.T) { } } } + +func TestAtlas_WorldGen(t *testing.T) { + a := NewAtlas(8) + // Spawn a large world + _, _ = a.QueryPosition(vector.Vector{X: 20, Y: 20}) + + // Print out the world for manual evaluation + num := 20 + for j := num - 1; j >= 0; j-- { + for i := 0; i < num; i++ { + t, o := a.QueryPosition(vector.Vector{X: i, Y: j}) + if o.Type != objects.None { + fmt.Printf("%c", o.Type) + } else if t != byte(TileNone) { + fmt.Printf("%c", t) + } else { + fmt.Printf(" ") + } + + } + fmt.Print("\n") + } +} From 7b4541716abaf4db3eb2365a631a32ddcd91121d Mon Sep 17 00:00:00 2001 From: Marc Di Luzio <marc.diluzio@gmail.com> Date: Wed, 8 Jul 2020 23:45:52 +0100 Subject: [PATCH 3/5] Add gravel tiles --- pkg/atlas/atlas.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/atlas/atlas.go b/pkg/atlas/atlas.go index c6bcc37..144ce93 100644 --- a/pkg/atlas/atlas.go +++ b/pkg/atlas/atlas.go @@ -18,10 +18,13 @@ const ( TileNone = Tile(0) // TileRock is solid rock ground - TileRock = Tile('.') + TileRock = Tile('-') + + // TileGravel is loose rocks + TileGravel = Tile(':') // TileSand is sand - TileSand = Tile(',') + TileSand = Tile('~') ) // Chunk represents a fixed square grid of tiles @@ -112,12 +115,14 @@ func (a *Atlas) populate(chunk int) { for j := 0; j < a.ChunkSize; j++ { // Get the perlin noise value for this location - pl := a.perlin.Noise2D(float64(origin.X+i)/10, float64(origin.Y+j)/10) + pl := a.perlin.Noise2D(float64(origin.X+i)/15, float64(origin.Y+j)/15) // Choose a tile based on the perlin noise value var tile Tile switch { - case pl > 0.1: + case pl > 0.2: + tile = TileGravel + case pl > 0.05: tile = TileSand default: tile = TileRock From 4b715bdff3684eab0426932b6a48aa3110ed0634 Mon Sep 17 00:00:00 2001 From: Marc Di Luzio <marc.diluzio@gmail.com> Date: Wed, 8 Jul 2020 23:58:11 +0100 Subject: [PATCH 4/5] Move to OpenSimplex noise Apart from other benefits, this produces much nicer direction agnostic noise --- go.mod | 2 +- go.sum | 4 ++-- pkg/atlas/atlas.go | 18 +++++++++++------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 6c4320a..db01c84 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module github.com/mdiluz/rove go 1.14 require ( - github.com/aquilax/go-perlin v0.0.0-20191229124216-0af9ce917c28 github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/protobuf v1.4.2 github.com/google/uuid v1.1.1 github.com/grpc-ecosystem/grpc-gateway v1.14.6 github.com/kr/pretty v0.1.0 // indirect + github.com/ojrac/opensimplex-go v1.0.1 github.com/robfig/cron v1.2.0 github.com/stretchr/testify v1.6.0 golang.org/x/net v0.0.0-20200602114024-627f9648deb9 diff --git a/go.sum b/go.sum index e579bf8..a2bd9a2 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,6 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/aquilax/go-perlin v0.0.0-20191229124216-0af9ce917c28 h1:iQUvYFmTKLXaDf3N0YfsJG5vgVtA1La82fHFDkpX5y4= -github.com/aquilax/go-perlin v0.0.0-20191229124216-0af9ce917c28/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU= @@ -52,6 +50,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/ojrac/opensimplex-go v1.0.1 h1:XslvpLP6XqQSATUtsOnGBYtFPw7FQ6h6y0ihjVeOLHo= +github.com/ojrac/opensimplex-go v1.0.1/go.mod h1:MoSgj04tZpH8U0RefZabnHV2AbLgv/2mo3hLJtWqSEs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= diff --git a/pkg/atlas/atlas.go b/pkg/atlas/atlas.go index 144ce93..0526488 100644 --- a/pkg/atlas/atlas.go +++ b/pkg/atlas/atlas.go @@ -4,10 +4,10 @@ import ( "log" "math/rand" - "github.com/aquilax/go-perlin" "github.com/mdiluz/rove/pkg/maths" "github.com/mdiluz/rove/pkg/objects" "github.com/mdiluz/rove/pkg/vector" + "github.com/ojrac/opensimplex-go" ) // Tile describes the type of terrain @@ -52,10 +52,14 @@ type Atlas struct { // ChunkSize is the x/y dimensions of each square chunk ChunkSize int `json:"chunksize"` - // perlin is the current perlin noise generator - perlin *perlin.Perlin + // noise is an OpenSimplex noise generator + noise opensimplex.Noise } +const ( + noiseSeed = 1024 +) + // NewAtlas creates a new empty atlas func NewAtlas(chunkSize int) Atlas { // Start up with one chunk @@ -64,7 +68,7 @@ func NewAtlas(chunkSize int) Atlas { Chunks: make([]Chunk, 1), LowerBound: vector.Vector{X: 0, Y: 0}, UpperBound: vector.Vector{X: chunkSize, Y: chunkSize}, - perlin: perlin.NewPerlin(2, 2, 3, 100), + noise: opensimplex.New(noiseSeed), } // Initialise the first chunk a.populate(0) @@ -115,12 +119,12 @@ func (a *Atlas) populate(chunk int) { for j := 0; j < a.ChunkSize; j++ { // Get the perlin noise value for this location - pl := a.perlin.Noise2D(float64(origin.X+i)/15, float64(origin.Y+j)/15) + pl := a.noise.Eval2(float64(origin.X+i)/6, float64(origin.Y+j)/6) // Choose a tile based on the perlin noise value var tile Tile switch { - case pl > 0.2: + case pl > 0.5: tile = TileGravel case pl > 0.05: tile = TileSand @@ -238,7 +242,7 @@ func (a *Atlas) worldSpaceToChunkWithGrow(v vector.Vector) int { LowerBound: lower, UpperBound: upper, Chunks: make([]Chunk, size.X*size.Y), - perlin: a.perlin, + noise: a.noise, } // Log that we're resizing From 9682cfa7ea331c265b45c516b32a5ea207bbbf20 Mon Sep 17 00:00:00 2001 From: Marc Di Luzio <marc.diluzio@gmail.com> Date: Thu, 9 Jul 2020 00:04:46 +0100 Subject: [PATCH 5/5] Spawn objects using OpenSimplex noise as well --- pkg/atlas/atlas.go | 57 ++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/pkg/atlas/atlas.go b/pkg/atlas/atlas.go index 0526488..dd618e7 100644 --- a/pkg/atlas/atlas.go +++ b/pkg/atlas/atlas.go @@ -52,23 +52,29 @@ type Atlas struct { // ChunkSize is the x/y dimensions of each square chunk ChunkSize int `json:"chunksize"` - // noise is an OpenSimplex noise generator - noise opensimplex.Noise + // terrainNoise describes the noise function for the terrain + terrainNoise opensimplex.Noise + + // terrainNoise describes the noise function for the terrain + objectNoise opensimplex.Noise } const ( - noiseSeed = 1024 + noiseSeed = 1024 + terrainNoiseScale = 6 + objectNoiseScale = 3 ) // NewAtlas creates a new empty atlas func NewAtlas(chunkSize int) Atlas { // Start up with one chunk a := Atlas{ - ChunkSize: chunkSize, - Chunks: make([]Chunk, 1), - LowerBound: vector.Vector{X: 0, Y: 0}, - UpperBound: vector.Vector{X: chunkSize, Y: chunkSize}, - noise: opensimplex.New(noiseSeed), + ChunkSize: chunkSize, + Chunks: make([]Chunk, 1), + LowerBound: vector.Vector{X: 0, Y: 0}, + UpperBound: vector.Vector{X: chunkSize, Y: chunkSize}, + terrainNoise: opensimplex.New(noiseSeed), + objectNoise: opensimplex.New(noiseSeed), } // Initialise the first chunk a.populate(0) @@ -118,21 +124,31 @@ func (a *Atlas) populate(chunk int) { for i := 0; i < a.ChunkSize; i++ { for j := 0; j < a.ChunkSize; j++ { - // Get the perlin noise value for this location - pl := a.noise.Eval2(float64(origin.X+i)/6, float64(origin.Y+j)/6) - - // Choose a tile based on the perlin noise value + // Get the terrain noise value for this location + t := a.terrainNoise.Eval2(float64(origin.X+i)/terrainNoiseScale, float64(origin.Y+j)/terrainNoiseScale) var tile Tile switch { - case pl > 0.5: + case t > 0.5: tile = TileGravel - case pl > 0.05: + case t > 0.05: tile = TileSand default: tile = TileRock } - c.Tiles[j*a.ChunkSize+i] = byte(tile) + + // Get the object noise value for this location + o := a.objectNoise.Eval2(float64(origin.X+i)/objectNoiseScale, float64(origin.Y+j)/objectNoiseScale) + var obj = objects.None + switch { + case o > 0.6: + obj = objects.LargeRock + case o > 0.5: + obj = objects.SmallRock + } + if obj != objects.None { + c.Objects[j*a.ChunkSize+i] = objects.Object{Type: obj} + } } } @@ -238,11 +254,12 @@ func (a *Atlas) worldSpaceToChunkWithGrow(v vector.Vector) int { // Create the new empty atlas newAtlas := Atlas{ - ChunkSize: a.ChunkSize, - LowerBound: lower, - UpperBound: upper, - Chunks: make([]Chunk, size.X*size.Y), - noise: a.noise, + ChunkSize: a.ChunkSize, + LowerBound: lower, + UpperBound: upper, + Chunks: make([]Chunk, size.X*size.Y), + terrainNoise: a.terrainNoise, + objectNoise: a.objectNoise, } // Log that we're resizing