diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/biome/accessor/VoronoiBiomeAccessorTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/biome/accessor/VoronoiBiomeAccessorTest.kt index 769b77f9f..0ab043c26 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/biome/accessor/VoronoiBiomeAccessorTest.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/biome/accessor/VoronoiBiomeAccessorTest.kt @@ -23,7 +23,7 @@ import org.testng.annotations.Test class VoronoiBiomeAccessorTest { private val getBiomeOffset = VoronoiBiomeAccessor::class.java.getDeclaredMethod("getBiomeOffset", Long::class.java, Int::class.java, Int::class.java, Int::class.java).apply { isAccessible = true } - // TODO: those values are too far off. They match vanilla, yes, but I am still not going to allow that. The noise should be fairly smooth around the data + // Those values are actually undefined, the getBiomeOffset method only allows values from 0 to 15 @Test(enabled = false) fun testBiomeNoise1() { assertEquals(calculate(129, 3274, 91, 1823123L), Vec3i(32, 818, 22)) @@ -48,14 +48,52 @@ class VoronoiBiomeAccessorTest { assertEquals(calculate(0, 3, 1, -33135639), Vec3i(0, 0, 0)) } + @Test(enabled = false) fun testBiomeNoise6() { assertEquals(calculate(16, 15, -16, 561363374), Vec3i(4, 3, -4)) } + @Test(enabled = false) fun testBiomeNoise7() { assertEquals(calculate(16, -15, -16, 79707367), Vec3i(4, -4, -5)) } + fun `noise at (0,0,0) seed1`() { + assertEquals(calculate(0, 0, 0, -33135639), Vec3i(-1, 0, -1)) + } + + fun `noise at (0,0,0) seed2`() { + assertEquals(calculate(0, 0, 0, 1234567891234567891L), Vec3i(-1, 0, -1)) + } + + fun `noise at (0,0,0) seed3`() { + assertEquals(calculate(0, 0, 0, -987654321987654319L), Vec3i(-1, 0, 0)) + } + + fun `noise at (15,15,15) seed1`() { + assertEquals(calculate(15, 15, 15, -33135639), Vec3i(3, 3, 3)) + } + + fun `noise at (15,15,15) seed2`() { + assertEquals(calculate(15, 15, 15, 1234567891234567891L), Vec3i(3, 3, 3)) + } + + fun `noise at (15,15,15) seed3`() { + assertEquals(calculate(15, 15, 15, -987654321987654319L), Vec3i(3, 3, 3)) + } + + fun `noise at (8,15,4) seed1`() { + assertEquals(calculate(8, 15, 4, -33135639), Vec3i(1, 3, 0)) + } + + fun `noise at (8,15,4) seed2`() { + assertEquals(calculate(8, 15, 4, 1234567891234567891L), Vec3i(1, 3, 1)) + } + + fun `noise at (8,15,4) seed3`() { + assertEquals(calculate(8, 15, 4, -987654321987654319L), Vec3i(1, 4, 0)) + } + private fun calculate(x: Int, y: Int, z: Int, seed: Long): Vec3i { val accessor = VoronoiBiomeAccessor::class.java.allocate() val index = getBiomeOffset.invoke(accessor, seed, x, y, z) as Int diff --git a/src/main/java/de/bixilon/minosoft/data/world/biome/accessor/noise/VoronoiBiomeAccessor.kt b/src/main/java/de/bixilon/minosoft/data/world/biome/accessor/noise/VoronoiBiomeAccessor.kt index 5d3a75a5b..ea4fe0b25 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/biome/accessor/noise/VoronoiBiomeAccessor.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/biome/accessor/noise/VoronoiBiomeAccessor.kt @@ -16,6 +16,7 @@ package de.bixilon.minosoft.data.world.biome.accessor.noise import de.bixilon.kutil.math.simple.DoubleMath.square import de.bixilon.minosoft.data.registries.biomes.Biome import de.bixilon.minosoft.data.world.World +import de.bixilon.minosoft.data.world.biome.source.SpatialBiomeArray import de.bixilon.minosoft.data.world.chunk.chunk.Chunk class VoronoiBiomeAccessor( @@ -41,61 +42,65 @@ class VoronoiBiomeAccessor( } private fun getBiomeOffset(seed: Long, x: Int, y: Int, z: Int): Int { - val m = x - 2 - val n = y - 2 - val o = z - 2 + // all xyz coordinates are from 0..15 - val p = m shr 2 - val q = n shr 2 - val r = o shr 2 + // target biome can also be on the negative side, offset by -2 + val cX = x - 2 + val cY = y - 2 + val cZ = z - 2 - val d = (m and 0x03) / 4.0 - val e = (n and 0x03) / 4.0 - val f = (o and 0x03) / 4.0 + // array source + val sX = cX shr 2 + val sY = cY shr 2 + val sZ = cZ shr 2 - var s = 0 - var g = Double.POSITIVE_INFINITY + // in array + val iX = (cX and 0x03) / 4.0 + val iY = (cY and 0x03) / 4.0 + val iZ = (cZ and 0x03) / 4.0 - for (i in 0 until 8) { - var u = p - var xFraction = d - if (i and 0x04 != 0) { - u++ - xFraction -= 1.0 + var minXYZ = 0 + var minDistance = Double.POSITIVE_INFINITY + + for (xyz in 0 until 2 * 2 * 2) { + var uX = sX + var offsetX = iX + if (xyz and 0x04 != 0) { + uX++ + offsetX -= 1.0 } - var v = q - var yFraction = e - if (i and 0x02 != 0) { - v++ - yFraction -= 1.0 + var uY = sY + var offsetY = iY + if (xyz and 0x02 != 0) { + uY++ + offsetY -= 1.0 } - var w = r - var zFraction = f - if (i and 0x01 != 0) { - w++ - zFraction -= 1.0 + var uZ = sZ + var offsetZ = iZ + if (xyz and 0x01 != 0) { + uZ++ + offsetZ -= 1.0 } - val d3 = calculateFiddle(seed, u, v, w, xFraction, yFraction, zFraction) - if (g > d3) { - s = i - g = d3 - } + val distance = noiseDistance(seed, uX, uY, uZ, offsetX, offsetY, offsetZ) + if (distance > minDistance) continue + minXYZ = xyz + minDistance = distance } - var biomeX = p - if (s and 0x04 != 0) { + var biomeX = sX + if (minXYZ and 0x04 != 0) { biomeX++ } - var biomeY = q - if (s and 0x02 != 0) { + var biomeY = sY + if (minXYZ and 0x02 != 0) { biomeY++ } - var biomeZ = r - if (s and 0x01 != 0) { + var biomeZ = sZ + if (minXYZ and 0x01 != 0) { biomeZ++ } @@ -103,32 +108,32 @@ class VoronoiBiomeAccessor( } - private fun calculateFiddle(seed: Long, x: Int, y: Int, z: Int, xFraction: Double, yFraction: Double, zFraction: Double): Double { - var ret = seed + private fun noiseDistance(seed: Long, x: Int, y: Int, z: Int, offsetX: Double, offsetY: Double, offsetZ: Double): Double { + var ret = mix(seed, x, y, z) - ret = next(ret, x) - ret = next(ret, y) - ret = next(ret, z) - ret = next(ret, x) - ret = next(ret, y) - ret = next(ret, z) + val noiseX = nextNoiseOffset(ret); ret = next(ret, seed) + val noiseY = nextNoiseOffset(ret); ret = next(ret, seed) + val noiseZ = nextNoiseOffset(ret) - val xSalt = distribute(ret) - - ret = next(ret, seed) - - val ySalt = distribute(ret) - - ret = next(ret, seed) - - val zSalt = distribute(ret) - - return (xFraction + xSalt).square() + (yFraction + ySalt).square() + (zFraction + zSalt).square() + return (offsetX + noiseX).square() + (offsetY + noiseY).square() + (offsetZ + noiseZ).square() } - private fun distribute(seed: Long): Double { - val d = Math.floorMod(seed shr 24, 1024L).toInt() / 1024.0 - return (d - 0.5) * 0.9 + private fun mix(seed: Long, x: Int, y: Int, z: Int): Long { + var mixed = seed + mixed = next(mixed, x) + mixed = next(mixed, y) + mixed = next(mixed, z) + mixed = next(mixed, x) + mixed = next(mixed, y) + mixed = next(mixed, z) + return mixed + } + + private fun nextNoiseOffset(seed: Long): Double { + val floor = Math.floorMod(seed shr 24, SpatialBiomeArray.SIZE.toLong()).toInt() + val double = (floor - (SpatialBiomeArray.SIZE / 2)) / SpatialBiomeArray.SIZE.toDouble() + + return double * 0.9 } // https://en.wikipedia.org/wiki/Linear_congruential_generator