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
|
|
@ -30,66 +30,73 @@ type rawParameter struct {
|
|||
} `json:"parameters"`
|
||||
}
|
||||
|
||||
// depthScalar extracts a scalar depth from a raw entry, accepting either a JSON
|
||||
// number or a single-element [v] array. Arrays with a range are cave entries
|
||||
// (non-surface) and return ok=false so the caller skips them.
|
||||
func depthScalar(v any) (float64, bool) {
|
||||
// depthRange extracts a depth band from a raw entry. It accepts a JSON number
|
||||
// (mapped to the half-open band [v, v+1) so a scalar value matches exactly one
|
||||
// integer depth layer), a single-element [v] array (same as the scalar), or a
|
||||
// two-element [min, max] range (used by cave biomes like lush/dripstone_caves
|
||||
// whose depth is [0.2, 0.9]). Returns ok=false only for malformed input.
|
||||
func depthRange(v any) (worldgen.ClimateRange, bool) {
|
||||
switch d := v.(type) {
|
||||
case float64:
|
||||
return d, true
|
||||
q := worldgen.Quantize(d)
|
||||
return worldgen.ClimateRange{Min: q, Max: q + 1}, true
|
||||
case []any:
|
||||
if len(d) == 1 {
|
||||
switch len(d) {
|
||||
case 1:
|
||||
if f, ok := d[0].(float64); ok {
|
||||
return f, true
|
||||
q := worldgen.Quantize(f)
|
||||
return worldgen.ClimateRange{Min: q, Max: q + 1}, true
|
||||
}
|
||||
case 2:
|
||||
lo, ok1 := d[0].(float64)
|
||||
hi, ok2 := d[1].(float64)
|
||||
if ok1 && ok2 {
|
||||
return worldgen.ClimateRange{Min: worldgen.Quantize(lo), Max: worldgen.Quantize(hi)}, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
return worldgen.ClimateRange{}, false
|
||||
}
|
||||
|
||||
// surfaceTable is the biome parameter table filtered to depth=0 (surface layer),
|
||||
// built once at init. Cave/underground entries (depth=1, or non-zero offset for
|
||||
// lush/dripstone/deep_dark) are excluded until the per-cell milestone.
|
||||
// biomeTable is the full biome parameter table (surface + underground twins +
|
||||
// cave biomes), built once at init. The finder's range-contains check on the
|
||||
// depth axis selects the correct layer per cell.
|
||||
var (
|
||||
surfaceTable *worldgen.ParameterTable
|
||||
surfaceTableOnce sync.Once
|
||||
biomeTable *worldgen.ParameterTable
|
||||
biomeTableOnce sync.Once
|
||||
)
|
||||
|
||||
// loadSurfaceTable parses the embedded biome parameters once and returns the
|
||||
// surface-only ParameterTable. Panics on a parse error (a corrupt embedded
|
||||
// table is a build-time bug, not a runtime condition).
|
||||
func loadSurfaceTable() *worldgen.ParameterTable {
|
||||
surfaceTableOnce.Do(func() {
|
||||
// loadBiomeTable parses the embedded biome parameters once and returns the full
|
||||
// ParameterTable. Panics on a parse error (a corrupt embedded table is a
|
||||
// build-time bug, not a runtime condition).
|
||||
func loadBiomeTable() *worldgen.ParameterTable {
|
||||
biomeTableOnce.Do(func() {
|
||||
var raw struct {
|
||||
Biomes []rawParameter `json:"biomes"`
|
||||
}
|
||||
if err := json.Unmarshal(biomeParametersJSON, &raw); err != nil {
|
||||
panic(fmt.Sprintf("world: parsing embedded biome_parameters.json: %v", err))
|
||||
}
|
||||
params := make([]worldgen.BiomeParameter, 0, len(raw.Biomes)/2)
|
||||
params := make([]worldgen.BiomeParameter, 0, len(raw.Biomes))
|
||||
for _, e := range raw.Biomes {
|
||||
// Surface layer only: depth resolves to the scalar 0.0, and no cave
|
||||
// offset. Range/array depths and non-zero offsets belong to cave
|
||||
// biomes (lush/dripstone/deep_dark), deferred to the per-cell stage.
|
||||
dp, ok := depthScalar(e.Param.Depth)
|
||||
if !ok || dp != 0.0 || e.Param.Offset != 0.0 {
|
||||
continue
|
||||
dp, ok := depthRange(e.Param.Depth)
|
||||
if !ok {
|
||||
continue // malformed depth; skip defensively
|
||||
}
|
||||
params = append(params, makeBiomeParameter(e, dp))
|
||||
}
|
||||
surfaceTable = worldgen.NewParameterTable(params)
|
||||
biomeTable = worldgen.NewParameterTable(params)
|
||||
})
|
||||
return surfaceTable
|
||||
return biomeTable
|
||||
}
|
||||
|
||||
// makeBiomeParameter converts a raw JSON entry into a BiomeParameter, mapping
|
||||
// the [min,max] ranges to quantized ClimateRanges. depth is a scalar in the
|
||||
// source but a [depth, depth] band in the table (a single value).
|
||||
func makeBiomeParameter(e rawParameter, depth float64) worldgen.BiomeParameter {
|
||||
// the [min,max] ranges to quantized ClimateRanges. depth is a ClimateRange
|
||||
// (half-open band for scalar depths, explicit range for cave biomes).
|
||||
func makeBiomeParameter(e rawParameter, depth worldgen.ClimateRange) worldgen.BiomeParameter {
|
||||
qr := func(a [2]float64) worldgen.ClimateRange {
|
||||
return worldgen.ClimateRange{Min: worldgen.Quantize(a[0]), Max: worldgen.Quantize(a[1])}
|
||||
}
|
||||
dpQ := worldgen.Quantize(depth)
|
||||
return worldgen.BiomeParameter{
|
||||
Name: e.Biome,
|
||||
Ranges: [worldgen.AxisCount]worldgen.ClimateRange{
|
||||
|
|
@ -98,20 +105,37 @@ func makeBiomeParameter(e rawParameter, depth float64) worldgen.BiomeParameter {
|
|||
qr(e.Param.Continentalness),
|
||||
qr(e.Param.Erosion),
|
||||
qr(e.Param.Weirdness),
|
||||
{Min: dpQ, Max: dpQ + 1}, // half-open band covering exactly depth
|
||||
depth, // half-open band (scalar) or explicit range (cave biomes)
|
||||
},
|
||||
Offset: worldgen.Quantize(e.Param.Offset),
|
||||
}
|
||||
}
|
||||
|
||||
// BiomeAt returns the network biome ID for the surface biome at block (wx, wz)
|
||||
// given the loaded overworld density. It samples the climate axes at sea level,
|
||||
// finds the matching biome in the parameter table, and resolves its name to a
|
||||
// numeric ID via the synchronized biome registry. Unknown biomes fall back to
|
||||
// plains so chunk encoding always gets a valid ID.
|
||||
// given the loaded overworld density. It samples the climate axes at sea level
|
||||
// with depth fixed to 0 (surface layer), finds the matching biome in the full
|
||||
// parameter table, and resolves its name to a numeric ID via the synchronized
|
||||
// biome registry. Unknown biomes fall back to plains so chunk encoding always
|
||||
// gets a valid ID.
|
||||
//
|
||||
// Kept for surface-only (per-chunk) lookups; 3D per-cell code uses BiomeAt3D.
|
||||
func BiomeAt(od *worldgen.OverworldDensity, wx, wz int) uint16 {
|
||||
point := worldgen.SampleColumn(od, SeaLevel, wx, wz)
|
||||
name := loadSurfaceTable().FindBiome(point)
|
||||
return biomeID(loadBiomeTable().FindBiome(point))
|
||||
}
|
||||
|
||||
// BiomeAt3D returns the network biome ID for the biome cell containing block
|
||||
// (wx, wy, wz). s2D carries the five precomputed 2D climate axes for the column
|
||||
// (sampled once via SampleColumn2D); the 3D depth axis is evaluated at wy inside
|
||||
// this function. Surface, underground-twin, and cave biomes are all selectable
|
||||
// because the full parameter table is searched with depth as a true range.
|
||||
func BiomeAt3D(od *worldgen.OverworldDensity, s2D worldgen.Sample2D, wx, wy, wz int) uint16 {
|
||||
point := worldgen.SampleCell(od, s2D, wx, wy, wz)
|
||||
return biomeID(loadBiomeTable().FindBiome(point))
|
||||
}
|
||||
|
||||
// biomeID resolves a biome name to its network ID, falling back to plains.
|
||||
func biomeID(name string) uint16 {
|
||||
if id := registry.Index("minecraft:worldgen/biome", name); id >= 0 {
|
||||
return uint16(id)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue