The router has carried vein_toggle, vein_ridged and vein_gap since the whole of it was parsed, and nothing read them. So the copper and iron mega-veins -- the long branching sheets that run through the deepslate and the copper band, not the small scattered ore blobs -- did not exist. OreVeinifier is not a decoration feature. It is the second entry of the same MaterialRuleList the aquifer heads: the aquifer answers first, and only where it says the position is solid rock does the veinifier get a turn at what would otherwise be plain stone. That is why a vein never opens into a cave, and it is why this lands in the density pass rather than in decorate. Three random draws per position, in a fixed order, from a factory hashed off "minecraft:ore": solidness, then richness, then the rare raw-ore block. Reordering them or hoisting the vein_gap compute above the second draw would change every vein in the world, so the code follows the bytecode's order rather than the one that reads better. The Y windows are the sharp edge worth knowing about: a vein's type comes from the sign of the veininess noise, but its window comes from the type, so veininess <= 0 anywhere outside -60..-8 is simply not a vein. Over 49 sampled chunks copper lands in 0..49 and iron in -57..-8, and raw ore comes out at 1.9% of ore blocks against vanilla's 2%.
530 lines
18 KiB
Go
530 lines
18 KiB
Go
package world
|
||
|
||
import (
|
||
"math"
|
||
"math/rand"
|
||
"sync"
|
||
|
||
"regionio/internal/worldgen"
|
||
)
|
||
|
||
// Noise cell dimensions for the overworld (size_horizontal=1 → 4 wide,
|
||
// size_vertical=2 → 8 tall). Only the Interpolated terrain noise is sampled on
|
||
// the cell-corner grid and trilinearly interpolated (as vanilla's NoiseChunk
|
||
// does); the rest of final_density — squeeze/min and the caves — is evaluated
|
||
// per block with those interpolated values substituted in.
|
||
const (
|
||
cellWidth = 4
|
||
cellHeight = 8
|
||
cellsXZ = 16 / cellWidth // 4
|
||
cellsY = WorldHeight / cellHeight // 48
|
||
)
|
||
|
||
type cornerGrid [cellsXZ + 1][cellsY + 1][cellsXZ + 1]float64
|
||
|
||
// NewVanillaGenerator returns a generator backed by the real overworld
|
||
// final_density tree for the given seed, plus a simplified cosmetic pass
|
||
// (beaches and trees) layered on the bit-accurate terrain.
|
||
func NewVanillaGenerator(seed int64) Generator {
|
||
od, err := worldgen.LoadOverworldFinalDensity(seed)
|
||
if err != nil {
|
||
panic("world: loading overworld density: " + err.Error())
|
||
}
|
||
fluidPicker := worldgen.OverworldFluidPicker(od.SeaLevel)
|
||
veins := worldgen.NewOreVeinifier(od)
|
||
return func(cx, cz int32) *Chunk {
|
||
return generateVanilla(od, fluidPicker, veins, seed, cx, cz)
|
||
}
|
||
}
|
||
|
||
func generateVanilla(od *worldgen.OverworldDensity, fluidPicker worldgen.FluidPicker, veins *worldgen.OreVeinifier, seed int64, cx, cz int32) *Chunk {
|
||
c := NewChunk(cx, cz, BiomePlains) // per-cell biomes override below
|
||
baseX, baseZ := int(cx)*16, int(cz)*16
|
||
|
||
grids := make([]cornerGrid, len(od.Interpolated))
|
||
var wg sync.WaitGroup
|
||
for ix := 0; ix <= cellsXZ; ix++ {
|
||
wg.Add(1)
|
||
go func(ix int) {
|
||
defer wg.Done()
|
||
wx := float64(baseX + ix*cellWidth)
|
||
for iy := 0; iy <= cellsY; iy++ {
|
||
wy := float64(MinY + iy*cellHeight)
|
||
for iz := 0; iz <= cellsXZ; iz++ {
|
||
ctx := worldgen.FunctionContext{X: wx, Y: wy, Z: float64(baseZ + iz*cellWidth)}
|
||
for n, node := range od.Interpolated {
|
||
grids[n][ix][iy][iz] = node.Inner.Compute(ctx)
|
||
}
|
||
}
|
||
}
|
||
}(ix)
|
||
}
|
||
wg.Wait()
|
||
|
||
// Surface biomes and 2D climate are needed before column fill so the surface
|
||
// rule tree can pick biome-specific blocks. They are also reused by
|
||
// fillBiomes3D below, so compute them once here.
|
||
var s2D [16][16]worldgen.Sample2D
|
||
var biomeName [16][16]string
|
||
for lx := 0; lx < 16; lx++ {
|
||
wg.Add(1)
|
||
go func(lx int) {
|
||
defer wg.Done()
|
||
for lz := 0; lz < 16; lz++ {
|
||
s2D[lx][lz] = worldgen.SampleColumn2D(od, SeaLevel, baseX+lx, baseZ+lz)
|
||
biomeName[lx][lz] = loadBiomeTable().FindBiome(
|
||
worldgen.NewTargetPoint(s2D[lx][lz].Temperature, s2D[lx][lz].Humidity,
|
||
s2D[lx][lz].Continentalness, s2D[lx][lz].Erosion, s2D[lx][lz].Weirdness, 0))
|
||
}
|
||
}(lx)
|
||
}
|
||
wg.Wait()
|
||
|
||
// The surface rule set is compiled against the world seed at load time. If
|
||
// it failed to parse, the surface pass falls back to biome-blind heuristics
|
||
// rather than leaving the terrain bare.
|
||
surfaceRule, ruleErr := od.SurfaceRule()
|
||
|
||
// The aquifer decides fluid per position while the column is laid down. Its
|
||
// cell grid spans the chunk plus a margin, so it is built once per chunk and
|
||
// shared, read-only, by the parallel column fill.
|
||
var aq *worldgen.Aquifer
|
||
if od.AquifersEnabled {
|
||
aq = worldgen.NewAquifer(od, int(cx), int(cz), fluidPicker)
|
||
}
|
||
|
||
var columns [16][16][WorldHeight]uint16
|
||
var surfTop [16][16]int // top solid index, -1 if none
|
||
var worldSurface [16][16]int // topmost non-air Y, the WORLD_SURFACE_WG heightmap
|
||
var grass [16][16]bool // grassy land surface (tree-plantable)
|
||
|
||
// Terrain and fluids first, for the whole chunk. The surface pass has to
|
||
// wait for all of it: the "steep" condition reads the heights of the
|
||
// column's neighbours, which vanilla takes from the heightmap that doFill
|
||
// finishes before buildSurface starts.
|
||
for lx := 0; lx < 16; lx++ {
|
||
wg.Add(1)
|
||
go func(lx int) {
|
||
defer wg.Done()
|
||
interp := make([]float64, len(od.Interpolated))
|
||
for lz := 0; lz < 16; lz++ {
|
||
surfTop[lx][lz], worldSurface[lx][lz], grass[lx][lz] =
|
||
fillVanillaColumn(od, aq, fluidPicker, veins, grids, interp, &columns[lx][lz], baseX+lx, baseZ+lz, lx, lz)
|
||
}
|
||
}(lx)
|
||
}
|
||
wg.Wait()
|
||
|
||
for lx := 0; lx < 16; lx++ {
|
||
wg.Add(1)
|
||
go func(lx int) {
|
||
defer wg.Done()
|
||
var sctx *worldgen.SurfaceContext
|
||
if ruleErr == nil {
|
||
sctx = surfaceRule.NewContext()
|
||
}
|
||
for lz := 0; lz < 16; lz++ {
|
||
if ruleErr == nil {
|
||
applySurfaceRule(od, surfaceRule, sctx, &columns[lx][lz],
|
||
baseX+lx, baseZ+lz, lx, lz, &worldSurface, biomeName[lx][lz])
|
||
continue
|
||
}
|
||
fillLegacySurface(&columns[lx][lz], surfTop[lx][lz], newColumnRand(baseX+lx, baseZ+lz, int(seed)))
|
||
}
|
||
}(lx)
|
||
}
|
||
wg.Wait()
|
||
|
||
for lx := 0; lx < 16; lx++ {
|
||
for lz := 0; lz < 16; lz++ {
|
||
col := &columns[lx][lz]
|
||
for i := 0; i < WorldHeight; i++ {
|
||
if s := col[i]; s != StateAir {
|
||
c.setBlockRaw(lx, MinY+i, lz, s)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
fillBiomes3D(c, od, s2D, baseX, baseZ)
|
||
decorate(c, od, cx, cz, seed, &surfTop, &grass, &biomeName)
|
||
return c
|
||
}
|
||
|
||
// fillBiomes3D assigns a per-cell 4×4×4 biome to every section of the chunk.
|
||
// It receives the precomputed 2D climate grid (s2D, already sampled per column
|
||
// for the surface pass) and evaluates only the 3D depth axis per cell, keeping
|
||
// per-cell cost to a single density-function compute. The biome columns are
|
||
// processed in parallel to keep generation fast.
|
||
func fillBiomes3D(c *Chunk, od *worldgen.OverworldDensity, s2D [16][16]worldgen.Sample2D, baseX, baseZ int) {
|
||
var wg sync.WaitGroup
|
||
// One biome per 4×4×4 cell. Sampling at the cell corner (bx*4, bz*4) is
|
||
// representative because the 2D climate noises vary slowly relative to a
|
||
// 4-block cell; depth carries the vertical variation.
|
||
for bx := 0; bx < biomeCellsXZ; bx++ {
|
||
wg.Add(1)
|
||
go func(bx int) {
|
||
defer wg.Done()
|
||
lx := bx * biomeCellSize
|
||
for bz := 0; bz < biomeCellsXZ; bz++ {
|
||
lz := bz * biomeCellSize
|
||
col2D := s2D[lx][lz]
|
||
for si := 0; si < SectionCount; si++ {
|
||
for by := 0; by < biomeCellsXZ; by++ {
|
||
wy := MinY + si*16 + by*biomeCellSize
|
||
biome := BiomeAt3D(od, col2D, baseX+lx, wy, baseZ+lz)
|
||
c.SetBiome(lx, wy, lz, biome)
|
||
}
|
||
}
|
||
}
|
||
}(bx)
|
||
}
|
||
wg.Wait()
|
||
}
|
||
|
||
// fillVanillaColumn lays the blocks for one column and returns the top solid
|
||
// index and whether the surface is grassy land (suitable for trees).
|
||
//
|
||
// The order matches vanilla: the density pass decides stone-or-not, the aquifer
|
||
// turns every non-stone position into air, water or lava (and can also seal a
|
||
// position back to stone where the barrier noise says the rock holds), and only
|
||
// then does the surface rule tree walk the finished column. Doing it the other
|
||
// way round is what forced the old unconditional "flood everything under sea
|
||
// level" pass, which left every cave below y=63 underwater.
|
||
func fillVanillaColumn(od *worldgen.OverworldDensity, aq *worldgen.Aquifer, fluidPicker worldgen.FluidPicker, veins *worldgen.OreVeinifier, grids []cornerGrid, interp []float64, out *[WorldHeight]uint16, wx, wz, lx, lz int) (top, worldSurface int, grass bool) {
|
||
cx0 := lx / cellWidth
|
||
cz0 := lz / cellWidth
|
||
fx := float64(lx%cellWidth) / cellWidth
|
||
fz := float64(lz%cellWidth) / cellWidth
|
||
|
||
top, worldSurface = -1, MinY-1
|
||
for i := 0; i < WorldHeight; i++ {
|
||
cy0 := i / cellHeight
|
||
fy := float64(i%cellHeight) / cellHeight
|
||
for n := range grids {
|
||
interp[n] = trilerp(&grids[n], cx0, cy0, cz0, fx, fy, fz)
|
||
}
|
||
y := MinY + i
|
||
ctx := worldgen.FunctionContext{X: float64(wx), Y: float64(y), Z: float64(wz)}.WithInterp(interp)
|
||
density := od.Final.Compute(ctx)
|
||
state, solid := substance(aq, fluidPicker, veins, ctx, wx, y, wz, density)
|
||
out[i] = state
|
||
if solid {
|
||
top = i
|
||
}
|
||
if state != StateAir {
|
||
worldSurface = y
|
||
}
|
||
}
|
||
|
||
topY := MinY + top
|
||
// Beach: a narrow band straddling the waterline. Dry columns well above sea
|
||
// level stay grass; deep water floors become gravel, not sand.
|
||
const beachBand = 3
|
||
beach := top >= 0 && topY >= SeaLevel-beachBand && topY <= SeaLevel+1
|
||
deepWater := top >= 0 && topY < SeaLevel-beachBand
|
||
return top, worldSurface, top >= 0 && !beach && !deepWater && topY >= SeaLevel
|
||
}
|
||
|
||
// steepAt is SurfaceRules.SteepMaterialCondition: true where the column's
|
||
// neighbours inside the chunk differ in height by four blocks or more. The
|
||
// neighbour indices are clamped to the chunk, as vanilla's are — the condition
|
||
// deliberately does not look at the chunk next door.
|
||
func steepAt(worldSurface *[16][16]int, lx, lz int) bool {
|
||
north := max(lz-1, 0)
|
||
south := min(lz+1, 15)
|
||
if worldSurface[lx][south] >= worldSurface[lx][north]+4 {
|
||
return true
|
||
}
|
||
west := max(lx-1, 0)
|
||
east := min(lx+1, 15)
|
||
return worldSurface[west][lz] >= worldSurface[east][lz]+4
|
||
}
|
||
|
||
// substance resolves one position to the block the terrain pass leaves behind,
|
||
// mirroring vanilla's MaterialRuleList: the aquifer answers first and, where it
|
||
// says the position is solid rock, the ore veinifier gets a turn before the
|
||
// default block is used. The second result says whether the position ended up
|
||
// solid, so the caller can track the top solid block without re-testing.
|
||
func substance(aq *worldgen.Aquifer, fluidPicker worldgen.FluidPicker, veins *worldgen.OreVeinifier, ctx worldgen.FunctionContext, x, y, z int, density float64) (state uint16, solid bool) {
|
||
if aq == nil {
|
||
// aquifers_enabled=false: Aquifer.createDisabled, the global fluid rule
|
||
// with no cells and no barriers.
|
||
if density > 0 {
|
||
return veinOrDefault(veins, ctx, x, y, z), true
|
||
}
|
||
return fluidPicker(x, y, z).At(y), false
|
||
}
|
||
if s, ok := aq.ComputeSubstance(x, y, z, density); ok {
|
||
return s, false
|
||
}
|
||
return veinOrDefault(veins, ctx, x, y, z), true
|
||
}
|
||
|
||
// veinOrDefault is the tail of the rule list: an ore vein if one reaches here,
|
||
// otherwise the settings' default block.
|
||
func veinOrDefault(veins *worldgen.OreVeinifier, ctx worldgen.FunctionContext, x, y, z int) uint16 {
|
||
if veins != nil {
|
||
if s, ok := veins.Calculate(ctx, x, y, z); ok {
|
||
return s
|
||
}
|
||
}
|
||
return StateStone
|
||
}
|
||
|
||
// applySurfaceRule walks the finished column from the top down, applying the
|
||
// rule tree to every default-block position, and mirrors SurfaceSystem's
|
||
// bookkeeping as it goes:
|
||
//
|
||
// - air resets both the stone depth and the water height;
|
||
// - a fluid records the height of the first (topmost) block of its run;
|
||
// - stone carries a depth counted down from the top of its run, and a depth
|
||
// counted up from the bottom, found by looking ahead to the next non-stone
|
||
// block below.
|
||
//
|
||
// The rule only replaces the default block, so anything the aquifer placed —
|
||
// water in an ocean, lava in a deep pocket — survives untouched.
|
||
//
|
||
// One *rand.Rand is created per column (not per block) — bandlands/gradient
|
||
// consume from it sequentially, which is correct because vanilla seeds those
|
||
// per-column too. This avoids ~98k rand.New allocations per chunk.
|
||
func applySurfaceRule(od *worldgen.OverworldDensity, rules *worldgen.SurfaceRuleSet, sctx *worldgen.SurfaceContext, out *[WorldHeight]uint16, wx, wz, lx, lz int, worldSurface *[16][16]int, biomeName string) {
|
||
top := -1
|
||
for i := WorldHeight - 1; i >= 0; i-- {
|
||
if out[i] != StateAir {
|
||
top = i
|
||
break
|
||
}
|
||
}
|
||
if top < 0 {
|
||
return
|
||
}
|
||
// Column-constant surface quantities, refreshed once per column exactly as
|
||
// SurfaceRules.Context.updateXZ does. The context itself is reused across
|
||
// the whole 16-column strip to avoid ~98k allocations per chunk; the fields
|
||
// that vary per block are set inside the loop below.
|
||
rules.BeginColumn(sctx, wx, wz)
|
||
surfaceDepth := od.Surface.SurfaceDepth(wx, wz)
|
||
sctx.SeaLevel = SeaLevel
|
||
sctx.BiomeName = biomeName
|
||
sctx.MinY = MinY
|
||
sctx.SurfaceSecondary = od.Surface.SurfaceSecondary(wx, wz)
|
||
sctx.SurfaceDepth = surfaceDepth
|
||
sctx.MinSurfaceLevel = od.MinSurfaceLevelAt(wx, wz, surfaceDepth)
|
||
sctx.Steep = steepAt(worldSurface, lx, lz)
|
||
minY := MinY
|
||
stoneDepthAbove := 0
|
||
waterHeight := worldgen.NoWaterAbove
|
||
nextCeilingStoneY := math.MaxInt
|
||
for i := top; i >= 0; i-- {
|
||
y := minY + i
|
||
old := out[i]
|
||
if old == StateAir {
|
||
stoneDepthAbove = 0
|
||
waterHeight = worldgen.NoWaterAbove
|
||
continue
|
||
}
|
||
if isFluidState(old) {
|
||
if waterHeight == worldgen.NoWaterAbove {
|
||
waterHeight = y + 1
|
||
}
|
||
continue
|
||
}
|
||
if nextCeilingStoneY >= y {
|
||
// Look ahead to the first non-stone block below; the scan runs one
|
||
// past the world floor, which reads as air, so it always terminates.
|
||
nextCeilingStoneY = worldgen.WayBelowMinY
|
||
for j := i - 1; j >= -1; j-- {
|
||
if j >= 0 && isStoneState(out[j]) {
|
||
continue
|
||
}
|
||
nextCeilingStoneY = minY + j + 1
|
||
break
|
||
}
|
||
}
|
||
stoneDepthAbove++
|
||
sctx.Y = y
|
||
sctx.StoneDepthAbove = stoneDepthAbove
|
||
sctx.StoneDepthBelow = y - nextCeilingStoneY + 1
|
||
sctx.WaterHeight = waterHeight
|
||
if old != StateStone {
|
||
continue
|
||
}
|
||
// A matched rule places its block even when that block is air: the
|
||
// frozen-ocean surface deliberately carves one away. Only "no rule
|
||
// matched" leaves the default block alone.
|
||
if state, ok := rules.Apply(sctx); ok {
|
||
out[i] = state
|
||
}
|
||
}
|
||
}
|
||
|
||
// isFluidState reports whether a raw terrain block is a fluid (SurfaceSystem
|
||
// branches on getFluidState().isEmpty()). Only the aquifer's own fluids can
|
||
// appear here, since the rule pass runs before decoration.
|
||
func isFluidState(s uint16) bool { return s == StateWater || s == StateLava }
|
||
|
||
// isStoneState is SurfaceSystem.isStone: solid, non-fluid, non-air.
|
||
func isStoneState(s uint16) bool { return s != StateAir && !isFluidState(s) }
|
||
|
||
// fillLegacySurface is the biome-blind heuristic used when no surface rule set
|
||
// is available (parse failure). It dresses the stone the terrain and aquifer
|
||
// passes already laid down, leaving their air and fluids alone.
|
||
func fillLegacySurface(out *[WorldHeight]uint16, top int, rng chunkRand) {
|
||
const beachBand = 3
|
||
topY := MinY + top
|
||
beach := top >= 0 && topY >= SeaLevel-beachBand && topY <= SeaLevel+1
|
||
deepWater := top >= 0 && topY < SeaLevel-beachBand
|
||
for i := 0; i < WorldHeight; i++ {
|
||
y := MinY + i
|
||
if !isStoneState(out[i]) {
|
||
continue
|
||
}
|
||
switch {
|
||
case y <= MinY:
|
||
out[i] = StateBedrock
|
||
case y <= MinY+4 && bedrockAt(&rng, y-MinY):
|
||
out[i] = StateBedrock
|
||
case beach && i > top-4:
|
||
out[i] = StateSand
|
||
case deepWater && i == top:
|
||
out[i] = StateGravel
|
||
case i == top && y >= SeaLevel:
|
||
out[i] = StateGrass
|
||
case i > top-4:
|
||
out[i] = StateDirt
|
||
}
|
||
}
|
||
}
|
||
|
||
// bedrockAt reports whether the block d layers above the world floor should be
|
||
// bedrock, consuming one draw from rng. It mirrors the datapack's
|
||
// vertical_gradient(minecraft:bedrock_floor, above_bottom 0 → above_bottom 5):
|
||
// the probability ramps linearly from 1 at the floor to 0 five blocks up, and
|
||
// vanilla tests nextFloat() < probability.
|
||
//
|
||
// rng is a pointer so successive layers draw successive values. Taking it by
|
||
// value handed every layer the same number, which nested the layers into a
|
||
// prefix condition instead of scattering them. The ramp also used to run the
|
||
// wrong way — bedrock was likelier four blocks up than at the floor.
|
||
//
|
||
// Only fillLegacySurface calls this; the normal path lets the surface rule tree
|
||
// place the floor from the same datapack rule.
|
||
func bedrockAt(rng *chunkRand, d int) bool {
|
||
if d <= 0 {
|
||
return true
|
||
}
|
||
if d >= 5 {
|
||
return false
|
||
}
|
||
return rng.nextFloat() < 1.0-float64(d)/5.0
|
||
}
|
||
|
||
// decorate places simple oak trees on grassy columns. Trunks are kept two
|
||
// blocks inside the chunk so the radius-2 canopy never crosses into a neighbour
|
||
// (avoiding cross-chunk coordination); placement is deterministic per chunk.
|
||
func decorate(c *Chunk, od *worldgen.OverworldDensity, cx, cz int32, seed int64, surfTop *[16][16]int, grass *[16][16]bool, biomeName *[16][16]string) {
|
||
r := newChunkRand(cx, cz, seed)
|
||
|
||
placeOres(c, &r)
|
||
placeFlora(c, &r, surfTop, grass, biomeName)
|
||
placeDesertFeatures(c, &r, surfTop, biomeName)
|
||
placeRocks(c, &r, surfTop, grass, biomeName)
|
||
|
||
const attempts = 8
|
||
for a := 0; a < attempts; a++ {
|
||
lx := 2 + int(r.next()%12)
|
||
lz := 2 + int(r.next()%12)
|
||
if !grass[lx][lz] {
|
||
continue
|
||
}
|
||
baseY := MinY + surfTop[lx][lz] + 1
|
||
placeOak(c, lx, baseY, lz, &r)
|
||
}
|
||
|
||
// Place large structures like villages and strongholds
|
||
worldgen.PlaceStructures(c, od, cx, cz, seed, surfTop, biomeName)
|
||
}
|
||
|
||
func placeOak(c *Chunk, lx, baseY, lz int, r *chunkRand) {
|
||
h := 4 + int(r.next()%3) // trunk height 4..6
|
||
for i := 0; i < h; i++ {
|
||
c.SetBlock(lx, baseY+i, lz, StateOakLog)
|
||
}
|
||
topY := baseY + h - 1
|
||
// Canopy: two wide layers around the top, then two narrow layers above.
|
||
layers := []struct {
|
||
dy, radius int
|
||
}{{-1, 2}, {0, 2}, {1, 1}, {2, 1}}
|
||
for _, ly := range layers {
|
||
y := topY + ly.dy
|
||
for dx := -ly.radius; dx <= ly.radius; dx++ {
|
||
for dz := -ly.radius; dz <= ly.radius; dz++ {
|
||
if ly.radius == 2 && abs(dx) == 2 && abs(dz) == 2 {
|
||
continue // trim the far corners for a rounder shape
|
||
}
|
||
if c.GetBlock(lx+dx, y, lz+dz) == StateAir {
|
||
c.SetBlock(lx+dx, y, lz+dz, StateOakLeaf)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func abs(v int) int {
|
||
if v < 0 {
|
||
return -v
|
||
}
|
||
return v
|
||
}
|
||
|
||
// chunkRand is a tiny deterministic PRNG (SplitMix64) seeded per chunk.
|
||
type chunkRand struct{ s uint64 }
|
||
|
||
func newChunkRand(cx, cz int32, seed int64) chunkRand {
|
||
h := uint64(seed)
|
||
h ^= uint64(uint32(cx)) * 0x9E3779B97F4A7C15
|
||
h ^= uint64(uint32(cz)) * 0xC2B2AE3D27D4EB4F
|
||
return chunkRand{s: h | 1}
|
||
}
|
||
|
||
// newColumnRand seeds a deterministic PRNG from a column's world coordinates so
|
||
// each (x,z) gets a stable but independent stream (used for the random bedrock
|
||
// layer). Mixing in the world seed keeps worlds with the same terrain shape but
|
||
// different seeds distinct at the floor.
|
||
func newColumnRand(wx, wz, seed int) chunkRand {
|
||
h := uint64(seed)
|
||
h ^= uint64(uint32(wx)) * 0x9E3779B97F4A7C15
|
||
h ^= uint64(uint32(wz)) * 0xC2B2AE3D27D4EB4F
|
||
return chunkRand{s: h | 1}
|
||
}
|
||
|
||
func (r *chunkRand) next() uint32 {
|
||
r.s += 0x9E3779B97F4A7C15
|
||
z := r.s
|
||
z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9
|
||
z = (z ^ (z >> 27)) * 0x94D049BB133111EB
|
||
z = z ^ (z >> 31)
|
||
return uint32(z >> 32)
|
||
}
|
||
|
||
func (r *chunkRand) nextFloat() float64 {
|
||
return float64(r.next()) / float64(1<<32)
|
||
}
|
||
|
||
// toRand returns a *rand.Rand seeded from this column's state, for surface
|
||
// rules (vertical_gradient/bandlands) that consume a stdlib-style RNG. It draws
|
||
// once to advance state so repeated calls differ within a column.
|
||
func (r *chunkRand) toRand() *rand.Rand {
|
||
return rand.New(rand.NewSource(int64(r.next())))
|
||
}
|
||
|
||
func trilerp(c *cornerGrid, x0, y0, z0 int, fx, fy, fz float64) float64 {
|
||
x1, y1, z1 := x0+1, y0+1, z0+1
|
||
c00 := lerpf(fx, c[x0][y0][z0], c[x1][y0][z0])
|
||
c10 := lerpf(fx, c[x0][y1][z0], c[x1][y1][z0])
|
||
c01 := lerpf(fx, c[x0][y0][z1], c[x1][y0][z1])
|
||
c11 := lerpf(fx, c[x0][y1][z1], c[x1][y1][z1])
|
||
return lerpf(fz, lerpf(fy, c00, c10), lerpf(fy, c01, c11))
|
||
}
|
||
|
||
func lerpf(t, a, b float64) float64 { return a + t*(b-a) }
|