Created
November 5, 2012 01:18
-
-
Save s-l-teichmann/4014677 to your computer and use it in GitHub Desktop.
Fills a LevelDB with generated terrain chunks.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// terrainstore.go | |
// --------------- | |
// | |
// Fills a LevelDB with generated terrain chunks. | |
// | |
// (c) 2012 by Sascha L. Teichmann | |
// | |
package main | |
import ( | |
"bitbucket.org/s_l_teichmann/simplexnoise" | |
"bytes" | |
"encoding/binary" | |
"flag" | |
leveldb "github.com/jmhodges/levigo" | |
//leveldb "github.com/jmhodges/levigo_leveldb_1.4" | |
"log" | |
"runtime" | |
) | |
const ( | |
BITS = uint32(20) | |
ONE32 = uint32(1) | |
ZERO32 = uint32(0) | |
HI_MASK = ONE32 << (BITS + ONE32) | |
ZERO64 = uint64(0) | |
ONE64 = uint64(1) | |
WORLD_MID = ^(^0 << 19) | |
) | |
type PUGeneratorOptions struct { | |
Seed int64 | |
Step float64 | |
PUSize int | |
Thresh float64 | |
PT float64 | |
PB float64 | |
} | |
type PUGenerator struct { | |
noise *simplexnoise.SimplexNoise | |
Options *PUGeneratorOptions | |
} | |
type Pos struct { | |
X, Y, Z int | |
} | |
type PersistenceUnit struct { | |
Pos | |
data []byte | |
} | |
func (p *Pos) ZCode() uint64 { | |
return ZEncode( | |
uint32(p.X+WORLD_MID), | |
uint32(p.Y+WORLD_MID), | |
uint32(p.Z+WORLD_MID)) | |
} | |
func NewPersistenceUnit(x, y, z int, data []byte) *PersistenceUnit { | |
return &PersistenceUnit{Pos{x, y, z}, data} | |
} | |
func (pu *PersistenceUnit) Data() []byte { | |
return pu.data | |
} | |
type RLEncoder struct { | |
buf *bytes.Buffer | |
last byte | |
count int | |
} | |
func NewRLEncoder() *RLEncoder { | |
return &RLEncoder{new(bytes.Buffer), 0, 0} | |
} | |
func (rle *RLEncoder) Reset() { | |
rle.buf.Reset() | |
rle.last = 0 | |
rle.count = 0 | |
} | |
func (rle *RLEncoder) WriteByte(b byte) { | |
if rle.last == b { | |
rle.count++ | |
} else { | |
rle.Flush() | |
rle.last = b | |
rle.count = 1 | |
} | |
} | |
func (rle *RLEncoder) Flush() { | |
if rle.count > 0 { | |
for ; rle.count > 127; rle.count -= 127 { | |
rle.buf.WriteByte(0xff) | |
rle.buf.WriteByte(rle.last) | |
} | |
if rle.count == 1 { | |
if (rle.last & (1 << 7)) != 0 { | |
rle.buf.WriteByte((1 << 7) | 1) | |
} | |
rle.buf.WriteByte(rle.last) | |
} else { | |
rle.buf.WriteByte((1 << 7) | byte(rle.count)) | |
rle.buf.WriteByte(rle.last) | |
} | |
rle.count = 0 | |
} | |
} | |
func (rle *RLEncoder) Bytes() []byte { | |
return rle.buf.Bytes() | |
} | |
type Backend struct { | |
db *leveldb.DB | |
PUChannel chan *PersistenceUnit | |
} | |
func NewBackend(path string) (*Backend, error) { | |
opts := leveldb.NewOptions() | |
opts.SetCreateIfMissing(true) | |
db, err := leveldb.Open(path, opts) | |
if err != nil { | |
return nil, err | |
} | |
pu_chan := make(chan *PersistenceUnit, 32) | |
return &Backend{db, pu_chan}, nil | |
} | |
func (p *Backend) Close() { | |
log.Println("closing persistence layer") | |
p.db.Close() | |
close(p.PUChannel) | |
} | |
func (b *Backend) Store(done chan bool, worker int) { | |
wo := leveldb.NewWriteOptions() | |
for { | |
unit := <-b.PUChannel | |
if unit == nil { | |
worker-- | |
if worker <= 0 { | |
break | |
} | |
continue | |
} | |
data := unit.Data() | |
l := len(data) | |
zcode := unit.ZCode() | |
log.Printf("Store (%d, %d, %d) size = %d, code = %x\n", | |
unit.X, unit.Y, unit.Z, l, zcode) | |
key := ZCodeAsKey(zcode) | |
b.db.Put(wo, key, data) | |
} | |
done <- true | |
} | |
func NewPUGenerator(options *PUGeneratorOptions) *PUGenerator { | |
noise := simplexnoise.NewSimplexNoise(options.Seed) | |
return &PUGenerator{noise, options} | |
} | |
func (tg *PUGenerator) generatePU(p *Pos, pt, pb float64, rle *RLEncoder) { | |
size := tg.Options.PUSize | |
step := tg.Options.Step | |
x_start := float64(p.X*size) * step | |
y_start := float64(p.Y*size) * step | |
z_start := float64(p.Z*size) * step | |
// pt = 0*m + b <=> b = pt | |
// pb = (size-1)*m + pt | |
// m = (pb-pt)/(size-1) | |
b := float64(pt) | |
var m float64 | |
if size == 1 { | |
m = 0 | |
} else { | |
m = float64(pb-pt) / float64(size-1) | |
} | |
noise := tg.noise | |
thresh := tg.Options.Thresh | |
y_pos := y_start | |
for y := 0; y < size; y++ { | |
prob := m*float64(y) + b | |
z_pos := z_start | |
for z := 0; z < size; z++ { | |
x_pos := x_start | |
for x := 0; x < size; x++ { | |
n := (noise.Noise3D(x_pos, y_pos, z_pos) + 1.0) * 0.5 * prob | |
var v byte | |
if n < thresh { | |
v = 0 | |
} else { | |
v = 1 | |
} | |
rle.WriteByte(v) | |
x_pos += step | |
} | |
z_pos += step | |
} | |
y_pos += step | |
} | |
rle.Flush() | |
} | |
func (tg *PUGenerator) Generate(jobs chan *Pos, units chan *PersistenceUnit) { | |
for { | |
p := <-jobs | |
if p == nil { | |
break | |
} | |
log.Printf("Generate (%d, %d, %d)\n", p.X, p.Y, p.Z) | |
if p.Y <= 0 { // Do not generate anything when above the ground. | |
var pt, pb float64 | |
if p.Y == 0 { | |
// generate surface | |
pt, pb = tg.Options.PT, tg.Options.PB | |
} else { | |
// generate undergroud | |
pt, pb = 1.0, 1.0 | |
} | |
rle := NewRLEncoder() | |
tg.generatePU(p, pt, pb, rle) | |
units <- NewPersistenceUnit(p.X, p.Y, p.Z, rle.Bytes()) | |
} | |
} | |
units <- nil | |
} | |
func ZEncode(x, y, z uint32) uint64 { | |
code, p := ZERO64, ONE64 | |
for mask := ONE32; mask != HI_MASK; mask <<= 1 { | |
if (x & mask) != 0 { | |
code |= p | |
} | |
p <<= 1 | |
if (y & mask) != 0 { | |
code |= p | |
} | |
p <<= 1 | |
if (z & mask) != 0 { | |
code |= p | |
} | |
p <<= 1 | |
} | |
return code | |
} | |
func ZDecode(code uint64) (x, y, z uint32) { | |
p := ONE64 | |
x, y, z = ZERO32, ZERO32, ZERO32 | |
for mask := ONE32; mask != HI_MASK; mask <<= 1 { | |
if (code & p) != 0 { | |
x |= mask | |
} | |
p <<= 1 | |
if (code & p) != 0 { | |
y |= mask | |
} | |
p <<= 1 | |
if (code & p) != 0 { | |
z |= mask | |
} | |
p <<= 1 | |
} | |
return | |
} | |
func ZCodeAsKey(code uint64) []byte { | |
var b bytes.Buffer // XXX: Is there a better way todo this? | |
binary.Write(&b, binary.BigEndian, &code) | |
return b.Bytes() | |
} | |
func main() { | |
var ( | |
tgo PUGeneratorOptions | |
db string | |
x_size int | |
y_size int | |
z_size int | |
x_pos int | |
y_pos int | |
z_pos int | |
worker int | |
) | |
flag.StringVar(&db, "db", "terrain.db", "World database path") | |
flag.IntVar(&x_size, "xsize", 512, "number of units in x direction") | |
flag.IntVar(&y_size, "ysize", 512, "number of units in y direction") | |
flag.IntVar(&z_size, "zsize", 3, "number of units in z direction") | |
flag.IntVar(&x_pos, "xpos", 0, "position in x direction") | |
flag.IntVar(&y_pos, "ypos", 0, "position in y direction") | |
flag.IntVar(&z_pos, "zpos", 0, "position in z direction") | |
flag.Int64Var(&tgo.Seed, "seed", 1, "random seed") | |
flag.Float64Var(&tgo.Step, "step", 1.0/128.0, "step width in noise range") | |
flag.Float64Var(&tgo.Thresh, "thresh", 0.3, "threshold to generate holes") | |
flag.IntVar(&tgo.PUSize, "pusize", 64, "size of PUs") | |
flag.IntVar(&worker, "worker", runtime.NumCPU(), "number of workers") | |
flag.Float64Var(&tgo.PT, "pt", 0.0, "probability top") | |
flag.Float64Var(&tgo.PB, "pb", 1.0, "probability bottom") | |
flag.Parse() | |
backend, err := NewBackend(db) | |
if err != nil { | |
log.Fatal("Cannot open world database.", err) | |
} | |
defer backend.Close() | |
tg := NewPUGenerator(&tgo) | |
done_chan := make(chan bool) | |
go backend.Store(done_chan, worker) | |
work_chan := make(chan *Pos) | |
for w := 0; w < worker; w++ { | |
go tg.Generate(work_chan, backend.PUChannel) | |
} | |
for z := 0; z < z_size; z++ { | |
zp := z + z_pos | |
for y := 0; y < y_size; y++ { | |
yp := y + y_pos | |
for x := 0; x < x_size; x++ { | |
xp := x + x_pos | |
work_chan <- &Pos{xp, yp, zp} | |
} | |
} | |
} | |
for w := 0; w < worker; w++ { | |
work_chan <- nil | |
} | |
<-done_chan | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment