Added: Initial world generation tool
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
package field
|
||||
|
||||
import "math"
|
||||
|
||||
// The vertex convention, which every resample here obeys: a field of N samples a side spans N-1 quads, so
|
||||
// sample i sits at parameter i/(N-1) and the four corners are fixed points of any resize. Getting this wrong
|
||||
// shifts the whole map by half a cell per resize and the error compounds over a pipeline.
|
||||
|
||||
// Resample returns the field at a new resolution: block means when shrinking by an exact integer factor
|
||||
// (which is what the geology grid wants, and what preserves mass), bilinear otherwise. Ported from
|
||||
// heightmap_io.resample, which chose the same two paths for the same reasons.
|
||||
func (f *Field) Resample(w, h int) *Field {
|
||||
if w == f.W && h == f.H {
|
||||
return f.Clone()
|
||||
}
|
||||
cell := f.CellM * float64(f.W-1) / float64(w-1)
|
||||
if w < f.W && (f.W-1)%(w-1) == 0 && (f.H-1)%(h-1) == 0 && (f.W-1)/(w-1) == (f.H-1)/(h-1) {
|
||||
return f.blockMean((f.W-1)/(w-1), w, h, cell)
|
||||
}
|
||||
return f.bilinear(w, h, cell)
|
||||
}
|
||||
|
||||
// blockMean averages each factor x factor block of quads onto one output sample. The last row and column are
|
||||
// half-blocks under the vertex convention, which is why the accumulation counts what it actually summed.
|
||||
func (f *Field) blockMean(factor, w, h int, cell float64) *Field {
|
||||
out := New(w, h, cell)
|
||||
Rows(h, func(y0, y1 int) {
|
||||
for oy := y0; oy < y1; oy++ {
|
||||
for ox := 0; ox < w; ox++ {
|
||||
var sum float64
|
||||
var n int
|
||||
for dy := 0; dy < factor; dy++ {
|
||||
sy := oy*factor + dy - factor/2
|
||||
if sy < 0 || sy >= f.H {
|
||||
continue
|
||||
}
|
||||
for dx := 0; dx < factor; dx++ {
|
||||
sx := ox*factor + dx - factor/2
|
||||
if sx < 0 || sx >= f.W {
|
||||
continue
|
||||
}
|
||||
sum += float64(f.At(sx, sy))
|
||||
n++
|
||||
}
|
||||
}
|
||||
if n > 0 {
|
||||
out.Data[out.Idx(ox, oy)] = float32(sum / float64(n))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
func (f *Field) bilinear(w, h int, cell float64) *Field {
|
||||
out := New(w, h, cell)
|
||||
sx := float64(f.W-1) / float64(w-1)
|
||||
sy := float64(f.H-1) / float64(h-1)
|
||||
Rows(h, func(y0, y1 int) {
|
||||
for oy := y0; oy < y1; oy++ {
|
||||
fy := float64(oy) * sy
|
||||
iy := int(fy)
|
||||
ty := float32(fy - float64(iy))
|
||||
for ox := 0; ox < w; ox++ {
|
||||
fx := float64(ox) * sx
|
||||
ix := int(fx)
|
||||
tx := float32(fx - float64(ix))
|
||||
a := f.AtClamped(ix, iy)
|
||||
b := f.AtClamped(ix+1, iy)
|
||||
c := f.AtClamped(ix, iy+1)
|
||||
d := f.AtClamped(ix+1, iy+1)
|
||||
top := a + (b-a)*tx
|
||||
bot := c + (d-c)*tx
|
||||
out.Data[out.Idx(ox, oy)] = top + (bot-top)*ty
|
||||
}
|
||||
}
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
// UpsampleInt is the geology-to-detail step: an exact integer factor on the quad count, so 1786 at factor 4
|
||||
// becomes (1786-1)*4+1 = 7141 with every source sample landing exactly on an output sample and no resample
|
||||
// phase error at all. Catmull-Rom between them, which is the bicubic the spec asks for and does not overshoot
|
||||
// into ringing the way a plain cubic does on a ridge.
|
||||
func (f *Field) UpsampleInt(factor int) *Field {
|
||||
if factor <= 1 {
|
||||
return f.Clone()
|
||||
}
|
||||
w := (f.W-1)*factor + 1
|
||||
h := (f.H-1)*factor + 1
|
||||
out := New(w, h, f.CellM/float64(factor))
|
||||
inv := 1.0 / float64(factor)
|
||||
Rows(h, func(y0, y1 int) {
|
||||
for oy := y0; oy < y1; oy++ {
|
||||
sy := oy / factor
|
||||
ty := float64(oy%factor) * inv
|
||||
for ox := 0; ox < w; ox++ {
|
||||
sx := ox / factor
|
||||
tx := float64(ox%factor) * inv
|
||||
var col [4]float64
|
||||
for k := 0; k < 4; k++ {
|
||||
col[k] = catmullRom(
|
||||
float64(f.AtClamped(sx-1, sy-1+k)),
|
||||
float64(f.AtClamped(sx, sy-1+k)),
|
||||
float64(f.AtClamped(sx+1, sy-1+k)),
|
||||
float64(f.AtClamped(sx+2, sy-1+k)), tx)
|
||||
}
|
||||
out.Data[out.Idx(ox, oy)] = float32(catmullRom(col[0], col[1], col[2], col[3], ty))
|
||||
}
|
||||
}
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
func catmullRom(p0, p1, p2, p3, t float64) float64 {
|
||||
t2 := t * t
|
||||
t3 := t2 * t
|
||||
return 0.5 * ((2 * p1) +
|
||||
(-p0+p2)*t +
|
||||
(2*p0-5*p1+4*p2-p3)*t2 +
|
||||
(-p0+3*p1-3*p2+p3)*t3)
|
||||
}
|
||||
|
||||
// ToUnit squashes a field into [0, 1] against a percentile, optionally through log1p first: what the four
|
||||
// derivative maps (flow, wear, deposit) need before they become 8-bit PNGs. Ported from
|
||||
// heightmap_erosion.to_unit.
|
||||
func (f *Field) ToUnit(percentile float64, logScale bool) *Field {
|
||||
out := NewLike(f)
|
||||
for i, v := range f.Data {
|
||||
x := float64(v)
|
||||
if x < 0 {
|
||||
x = 0
|
||||
}
|
||||
if logScale {
|
||||
x = math.Log1p(x)
|
||||
}
|
||||
out.Data[i] = float32(x)
|
||||
}
|
||||
top := float64(out.Percentile(percentile))
|
||||
if top < 1e-6 {
|
||||
top = 1e-6
|
||||
}
|
||||
for i, v := range out.Data {
|
||||
x := float64(v) / top
|
||||
if x > 1 {
|
||||
x = 1
|
||||
}
|
||||
out.Data[i] = float32(x)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Sub extracts a sub-rectangle given in map coordinates (x0, y0, x1, y1 in 0..1), at the source resolution.
|
||||
// Used by the preview to look at a piece of the map closely, which is the only way to judge whether hill
|
||||
// country reads as hill country rather than as small mountains.
|
||||
func (f *Field) Sub(crop [4]float64) *Field {
|
||||
clamp := func(v float64) float64 {
|
||||
if v < 0 {
|
||||
return 0
|
||||
}
|
||||
if v > 1 {
|
||||
return 1
|
||||
}
|
||||
return v
|
||||
}
|
||||
x0 := int(clamp(crop[0]) * float64(f.W-1))
|
||||
y0 := int(clamp(crop[1]) * float64(f.H-1))
|
||||
x1 := int(clamp(crop[2]) * float64(f.W-1))
|
||||
y1 := int(clamp(crop[3]) * float64(f.H-1))
|
||||
if x1 <= x0 {
|
||||
x1 = x0 + 1
|
||||
}
|
||||
if y1 <= y0 {
|
||||
y1 = y0 + 1
|
||||
}
|
||||
w, h := x1-x0+1, y1-y0+1
|
||||
out := New(w, h, f.CellM)
|
||||
for y := 0; y < h; y++ {
|
||||
copy(out.Data[y*w:(y+1)*w], f.Data[(y0+y)*f.W+x0:(y0+y)*f.W+x0+w])
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user