RegionIO/internal/worldgen/blended.go
Master290 a7bb9496ae Initial commit: RegionIO Minecraft server core (26.1.2/protocol 775)
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.
2026-06-24 00:32:51 +03:00

102 lines
3.2 KiB
Go

package worldgen
// BlendedNoise is the old_blended_noise density function: the legacy 3D
// terrain noise built from min/max limit noises and a main noise. Transcribed
// from the official BlendedNoise; the building-block noises are validated
// bit-for-bit against captured reference values.
type BlendedNoise struct {
minLimit, maxLimit, main *PerlinNoise
xzScale, yScale, xzFactor, yFactor float64
smearScaleMultiplier float64
xzMultiplier, yMultiplier float64
maxValue float64
}
// NewBlendedNoise builds a BlendedNoise from r (legacy seeding: three Perlin
// stacks drawn sequentially) and the scale parameters.
func NewBlendedNoise(r RandomSource, xzScale, yScale, xzFactor, yFactor, smearScaleMultiplier float64) *BlendedNoise {
b := &BlendedNoise{
minLimit: legacyOctaves(r, -15, 0),
maxLimit: legacyOctaves(r, -15, 0),
main: legacyOctaves(r, -7, 0),
xzScale: xzScale,
yScale: yScale,
xzFactor: xzFactor,
yFactor: yFactor,
smearScaleMultiplier: smearScaleMultiplier,
}
b.xzMultiplier = 684.412 * xzScale
b.yMultiplier = 684.412 * yScale
b.maxValue = b.minLimit.MaxBrokenValue(b.yMultiplier)
return b
}
// legacyOctaves creates a legacy PerlinNoise over the inclusive octave range
// [firstOctave, lastOctave], all amplitudes 1 (PerlinNoise.makeAmplitudes).
func legacyOctaves(r RandomSource, firstOctave, lastOctave int) *PerlinNoise {
count := lastOctave - firstOctave + 1
amps := make([]float64, count)
for i := range amps {
amps[i] = 1.0
}
return NewLegacyPerlinNoise(r, firstOctave, amps)
}
// Compute samples the blended noise at (x, y, z).
func (b *BlendedNoise) Compute(c FunctionContext) float64 {
limitX := c.X * b.xzMultiplier
limitY := c.Y * b.yMultiplier
limitZ := c.Z * b.xzMultiplier
mainX := limitX / b.xzFactor
mainY := limitY / b.yFactor
mainZ := limitZ / b.xzFactor
limitSmear := b.yMultiplier * b.smearScaleMultiplier
mainSmear := limitSmear / b.yFactor
mainNoiseValue := 0.0
pow := 1.0
for i := 0; i < 8; i++ {
if oct := b.main.GetOctaveNoise(i); oct != nil {
mainNoiseValue += oct.NoiseY(wrap(mainX*pow), wrap(mainY*pow), wrap(mainZ*pow), mainSmear*pow, mainY*pow) / pow
}
pow /= 2.0
}
factor := (mainNoiseValue/10.0 + 1.0) / 2.0
isMax := factor >= 1.0
isMin := factor <= 0.0
blendMin, blendMax := 0.0, 0.0
pow = 1.0
for i := 0; i < 16; i++ {
wx := wrap(limitX * pow)
wy := wrap(limitY * pow)
wz := wrap(limitZ * pow)
yScalePow := limitSmear * pow
if !isMax {
if oct := b.minLimit.GetOctaveNoise(i); oct != nil {
blendMin += oct.NoiseY(wx, wy, wz, yScalePow, limitY*pow) / pow
}
}
if !isMin {
if oct := b.maxLimit.GetOctaveNoise(i); oct != nil {
blendMax += oct.NoiseY(wx, wy, wz, yScalePow, limitY*pow) / pow
}
}
pow /= 2.0
}
return clampedLerp(factor, blendMin/512.0, blendMax/512.0) / 128.0
}
// clampedLerp is Mth.clampedLerp(factor, min, max): min if factor<0, max if
// factor>1, otherwise linear interpolation.
func clampedLerp(factor, min, max float64) float64 {
if factor < 0 {
return min
}
if factor > 1 {
return max
}
return min + factor*(max-min)
}