Skip to content

Instantly share code, notes, and snippets.

@nicklanng
Last active August 10, 2020 18:17
Show Gist options
  • Save nicklanng/e750d19b3e8d40cff63cec47e10d1d43 to your computer and use it in GitHub Desktop.
Save nicklanng/e750d19b3e8d40cff63cec47e10d1d43 to your computer and use it in GitHub Desktop.
package main
import (
"fmt"
"image"
"image/color"
"image/png"
"math"
"os"
"github.com/ojrac/opensimplex-go"
)
const (
chunkWidth = 32 * 32 * 5
chunkHeight = 32 * 32
)
func main() {
// this is our heightmap in a 1d slice
tiles := make([]byte, chunkWidth*chunkHeight)
// init the noise
// change this seed value to draw different maps
seed := int64(1565378300)
n := opensimplex.NewNormalized(seed)
n2 := opensimplex.New(seed)
// create a noise function - the variables at the top of this bit are just for configuring more detailed maps later
maxWidth := float64(chunkWidth)
maxHeight := float64(chunkHeight)
startX := float64(0)
startY := float64(0)
f := func(x, y, octave float64, n opensimplex.Noise) float64 {
return n.Eval3(octave*math.Sin((x/maxWidth)*math.Pi*2), octave*math.Cos((x/maxWidth)*math.Pi*2), octave*math.Pi*0.5*y/maxHeight)
}
// for every tile in the map
var i int
for y := 0; y < chunkHeight; y++ {
fmt.Printf("%f%%\n", (float64(i)/float64(chunkWidth*chunkHeight))*100)
for x := 0; x < chunkWidth; x++ {
nx := float64(x)
ny := float64(y)
// here we do a bunch of operations like adding different octaves of noise, raising to powers to make slopes sharper etc...
// playing with these values is how you sculpt the final terrain crinkliness
val := f(nx+startX, maxHeight-(ny+startY), 1.1, n)
val *= f(nx+startX, maxHeight-(ny+startY), 1.51, n)
val *= f(nx+startX, maxHeight-(ny+startY), 1.7, n) * 0.5
val += f(nx+startX, maxHeight-(ny+startY), 5, n) * 0.1
val += f(nx+startX, maxHeight-(ny+startY), 9, n) * 0.05
val += f(nx+startX, maxHeight-(ny+startY), 25, n2) * 0.01
val *= val * val * val
val += f(nx+startX, maxHeight-(ny+startY), 1.1, n)*f(nx+startX, maxHeight-(ny+startY), 1.51, n)*f(nx+startX, maxHeight-(ny+startY), 1.7, n)*0.5 + f(nx+startX, maxHeight-(ny+startY), 5, n)*0.1
val += f(nx+startX, maxHeight-(ny+startY), 9, n) * 0.05
val += f(nx+startX, maxHeight-(ny+startY), 25, n2) * 0.01
val += f(nx+startX, maxHeight-(ny+startY), 50, n2) * 0.01
val += f(nx+startX, maxHeight-(ny+startY), 100, n2) * 0.005
val += f(nx+startX, maxHeight-(ny+startY), 200, n2) * 0.005
val += f(nx+startX, maxHeight-(ny+startY), 500, n2) * 0.002
val += f(nx+startX, maxHeight-(ny+startY), 1000, n2) * 0.001
val += math.Exp2(val) * val
// convert to a byte
byt := val * 255
if byt > 255 {
byt = 255
}
tiles[i] = byte(byt)
i++
}
}
// draw an image of the heightmap - here we're using colors to show the type of terrain
// but you could just do this
// color.RGBA{tiles[i], tiles[i], tiles[i], 0xff})
img := image.NewRGBA(image.Rectangle{Max: image.Point{X: chunkWidth, Y: chunkHeight}})
i = 0
for y := 0; y < chunkHeight; y++ {
for x := 0; x < chunkWidth; x++ {
switch {
// deeper water
case tiles[i] < 63: // water 3
img.Set(x, y, color.RGBA{0x1d, 0x97, 0xc1, 0xff})
case tiles[i] < 68: // water 2
img.Set(x, y, color.RGBA{0x53, 0xc7, 0xf0, 0xff})
case tiles[i] < 69: // water 1
img.Set(x, y, color.RGBA{0x87, 0xe0, 0xff, 0xff})
case tiles[i] < 70: // sand 1
img.Set(x, y, color.RGBA{0xe6, 0xd6, 0xc4, 0xff})
case tiles[i] < 71: // sand 2
img.Set(x, y, color.RGBA{0xdb, 0xc9, 0xb3, 0xff})
case tiles[i] < 81: // grass 1
img.Set(x, y, color.RGBA{0x62, 0x9b, 0x31, 0xff})
case tiles[i] < 97: // grass 2
img.Set(x, y, color.RGBA{0x4a, 0x73, 0x26, 0xff})
case tiles[i] < 128: // grass 3
img.Set(x, y, color.RGBA{0x31, 0x4c, 0x1a, 0xff})
case tiles[i] < 154: // rock 1
img.Set(x, y, color.RGBA{0x3e, 0x37, 0x3e, 0xff})
case tiles[i] < 178: // rock 2
img.Set(x, y, color.RGBA{0x4f, 0x45, 0x49, 0xff})
default: // rock 3
img.Set(x, y, color.RGBA{0x62, 0x5b, 0x5b, 0xff})
}
i++
}
}
file, _ := os.Create("out.png")
if err := png.Encode(file, img); err != nil {
panic(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment