From a8b090227b7d6877c65d989926dc42331ce57987 Mon Sep 17 00:00:00 2001 From: adman Date: Sun, 5 Jul 2026 10:25:43 +0200 Subject: [PATCH] mc gen start --- mc/gen/biome_noise.go | 70 ++++++++++++++++++++ mc/gen/double.go | 37 +++++++++++ mc/gen/noise.go | 137 +++++++++++++++++++++++++++++++++++++++ mc/gen/octave.go | 67 +++++++++++++++++++ mc/gen/xoroshiro.go | 82 +++++++++++++++++++++++ mc/gen/xoroshiro_test.go | 40 ++++++++++++ 6 files changed, 433 insertions(+) create mode 100644 mc/gen/biome_noise.go create mode 100644 mc/gen/double.go create mode 100644 mc/gen/noise.go create mode 100644 mc/gen/octave.go create mode 100644 mc/gen/xoroshiro.go create mode 100644 mc/gen/xoroshiro_test.go diff --git a/mc/gen/biome_noise.go b/mc/gen/biome_noise.go new file mode 100644 index 0000000..6a41a28 --- /dev/null +++ b/mc/gen/biome_noise.go @@ -0,0 +1,70 @@ +package gen + +type NoiseParam int + +const ( + NoiseParamTemperature NoiseParam = iota + NoiseParamHumidity + NoiseParamContinentalNess + NoiseParamErosion + NoiseParamShift + NoiseParamWeirdness +) + +type NoiseParamMapV struct { + A []float64 + S string + O int +} + +var NoiseParamMap = map[NoiseParam]NoiseParamMapV{ + NoiseParamShift: {A: []float64{1, 1, 1, 0}, S: "minecraft:offset", O: -3}, + NoiseParamTemperature: {A: []float64{1.5, 0, 1, 0, 0, 0}, S: "minecraft:temperature", O: -10}, + NoiseParamHumidity: {A: []float64{1, 1, 0, 0, 0, 0}, S: "minecraft:vegetation", O: -8}, + NoiseParamContinentalNess: {A: []float64{1, 1, 2, 2, 2, 1, 1, 1, 1}, S: "minecraft:continentalness", O: -9}, + NoiseParamErosion: {A: []float64{1, 1, 0, 1, 1}, S: "minecraft:erosion", O: -9}, + NoiseParamWeirdness: {A: []float64{1, 2, 1, 0, 0, 0}, S: "minecraft:ridge", O: -7}, +} + +func InitClimateSeed(n NoiseParam, x Xoroshiro, large bool, nmax int) DoublePerlin { + v := NoiseParamMap[n] + s := v.S + if large && n != NoiseParamShift && n != NoiseParamWeirdness { + s += "_large" + } + x.XorString(s) // SLOW potential + return DoublePerlinFromXoroshiro(x, v.A, v.O, len(v.A), nmax) +} + +type BiomeNoise struct { + Temperature, Humidity, ContinentalNess, Erosion, Shift, Weirdness DoublePerlin +} + +func BiomeNoiseFromXoroshiro(x Xoroshiro, large bool) (v BiomeNoise) { + x = x.Split() + + v.Temperature = InitClimateSeed(NoiseParamTemperature, x, large, -1) + v.Humidity = InitClimateSeed(NoiseParamHumidity, x, large, -1) + v.ContinentalNess = InitClimateSeed(NoiseParamContinentalNess, x, large, -1) + v.Erosion = InitClimateSeed(NoiseParamErosion, x, large, -1) + v.Shift = InitClimateSeed(NoiseParamShift, x, large, -1) + v.Weirdness = InitClimateSeed(NoiseParamWeirdness, x, large, -1) + return +} + +func (b BiomeNoise) Sample(x, y, z int) { + px := float64(x) + pz := float64(z) + px += b.Shift.Sample(px, 0, pz) * 4 + pz += b.Shift.Sample(pz, float64(x), 0) * 4 + + c := b.ContinentalNess.Sample(px, 0, pz) + e := b.Erosion.Sample(px, 0, pz) + w := b.Weirdness.Sample(px, 0, pz) + t := b.Temperature.Sample(px, 0, pz) + h := b.Humidity.Sample(px, 0, pz) + + d := float64(0) // FIXME + var combinedData = [6]int64{int64(t * 10000), int64(h * 10000), int64(c * 10000), int64(e * 10000), int64(d * 10000), int64(w * 10000)} + _ = combinedData +} diff --git a/mc/gen/double.go b/mc/gen/double.go new file mode 100644 index 0000000..fd21824 --- /dev/null +++ b/mc/gen/double.go @@ -0,0 +1,37 @@ +package gen + +type DoublePerlin struct { + Amplitude float64 + A Octave + B Octave +} + +func DoublePerlinFromXoroshiro(x Xoroshiro, amplitudes []float64, omin int, lenn int, nmax int) (d DoublePerlin) { + na := -1 + nb := -1 + if nmax > 0 { + na = (nmax + 1) >> 1 + nb = nmax - na + } + d.A = OctaveFromXoroshiro(x, amplitudes, omin, lenn, na) + d.B = OctaveFromXoroshiro(x, amplitudes, omin, lenn, nb) + + for i := lenn - 1; i >= 0 && amplitudes[i] == 0.0; i-- { + lenn-- + } + for i := 0; amplitudes[i] == 0.0; i++ { + lenn-- + } + var ampInits = []float64{ // (5 ./ 3) * len / (len + 1), len = 2..16 + 0, 5 / 6, 10 / 9, 15 / 12, 20 / 15, 25 / 18, 30 / 21, 35 / 24, 40 / 27, 45 / 30, + 50 / 33, 55 / 36, 60 / 39, 65 / 42, 70 / 45, 75 / 48, 80 / 51, + } + d.Amplitude = ampInits[lenn] + return +} + +const DoublePerlinNoiseFactor = 337.0 / 331.0 + +func (d *DoublePerlin) Sample(x, y, z float64) float64 { + return d.Amplitude * (d.A.Sample(x, y, z) + d.B.Sample(x*DoublePerlinNoiseFactor, y*DoublePerlinNoiseFactor, z*DoublePerlinNoiseFactor)) +} diff --git a/mc/gen/noise.go b/mc/gen/noise.go new file mode 100644 index 0000000..e6666c3 --- /dev/null +++ b/mc/gen/noise.go @@ -0,0 +1,137 @@ +package gen + +import "math" + +type PerlinNoise struct { + d [257]byte + h2 byte + xoffset, yoffset, zoffset, amplitude, lacunarity, d2, t2 float64 +} + +func indexedLerp(idx uint8, a, b, c float64) float64 { + switch idx & 0xf { + case 0: + return a + b + case 1: + return -a + b + case 2: + return a - b + case 3: + return -a - b + case 4: + return a + c + case 5: + return -a + c + case 6: + return a - c + case 7: + return -a - c + case 8: + return b + c + case 9: + return -b + c + case 10: + return b - c + case 11: + return -b - c + case 12: + return a + b + case 13: + return -b + c + case 14: + return -a + b + case 15: + return -b - c + } + panic("unreachable") +} + +func lerp(part, from, to float64) float64 { + return from + part*(to-from) +} + +func (n PerlinNoise) Sample(x, y, z float64, yamp, ymin float64) (v float64) { + var h1, h2, h3 uint8 + var t1, t2, t3 float64 + + if y == 0 { + y = n.d2 + h2 = n.h2 + t2 = n.t2 + } else { + y += n.yoffset + yFloored := math.Floor(y) + y -= yFloored + h2 = uint8(int(yFloored)) + t2 = y * y * y * (y*(y*6.0-15.0) + 10.0) + } + + x += n.xoffset + z += n.zoffset + + xFloored := math.Floor(x) + zFloored := math.Floor(z) + x -= xFloored + z -= zFloored + + h1 = uint8(int(xFloored)) + h3 = uint8(int(zFloored)) + + t1 = x * x * x * (x*(x*6.0-15.0) + 10.0) + t3 = z * z * z * (z*(z*6.0-15.0) + 10.0) + + if yamp != 0 { + y -= math.Floor(math.Min(y, ymin)/yamp) * yamp + } + + a1 := n.d[h1] + h2 + b1 := n.d[h1+1] + h2 + + a2 := n.d[a1] + h3 + b2 := n.d[b1] + h3 + a3 := n.d[a1+1] + h3 + b3 := n.d[b1+1] + h3 + + l1 := indexedLerp(n.d[a2], x, y, z) + l2 := indexedLerp(n.d[b2], x-1, y, z) + l3 := indexedLerp(n.d[a3], x, y-1, z) + l4 := indexedLerp(n.d[b3], x-1, y-1, z) + l5 := indexedLerp(n.d[a2+1], x, y, z-1) + l6 := indexedLerp(n.d[b2+1], x-1, y, z-1) + l7 := indexedLerp(n.d[a3+1], x, y-1, z-1) + l8 := indexedLerp(n.d[b3+1], x-1, y-1, z-1) + + l1 = lerp(t1, l1, l2) + l3 = lerp(t1, l3, l4) + l5 = lerp(t1, l5, l6) + l7 = lerp(t1, l7, l8) + + l1 = lerp(t2, l1, l3) + l5 = lerp(t2, l5, l7) + + return lerp(t3, l1, l5) +} + +func PerlinNoiseFromXoroshiro(x Xoroshiro) (p PerlinNoise) { + p.xoffset = x.NextFloat64() * 256.0 + p.yoffset = x.NextFloat64() * 256.0 + p.zoffset = x.NextFloat64() * 256.0 + p.amplitude = 1 + p.lacunarity = 1 + for i := range 256 { + p.d[i] = uint8(i) + } + for i := range int32(256) { + j := x.NextBound(uint32(256-i)) + i + n := p.d[i] + p.d[i] = p.d[j] + p.d[j] = n + } + p.d[256] = 0 + i2 := math.Floor(p.yoffset) + d2 := p.yoffset - i2 + p.h2 = uint8(int(i2)) + p.d2 = d2 + p.t2 = d2 * d2 * d2 * (d2*(d2*6.0-15.0) + 10.0) + return +} diff --git a/mc/gen/octave.go b/mc/gen/octave.go new file mode 100644 index 0000000..7422eff --- /dev/null +++ b/mc/gen/octave.go @@ -0,0 +1,67 @@ +package gen + +type Octave []PerlinNoise + +var octaveStrHashes = []Xoroshiro{ + XoroshiroFromString("octave_-16"), + XoroshiroFromString("octave_-15"), + XoroshiroFromString("octave_-14"), + XoroshiroFromString("octave_-13"), + XoroshiroFromString("octave_-12"), + XoroshiroFromString("octave_-11"), + XoroshiroFromString("octave_-10"), + XoroshiroFromString("octave_-9"), + XoroshiroFromString("octave_-8"), + XoroshiroFromString("octave_-7"), + XoroshiroFromString("octave_-6"), + XoroshiroFromString("octave_-5"), + XoroshiroFromString("octave_-4"), + XoroshiroFromString("octave_-3"), + XoroshiroFromString("octave_-2"), + XoroshiroFromString("octave_-1"), + XoroshiroFromString("octave_0"), +} + +var octaveLacunaInits = []float64{1, .5, .25, 1. / 8, 1. / 16, 1. / 32, 1. / 64, 1. / 128, 1. / 256, 1. / 512, 1. / 1024, + 1. / 2048, 1. / 4096, 1. / 8192, 1. / 16384, 1. / 32768, 1. / 65536} + +var octavePersistInits = []float64{0, 1, 2. / 3, 4. / 7, 8. / 15, 16. / 31, 32. / 63, 64. / 127, 128. / 255, 256. / 511, 512. / 1023, + 1024. / 2047, 2048. / 4095, 4096. / 8191, 8192. / 16383, 16384. / 32767, 32768. / 65535} + +func OctaveFromXoroshiro(x Xoroshiro, amplitudes []float64, omin int, lenn int, nmax int) (o Octave) { + if (-omin < 0 || -omin >= len(octaveLacunaInits)) || (lenn < 0 || lenn >= len(octavePersistInits)) { + panic("octave initialization out of range") + } + octaveX := x.Split() + i := 0 + n := 0 + lacuna := octaveLacunaInits[-omin] + persist := octavePersistInits[lenn] + + for i < lenn && n != nmax { + if amplitudes[i] == 0 { + i++ + lacuna *= 2 + persist *= 0.5 + continue + } + copyX := octaveX + copyX.Xor(octaveStrHashes[16+omin+i]) + p := PerlinNoiseFromXoroshiro(copyX) + p.amplitude = amplitudes[i] * persist + p.lacunarity = lacuna + o = append(o, p) + i++ + n++ + lacuna *= 2 + persist *= 0.5 + } + return +} + +func (o *Octave) Sample(x, y, z float64) (v float64) { + for _, n := range *o { + v += n.Sample(x*n.lacunarity, y*n.lacunarity, z*n.lacunarity, 0, 0) * n.amplitude + } + return +} diff --git a/mc/gen/xoroshiro.go b/mc/gen/xoroshiro.go new file mode 100644 index 0000000..67527c9 --- /dev/null +++ b/mc/gen/xoroshiro.go @@ -0,0 +1,82 @@ +package gen + +import ( + "crypto/md5" + "encoding/binary" +) + +func rotl64(x uint64, b uint8) uint64 { + return (x << b) | (x >> (64 - b)) +} + +type Xoroshiro struct { + L uint64 + H uint64 +} + +func (x *Xoroshiro) Next() uint64 { + l := x.L + h := x.H + n := rotl64(l+h, 17) + l + h ^= l + x.L = rotl64(l, 49) ^ h ^ (h << 21) + x.H = rotl64(h, 28) + return n +} + +func (x *Xoroshiro) NextBound(n uint32) int32 { + r := (x.Next() & 0xFFFFFFFF) * uint64(n) + if uint32(r) < n { + for uint32(r) < (^n+1)%n { + r = (x.Next() & 0xFFFFFFFF) * uint64(n) + } + } + return int32(r >> 32) +} + +func (x *Xoroshiro) NextFloat() float32 { + return float32(x.Next()>>(64-24)) * 5.9604645e-8 +} + +func (x *Xoroshiro) NextFloat64() float64 { + return float64(x.Next()>>(64-53)) * 1.1102230246251565e-16 +} + +func XoroshiroFromString(s string) (x Xoroshiro) { + sum := md5.Sum([]byte(s)) + x.L = binary.BigEndian.Uint64(sum[:8]) + x.H = binary.BigEndian.Uint64(sum[8:]) + return +} + +func XoroshiroFromSeed(seed int64) (x Xoroshiro) { + x.L = uint64(seed) ^ 0x6a09e667f3bcc909 + x.H = x.L + 0x9e3779b97f4a7c15 + return +} + +func mixStafford13(s uint64) uint64 { + s = (s ^ (s >> 30)) * 0xbf58476d1ce4e5b9 + s = (s ^ (s >> 27)) * 0x94d049bb133111eb + return s ^ (s >> 31) +} + +func (x *Xoroshiro) Mix() { + x.L = mixStafford13(x.L) + x.H = mixStafford13(x.H) +} + +func (x *Xoroshiro) Xor(x2 Xoroshiro) { + x.L ^= x2.L + x.H ^= x2.H +} + +func (x *Xoroshiro) XorString(s string) { + x.Xor(XoroshiroFromString(s)) +} + +func (x *Xoroshiro) Split() (x2 Xoroshiro) { + x2.L = x.Next() + x2.H = x.Next() + return +} diff --git a/mc/gen/xoroshiro_test.go b/mc/gen/xoroshiro_test.go new file mode 100644 index 0000000..0b74417 --- /dev/null +++ b/mc/gen/xoroshiro_test.go @@ -0,0 +1,40 @@ +package gen + +import ( + "testing" +) + +func TestXoroshiroFromString(t *testing.T) { + x := XoroshiroFromString("minecraft:offset") + if x.L != 0x080518cf6af25384 || x.H != 0x3f3dfb40a54febd5 { + t.Errorf("bad L: %x, H: %x", x.L, x.H) + } +} + +func TestSeedChain(t *testing.T) { + var worldSeed int64 = 70388202460762 + x := XoroshiroFromSeed(worldSeed) + x.XorString("minecraft:entities/wither_skeleton") + x.Mix() + i := 0 + for { + _ = x.NextBound(1) + _ = x.NextBound(2) + if x.NextFloat() < 0.025 { + i++ + } else { + break + } + } + if i != 7 { + t.Errorf("expected 7, got %d", i) + } +} + +func TestBiome(t *testing.T) { + var worldSeed int64 = 70388202460762 + x := XoroshiroFromSeed(worldSeed) + x.Mix() + b := BiomeNoiseFromXoroshiro(x, false) + b.Sample(-1, -16, 0) +}