RegionIO/internal/worldgen/density.go
Master290 ed045ee09d Parse aquifer and ore-vein noise routers
The overworld noise_router ships fourteen keys; we read six. The eight left on
the floor are exactly the ones the aquifer, the ore veins and the preliminary
surface estimate need, so every one of those subsystems has been impossible to
write.

Wire the rest of the router into OverworldDensity: barrier,
fluid_level_floodedness, fluid_level_spread and lava for the aquifer,
vein_toggle/vein_ridged/vein_gap for the veins, and preliminary_surface_level
for both. Two node types were missing and are added with them --
minecraft:invert (the reciprocal, not negation: Mapped.Type ordinal 5 is
1.0/input) and minecraft:find_top_surface, which walks down from an upper bound
in cell_height steps looking for positive density.

PreliminarySurfaceLevelAt wraps that node the way NoiseChunk does: quart-align
the column, then memoise. The cache is per generator rather than per chunk
because the aquifer samples columns up to three chunks away, so neighbours
overlap heavily -- with a shared cache a chunk costs a few dozen evaluations
instead of a few thousand.

Also lifts sea_level, min_y, height and the aquifers/ore-veins flags out of the
settings file, and adds PositionalRandomFactory.At for the aquifer cell centres
(Mth.getSeed hashed into the low half of the factory seed).

No generator output changes yet: nothing reads the new keys.
2026-07-27 01:44:45 +03:00

256 lines
7.2 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
}}
}
// Invert is the reciprocal transform (DensityFunctions.Mapped.Type.INVERT):
// 1/x, not negation. The overworld's preliminary_surface_level upper bound is
// the only place it appears.
func Invert(a DensityFunction) DensityFunction {
return unaryOp{a, func(x float64) float64 { return 1.0 / x }}
}
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)
}