Vanilla-faithful overworld generator (final_density + multi-noise biomes), full connection lifecycle (status/login/configuration/play), chunk streaming, creative block editing, and the protocol/nbt/registry infrastructure.
250 lines
6.9 KiB
Go
250 lines
6.9 KiB
Go
package worldgen
|
|
|
|
import "math"
|
|
|
|
// FunctionContext is the sample point for a density function (block coords).
|
|
// During chunk generation, interp holds the precomputed cell-interpolated value
|
|
// for each Interpolated node (indexed by node); it is nil for plain evaluation.
|
|
type FunctionContext struct {
|
|
X, Y, Z float64
|
|
interp []float64
|
|
}
|
|
|
|
// WithInterp returns a copy of c carrying the given per-node interpolated values.
|
|
func (c FunctionContext) WithInterp(v []float64) FunctionContext {
|
|
c.interp = v
|
|
return c
|
|
}
|
|
|
|
// Interpolated marks a sub-function that vanilla samples on the cell-corner grid
|
|
// and trilinearly interpolates (the heavy 3D terrain noise). During generation
|
|
// the value is looked up by Index; otherwise the inner function is evaluated.
|
|
type Interpolated struct {
|
|
Inner DensityFunction
|
|
Index int
|
|
}
|
|
|
|
func (n *Interpolated) Compute(c FunctionContext) float64 {
|
|
if c.interp != nil {
|
|
return c.interp[n.Index]
|
|
}
|
|
return n.Inner.Compute(c)
|
|
}
|
|
|
|
// DensityFunction is a node in the density-function tree. Compute returns the
|
|
// density at the given point; positive conventionally means "solid".
|
|
//
|
|
// This is the interpreter engine; only the node types we currently need are
|
|
// implemented. The full vanilla set (splines, blend_density, caches, etc.) can
|
|
// be added incrementally without changing this interface.
|
|
type DensityFunction interface {
|
|
Compute(c FunctionContext) float64
|
|
}
|
|
|
|
// Constant is a fixed value.
|
|
type Constant float64
|
|
|
|
func (c Constant) Compute(FunctionContext) float64 { return float64(c) }
|
|
|
|
type binaryOp struct {
|
|
a, b DensityFunction
|
|
op func(x, y float64) float64
|
|
}
|
|
|
|
func (n binaryOp) Compute(c FunctionContext) float64 { return n.op(n.a.Compute(c), n.b.Compute(c)) }
|
|
|
|
// Add, Mul, Min, Max combine two density functions pointwise.
|
|
func Add(a, b DensityFunction) DensityFunction {
|
|
return binaryOp{a, b, func(x, y float64) float64 { return x + y }}
|
|
}
|
|
func Mul(a, b DensityFunction) DensityFunction {
|
|
return binaryOp{a, b, func(x, y float64) float64 { return x * y }}
|
|
}
|
|
func Min(a, b DensityFunction) DensityFunction {
|
|
return binaryOp{a, b, func(x, y float64) float64 {
|
|
if x < y {
|
|
return x
|
|
}
|
|
return y
|
|
}}
|
|
}
|
|
func Max(a, b DensityFunction) DensityFunction {
|
|
return binaryOp{a, b, func(x, y float64) float64 {
|
|
if x > y {
|
|
return x
|
|
}
|
|
return y
|
|
}}
|
|
}
|
|
|
|
// YClampedGradient is the y_clamped_gradient node: a linear map of Y from
|
|
// [fromY, toY] onto [fromV, toV], clamped outside that range.
|
|
type YClampedGradient struct {
|
|
FromY, ToY, FromV, ToV float64
|
|
}
|
|
|
|
func (g YClampedGradient) Compute(c FunctionContext) float64 {
|
|
return clampedMap(c.Y, g.FromY, g.ToY, g.FromV, g.ToV)
|
|
}
|
|
|
|
// NoiseDF samples a NormalNoise, scaling the input coordinates (the "noise" /
|
|
// "shifted_noise" family, without the shift inputs).
|
|
type NoiseDF struct {
|
|
Noise *NormalNoise
|
|
XZScale, YScale float64
|
|
}
|
|
|
|
func (n NoiseDF) Compute(c FunctionContext) float64 {
|
|
return n.Noise.GetValue(c.X*n.XZScale, c.Y*n.YScale, c.Z*n.XZScale)
|
|
}
|
|
|
|
type unaryOp struct {
|
|
a DensityFunction
|
|
op func(float64) float64
|
|
}
|
|
|
|
func (n unaryOp) Compute(c FunctionContext) float64 { return n.op(n.a.Compute(c)) }
|
|
|
|
// Abs, Square, Cube, HalfNegative, QuarterNegative, Squeeze are the unary
|
|
// transforms used by the vanilla density tree.
|
|
func Abs(a DensityFunction) DensityFunction { return unaryOp{a, math.Abs} }
|
|
func Square(a DensityFunction) DensityFunction { return unaryOp{a, func(x float64) float64 { return x * x }} }
|
|
func Cube(a DensityFunction) DensityFunction { return unaryOp{a, func(x float64) float64 { return x * x * x }} }
|
|
func HalfNegative(a DensityFunction) DensityFunction {
|
|
return unaryOp{a, func(x float64) float64 {
|
|
if x > 0 {
|
|
return x
|
|
}
|
|
return x * 0.5
|
|
}}
|
|
}
|
|
func QuarterNegative(a DensityFunction) DensityFunction {
|
|
return unaryOp{a, func(x float64) float64 {
|
|
if x > 0 {
|
|
return x
|
|
}
|
|
return x * 0.25
|
|
}}
|
|
}
|
|
func Squeeze(a DensityFunction) DensityFunction {
|
|
return unaryOp{a, func(x float64) float64 {
|
|
d := clamp(x, -1, 1)
|
|
return d/2.0 - d*d*d/24.0
|
|
}}
|
|
}
|
|
|
|
// Clamp constrains a density function to [min, max].
|
|
func Clamp(a DensityFunction, min, max float64) DensityFunction {
|
|
return unaryOp{a, func(x float64) float64 { return clamp(x, min, max) }}
|
|
}
|
|
|
|
// RangeChoice picks whenInRange if input is within [min, max), else whenOut.
|
|
type RangeChoice struct {
|
|
Input DensityFunction
|
|
Min, Max float64
|
|
WhenInRange DensityFunction
|
|
WhenOutOfRange DensityFunction
|
|
}
|
|
|
|
func (r RangeChoice) Compute(c FunctionContext) float64 {
|
|
d := r.Input.Compute(c)
|
|
if d >= r.Min && d < r.Max {
|
|
return r.WhenInRange.Compute(c)
|
|
}
|
|
return r.WhenOutOfRange.Compute(c)
|
|
}
|
|
|
|
// ShiftedNoise samples a NormalNoise at coordinates scaled and offset by shift
|
|
// density functions (the workhorse of climate/terrain inputs).
|
|
type ShiftedNoise struct {
|
|
ShiftX, ShiftY, ShiftZ DensityFunction
|
|
XZScale, YScale float64
|
|
Noise *NormalNoise
|
|
}
|
|
|
|
func (s ShiftedNoise) Compute(c FunctionContext) float64 {
|
|
x := c.X*s.XZScale + s.ShiftX.Compute(c)
|
|
y := c.Y*s.YScale + s.ShiftY.Compute(c)
|
|
z := c.Z*s.XZScale + s.ShiftZ.Compute(c)
|
|
return s.Noise.GetValue(x, y, z)
|
|
}
|
|
|
|
// shiftNoise samples the offset noise at quarter scale, times four.
|
|
func shiftNoise(noise *NormalNoise, x, y, z float64) float64 {
|
|
return noise.GetValue(x*0.25, y*0.25, z*0.25) * 4.0
|
|
}
|
|
|
|
// ShiftA shifts along X/Z (used by shift_x): noise(x, 0, z).
|
|
type ShiftA struct{ Noise *NormalNoise }
|
|
|
|
func (s ShiftA) Compute(c FunctionContext) float64 { return shiftNoise(s.Noise, c.X, 0, c.Z) }
|
|
|
|
// ShiftB shifts with swapped axes (used by shift_z): noise(z, x, 0).
|
|
type ShiftB struct{ Noise *NormalNoise }
|
|
|
|
func (s ShiftB) Compute(c FunctionContext) float64 { return shiftNoise(s.Noise, c.Z, c.X, 0) }
|
|
|
|
// WeirdScaledSampler scales a noise sample by a rarity derived from an input
|
|
// density function (used by the spaghetti caves).
|
|
type WeirdScaledSampler struct {
|
|
Input DensityFunction
|
|
Noise *NormalNoise
|
|
Rarity func(float64) float64
|
|
}
|
|
|
|
func (w WeirdScaledSampler) Compute(c FunctionContext) float64 {
|
|
rarity := w.Rarity(w.Input.Compute(c))
|
|
return rarity * math.Abs(w.Noise.GetValue(c.X/rarity, c.Y/rarity, c.Z/rarity))
|
|
}
|
|
|
|
// SpaghettiRarity2D is the type_2 rarity mapping.
|
|
func SpaghettiRarity2D(v float64) float64 {
|
|
switch {
|
|
case v < -0.75:
|
|
return 0.5
|
|
case v < -0.5:
|
|
return 0.75
|
|
case v < 0.5:
|
|
return 1.0
|
|
case v < 0.75:
|
|
return 2.0
|
|
default:
|
|
return 3.0
|
|
}
|
|
}
|
|
|
|
// SpaghettiRarity3D is the type_1 rarity mapping.
|
|
func SpaghettiRarity3D(v float64) float64 {
|
|
switch {
|
|
case v < -0.5:
|
|
return 0.75
|
|
case v < 0.0:
|
|
return 1.0
|
|
case v < 0.5:
|
|
return 1.5
|
|
default:
|
|
return 2.0
|
|
}
|
|
}
|
|
|
|
func clamp(v, lo, hi float64) float64 {
|
|
if v < lo {
|
|
return lo
|
|
}
|
|
if v > hi {
|
|
return hi
|
|
}
|
|
return v
|
|
}
|
|
|
|
// clampedMap linearly maps v from [inMin,inMax] to [outMin,outMax], clamped.
|
|
func clampedMap(v, inMin, inMax, outMin, outMax float64) float64 {
|
|
if v <= inMin {
|
|
return outMin
|
|
}
|
|
if v >= inMax {
|
|
return outMax
|
|
}
|
|
t := (v - inMin) / (inMax - inMin)
|
|
return outMin + t*(outMax-outMin)
|
|
}
|