3D per-cell biomes (4x4x4) with surface/underground/cave layers
- Chunk stores per-section biome arrays (64 cells/section); flat generators keep the uniform single-valued fallback. - New writeBiomePalette uses min 1 bpe and direct at registry width (65 biomes). - Climate sampler splits 2D axes (sampled once per column) from 3D depth (per cell), keeping per-cell cost to a single density-function compute. - Full biome parameter table (surface + underground twins + lush/dripstone/ deep_dark caves) with depth as a true range, not a binary layer. - fillBiomes3D fills the 1536 cells/chunk in parallel; <0.3ms overhead vs baseline chunk gen (benchmark-verified). - Tests: cave-biome resolution, per-cell variation, flat-world regression, registry-range validity, plus chunk-gen and per-cell benchmarks.
This commit is contained in:
parent
a7bb9496ae
commit
d3142e7687
7 changed files with 431 additions and 75 deletions
|
|
@ -2,28 +2,54 @@ package worldgen
|
|||
|
||||
// This file samples the climate density functions into a TargetPoint for the
|
||||
// biome finder. The climate router keys are 2D (flat_cache + y_scale=0) except
|
||||
// depth, which is 3D. For surface biome selection we fix depth to 0.0, matching
|
||||
// the depth=0 (surface) entries of the biome parameter table; underground and
|
||||
// cave biomes use depth=1.0 / non-zero offset and are a later milestone.
|
||||
// depth, which is 3D. For per-chunk surface biome selection we fix depth to
|
||||
// 0.0; for the per-cell 3D milestone we evaluate real depth at each cell.
|
||||
|
||||
// Sample2D holds the five Y-invariant climate axes for one (x,z) column,
|
||||
// precomputed once so every vertical biome cell in that column reuses them.
|
||||
type Sample2D struct {
|
||||
Temperature, Humidity, Continentalness, Erosion, Weirdness float64
|
||||
}
|
||||
|
||||
// SampleColumn2D evaluates the five 2D climate axes at block (wx, wz). The
|
||||
// vertical coordinate passed to the flat noises (seaLevelY) does not affect the
|
||||
// result because they are flat_cache/y_scale=0, but is kept for symmetry.
|
||||
func SampleColumn2D(od *OverworldDensity, seaLevelY, wx, wz int) Sample2D {
|
||||
ctx := FunctionContext{X: float64(wx), Y: float64(seaLevelY), Z: float64(wz)}
|
||||
return Sample2D{
|
||||
Temperature: computeOrZero(od.Temperature, ctx),
|
||||
Humidity: computeOrZero(od.Humidity, ctx),
|
||||
Continentalness: computeOrZero(od.Continentalness, ctx),
|
||||
Erosion: computeOrZero(od.Erosion, ctx),
|
||||
Weirdness: computeOrZero(od.Weirdness, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
// SampleCell builds a full 3D TargetPoint at block (wx, wy, wz): the five 2D
|
||||
// axes come from the precomputed s2D (sampled once per column), and depth is
|
||||
// evaluated at the cell's real Y — the only Y-dependent climate axis. This
|
||||
// keeps per-cell cost at a single DensityFunction call (depth) instead of six.
|
||||
func SampleCell(od *OverworldDensity, s2D Sample2D, wx, wy, wz int) TargetPoint {
|
||||
depth := 0.0
|
||||
if od.Depth != nil {
|
||||
depth = od.Depth.Compute(FunctionContext{X: float64(wx), Y: float64(wy), Z: float64(wz)})
|
||||
}
|
||||
return NewTargetPoint(s2D.Temperature, s2D.Humidity, s2D.Continentalness,
|
||||
s2D.Erosion, s2D.Weirdness, depth)
|
||||
}
|
||||
|
||||
// SampleColumn evaluates the six climate parameters at block (wx, wz) using od
|
||||
// and returns the TargetPoint for surface biome lookup. seaLevelY is the Y at
|
||||
// which to sample the 2D climate noises (callers pass the world sea level).
|
||||
//
|
||||
// Kept for surface-only (per-chunk) lookups; per-cell 3D code uses
|
||||
// SampleColumn2D + SampleCell instead.
|
||||
func SampleColumn(od *OverworldDensity, seaLevelY int, wx, wz int) TargetPoint {
|
||||
ctx := FunctionContext{X: float64(wx), Y: float64(seaLevelY), Z: float64(wz)}
|
||||
|
||||
temp := computeOrZero(od.Temperature, ctx)
|
||||
humid := computeOrZero(od.Humidity, ctx)
|
||||
cont := computeOrZero(od.Continentalness, ctx)
|
||||
ero := computeOrZero(od.Erosion, ctx)
|
||||
weird := computeOrZero(od.Weirdness, ctx)
|
||||
|
||||
s2D := SampleColumn2D(od, seaLevelY, wx, wz)
|
||||
// Surface layer: depth axis is fixed at 0.0 so only the depth=0 (surface)
|
||||
// biome parameter entries match. The real 3D depth is consulted in the
|
||||
// per-cell milestone.
|
||||
const surfaceDepth = 0.0
|
||||
|
||||
return NewTargetPoint(temp, humid, cont, ero, weird, surfaceDepth)
|
||||
// biome parameter entries match.
|
||||
return NewTargetPoint(s2D.Temperature, s2D.Humidity, s2D.Continentalness,
|
||||
s2D.Erosion, s2D.Weirdness, 0.0)
|
||||
}
|
||||
|
||||
// computeOrZero evaluates df at ctx, returning 0 when df is nil (a climate key
|
||||
|
|
@ -35,3 +61,4 @@ func computeOrZero(df DensityFunction, ctx FunctionContext) float64 {
|
|||
}
|
||||
return df.Compute(ctx)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue