biomes: correct noise biome accessor

This commit is contained in:
Bixilon 2021-03-27 17:47:44 +01:00
parent 56eec8fb73
commit eaca291c96
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
19 changed files with 284 additions and 52 deletions

View File

@ -13,7 +13,7 @@
package de.bixilon.minosoft.data.world
import de.bixilon.minosoft.data.mappings.blocks.BlockState
import de.bixilon.minosoft.data.world.biome.BiomeAccessor
import de.bixilon.minosoft.data.world.biome.source.BiomeSource
import de.bixilon.minosoft.data.world.light.LightAccessor
import java.util.*
@ -22,13 +22,13 @@ import java.util.*
*/
class Chunk(
var sections: MutableMap<Int, ChunkSection>? = null,
var biomeAccessor: BiomeAccessor? = null,
var biomeSource: BiomeSource? = null,
var lightAccessor: LightAccessor? = null,
) {
private val lock = Object()
val isFullyLoaded: Boolean
get() {
return sections != null && biomeAccessor != null && lightAccessor != null
return sections != null && biomeSource != null && lightAccessor != null
}
fun getBlockState(position: InChunkPosition): BlockState? {
@ -59,8 +59,8 @@ class Chunk(
getSectionOrCreate(sectionHeight).setData(chunkSection)
}
}
data.biomeAccessor?.let {
this.biomeAccessor = it
data.biomeSource?.let {
this.biomeSource = it
}
data.lightAccessor?.let {
this.lightAccessor = it

View File

@ -13,12 +13,12 @@
package de.bixilon.minosoft.data.world
import de.bixilon.minosoft.data.world.biome.BiomeAccessor
import de.bixilon.minosoft.data.world.biome.source.BiomeSource
import de.bixilon.minosoft.data.world.light.LightAccessor
data class ChunkData(
var blocks: Map<Int, ChunkSection>? = null,
var biomeAccessor: BiomeAccessor? = null,
var biomeSource: BiomeSource? = null,
var lightAccessor: LightAccessor? = null,
) {
@ -26,8 +26,8 @@ data class ChunkData(
data.blocks?.let {
this.blocks = it
}
data.biomeAccessor?.let {
this.biomeAccessor = it
data.biomeSource?.let {
this.biomeSource = it
}
data.lightAccessor?.let {
this.lightAccessor = it

View File

@ -46,6 +46,10 @@ data class InChunkPosition(val x: Int, val y: Int, val z: Int) {
return this + direction?.directionVector
}
fun toVec3i(): Vec3i {
return Vec3i(x, y, z)
}
override fun toString(): String {
return "($x $y $z)"
}

View File

@ -17,7 +17,10 @@ import de.bixilon.minosoft.data.Difficulties
import de.bixilon.minosoft.data.entities.block.BlockEntityMetaData
import de.bixilon.minosoft.data.entities.entities.Entity
import de.bixilon.minosoft.data.mappings.Dimension
import de.bixilon.minosoft.data.mappings.biomes.Biome
import de.bixilon.minosoft.data.mappings.blocks.BlockState
import de.bixilon.minosoft.data.world.biome.accessor.BiomeAccessor
import de.bixilon.minosoft.data.world.biome.accessor.NullBiomeAccessor
import de.bixilon.minosoft.data.world.light.WorldLightAccessor
import java.util.*
import java.util.concurrent.ConcurrentHashMap
@ -25,7 +28,7 @@ import java.util.concurrent.ConcurrentHashMap
/**
* Collection of chunks and more
*/
class World {
class World : BiomeAccessor {
val chunks = ConcurrentHashMap<ChunkPosition, Chunk>()
val entityIdMap = HashBiMap.create<Int, Entity>()
val entityUUIDMap = HashBiMap.create<UUID, Entity>()
@ -35,6 +38,8 @@ class World {
var difficulty: Difficulties? = null
var difficultyLocked = false
val worldLightAccessor = WorldLightAccessor(this)
var hashedSeed = 0L
var biomeAccessor: BiomeAccessor = NullBiomeAccessor
fun getBlockState(blockPosition: BlockPosition): BlockState? {
val chunkLocation = blockPosition.getChunkPosition()
@ -107,4 +112,8 @@ class World {
setBlockEntityData(blockPosition, entityMetaData)
}
}
override fun getBiome(blockPosition: BlockPosition): Biome? {
return biomeAccessor.getBiome(blockPosition)
}
}

View File

@ -11,12 +11,12 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.biome
package de.bixilon.minosoft.data.world.biome.accessor
import de.bixilon.minosoft.data.mappings.biomes.Biome
import de.bixilon.minosoft.data.world.BlockPosition
interface BiomeAccessor {
fun getBiome(position: BlockPosition, is3d: Boolean = true): Biome?
fun getBiome(blockPosition: BlockPosition): Biome?
}

View File

@ -0,0 +1,26 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.biome.accessor
import de.bixilon.minosoft.data.mappings.biomes.Biome
import de.bixilon.minosoft.data.world.BlockPosition
import de.bixilon.minosoft.data.world.World
class BlockBiomeAccessor(private val world: World) : BiomeAccessor {
override fun getBiome(blockPosition: BlockPosition): Biome? {
val biomePosition = blockPosition.getInChunkPosition().toVec3i()
return world.getChunk(blockPosition.getChunkPosition())?.biomeSource?.getBiome(biomePosition)
}
}

View File

@ -11,29 +11,22 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.biome
package de.bixilon.minosoft.data.world.biome.accessor
import de.bixilon.minosoft.data.mappings.biomes.Biome
import de.bixilon.minosoft.data.world.BlockPosition
import de.bixilon.minosoft.data.world.World
import de.bixilon.minosoft.data.world.biome.noise.FuzzyNoiseBiomeCalculator
class NoiseBiomeAccessor(
private val biomes: Array<Biome>,
) : BiomeAccessor {
class NoiseBiomeAccessor(private val world: World) : BiomeAccessor {
private val blockBiomeAccessor = BlockBiomeAccessor(world)
override fun getBiome(position: BlockPosition, is3d: Boolean): Biome? {
val inChunk = position.getInChunkSectionPosition()
val y = if (is3d) {
inChunk.y / 4 * 16
override fun getBiome(blockPosition: BlockPosition): Biome? {
val y = if (world.dimension?.supports3DBiomes == true) {
blockPosition.y
} else {
0
}
val index = (y + ((inChunk.z / 4 * 4) + (inChunk.x / 4)))
if (index < 0 || index > biomes.size) {
return null
}
return biomes[index]
// ToDo: This value is pseudo randomly generated. It depends on the seed of the world (received in join game).
return FuzzyNoiseBiomeCalculator.getBiome(world.hashedSeed, blockPosition.x, y, blockPosition.z, world)
}
}

View File

@ -11,14 +11,14 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.biome
package de.bixilon.minosoft.data.world.biome.accessor
import de.bixilon.minosoft.data.mappings.biomes.Biome
import de.bixilon.minosoft.data.world.BlockPosition
class DummyBiomeAccessor(private val biome: Biome) : BiomeAccessor {
object NullBiomeAccessor : BiomeAccessor {
override fun getBiome(position: BlockPosition, is3d: Boolean): Biome {
return biome
override fun getBiome(blockPosition: BlockPosition): Biome? {
return null
}
}

View File

@ -0,0 +1,104 @@
package de.bixilon.minosoft.data.world.biome.noise
import de.bixilon.minosoft.data.mappings.biomes.Biome
import de.bixilon.minosoft.data.world.ChunkPosition
import de.bixilon.minosoft.data.world.World
import de.bixilon.minosoft.util.MMath.square
import glm_.vec3.Vec3i
object FuzzyNoiseBiomeCalculator {
fun getBiome(seed: Long, x: Int, y: Int, z: Int, world: World): Biome? {
val m = x - 2
val n = y - 2
val o = z - 2
val p = m shr 2
val q = n shr 2
val r = o shr 2
val d = (m and 0x03) / 4.0
val e = (n and 0x03) / 4.0
val f = (o and 0x03) / 4.0
var s = 0
var g = Double.POSITIVE_INFINITY
fun calculateFraction(a: Int, mask: Int, first: Int, second: Double): Pair<Int, Double> {
(a and mask == 0).let {
return Pair(
if (it) first else first + 1,
if (it) second else second - 1.0
)
}
}
fun checkMask(mask: Int, value: Int): Int {
return if (s and mask == 0) {
value
} else {
value + 1
}
}
for (i in 0 until 8) {
val (u, xFraction) = calculateFraction(i, 0x04, p, d)
val (v, yFraction) = calculateFraction(i, 0x02, q, e)
val (w, zFraction) = calculateFraction(i, 0x01, r, f)
val d3 = calculateFiddle(seed, u, v, w, xFraction, yFraction, zFraction)
if (g > d3) {
s = i
g = d3
}
}
val biomeX = checkMask(0x04, p)
val biomeY = checkMask(0x02, q)
val biomeZ = checkMask(0x01, r)
return world.getChunk(ChunkPosition(biomeX shr 2, biomeZ shr 2))?.biomeSource?.getBiome(Vec3i(biomeX, biomeY, biomeZ))
}
private fun calculateFiddle(seed: Long, x: Int, y: Int, z: Int, xFraction: Double, yFraction: Double, zFraction: Double): Double {
var ret = seed
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 xFractionSalt = distribute(ret)
ret = next(ret, seed)
val yFractionSalt = distribute(ret)
ret = next(ret, seed)
val zFractionSalt = distribute(ret)
return square(xFraction + xFractionSalt) + square(yFraction + yFractionSalt) + square(zFraction + zFractionSalt)
}
private fun distribute(seed: Long): Double {
val d = Math.floorMod(seed shr 24, 1024L).toInt() / 1024.0
return (d - 0.5) * 0.9
}
// https://en.wikipedia.org/wiki/Linear_congruential_generator
private fun next(seed: Long): Long {
return seed * (seed * 6364136223846793005L + 1442695040888963407L)
}
private fun next(seed: Long, salt: Int): Long {
return next(seed) + salt
}
private fun next(seed: Long, salt: Long): Long {
return next(seed) + salt
}
}

View File

@ -0,0 +1,22 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.biome.source
import de.bixilon.minosoft.data.mappings.biomes.Biome
import glm_.vec3.Vec3i
interface BiomeSource {
fun getBiome(position: Vec3i): Biome?
}

View File

@ -11,16 +11,14 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.biome
package de.bixilon.minosoft.data.world.biome.source
import de.bixilon.minosoft.data.mappings.biomes.Biome
import de.bixilon.minosoft.data.world.BlockPosition
import glm_.vec3.Vec3i
class XZBiomeAccessor(
private val biomes: Array<Biome>,
) : BiomeAccessor {
class DummyBiomeSource(private val biome: Biome) : BiomeSource {
override fun getBiome(position: BlockPosition, is3d: Boolean): Biome {
return biomes[(position.x and 0x0F) or ((position.z and 0x0F) shl 4)]
override fun getBiome(position: Vec3i): Biome {
return biome
}
}

View File

@ -0,0 +1,37 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.biome.source
import de.bixilon.minosoft.data.mappings.biomes.Biome
import de.bixilon.minosoft.util.MMath
import glm_.vec3.Vec3i
class SpatialBiomeArray(private val data: Array<Biome>) : BiomeSource {
override fun getBiome(position: Vec3i): Biome {
val index = (MMath.clamp(position.y, 0, Y_BIT_MASK)) shl 2 * X_SECTION_COUNT + X_SECTION_COUNT or
((position.z and X_BIT_MASK) shl X_SECTION_COUNT) or
(position.x and X_BIT_MASK)
return this.data[index]
}
companion object {
private const val X_SECTION_COUNT = 2
private const val Y_SECTION_COUNT = 6
private const val X_BIT_MASK = (1 shl X_SECTION_COUNT) - 1
private const val Y_BIT_MASK = (1 shl Y_SECTION_COUNT) - 1
}
}

View File

@ -0,0 +1,28 @@
/*
* Minosoft
* Copyright (C) 2021 Moritz Zwerger
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.If not, see <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.biome.source
import de.bixilon.minosoft.data.mappings.biomes.Biome
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import glm_.vec3.Vec3i
class XZBiomeArray(private val biomes: Array<Biome>) : BiomeSource {
init {
check(biomes.size == ProtocolDefinition.SECTION_WIDTH_X * ProtocolDefinition.SECTION_WIDTH_Z) { "Biome array size does not match the xz block count!" }
}
override fun getBiome(position: Vec3i): Biome {
return biomes[(position.x and 0x0F) or ((position.z and 0x0F) shl 4)]
}
}

View File

@ -206,14 +206,14 @@ class Camera(
headLocation = Position(cameraPosition)
feetLocation = Position(headLocation.x, headLocation.y - PLAYER_HEIGHT, headLocation.z)
blockPosition = feetLocation.toBlockPosition()
currentBiome = connection.player.world.getChunk(blockPosition.getChunkPosition())?.biomeAccessor?.getBiome(blockPosition, connection.player.world.dimension?.supports3DBiomes ?: false)
currentBiome = connection.player.world.getBiome(blockPosition)
chunkPosition = blockPosition.getChunkPosition()
sectionHeight = blockPosition.getSectionHeight()
inChunkSectionPosition = blockPosition.getInChunkSectionPosition()
// recalculate sky color for current biome
val blockPosition = Position(cameraPosition).toBlockPosition()
renderWindow.setSkyColor(connection.player.world.getChunk(blockPosition.getChunkPosition())?.biomeAccessor?.getBiome(blockPosition, connection.player.world.dimension?.supports3DBiomes ?: false)?.skyColor ?: RenderConstants.DEFAULT_SKY_COLOR)
renderWindow.setSkyColor(connection.player.world.getBiome(blockPosition)?.skyColor ?: RenderConstants.DEFAULT_SKY_COLOR)
frustum.recalculate()
renderWindow.worldRenderer.recalculateVisibleChunks()

View File

@ -54,8 +54,6 @@ class WorldRenderer(
queuedChunks.remove(chunkPosition)
}
val chunk = world.getChunk(chunkPosition) ?: error("Chunk in world is null at $chunkPosition?")
val dimensionSupports3dBiomes = connection.player.world.dimension?.supports3DBiomes ?: false
val meshCollection = ChunkMeshCollection()
for ((sectionHeight, section) in sections) {
@ -70,7 +68,8 @@ class WorldRenderer(
neighborBlocks[direction.ordinal] = world.getBlockState(blockPosition + direction)
}
val biome = chunk.biomeAccessor!!.getBiome(blockPosition, dimensionSupports3dBiomes)
val biome = world.getBiome(blockPosition)
var tintColor: RGBColor? = null

View File

@ -17,7 +17,7 @@ import de.bixilon.minosoft.data.mappings.tweaker.VersionTweaker
import de.bixilon.minosoft.data.world.BlockPosition
import de.bixilon.minosoft.data.world.ChunkData
import de.bixilon.minosoft.data.world.ChunkPosition
import de.bixilon.minosoft.data.world.biome.NoiseBiomeAccessor
import de.bixilon.minosoft.data.world.biome.source.SpatialBiomeArray
import de.bixilon.minosoft.modding.event.events.BlockEntityMetaDataChangeEvent
import de.bixilon.minosoft.modding.event.events.ChunkDataChangeEvent
import de.bixilon.minosoft.protocol.network.Connection
@ -83,7 +83,7 @@ class PacketChunkData : ClientboundPacket() {
heightMap = buffer.readNBT() as CompoundTag
}
if (!isFullChunk) {
chunkData!!.biomeAccessor = NoiseBiomeAccessor(buffer.readBiomeArray())
chunkData!!.biomeSource = SpatialBiomeArray(buffer.readBiomeArray())
}
val size = buffer.readVarInt()
val lastPos = buffer.position

View File

@ -19,6 +19,8 @@ import de.bixilon.minosoft.data.LevelTypes
import de.bixilon.minosoft.data.entities.entities.player.PlayerEntity
import de.bixilon.minosoft.data.mappings.Dimension
import de.bixilon.minosoft.data.mappings.ResourceLocation
import de.bixilon.minosoft.data.world.biome.accessor.BlockBiomeAccessor
import de.bixilon.minosoft.data.world.biome.accessor.NoiseBiomeAccessor
import de.bixilon.minosoft.modding.event.events.JoinGameEvent
import de.bixilon.minosoft.protocol.network.Connection
import de.bixilon.minosoft.protocol.packets.ClientboundPacket
@ -142,6 +144,12 @@ class PacketJoinGame : ClientboundPacket() {
val entity = PlayerEntity(connection, entityId, connection.player.playerUUID, null, null, connection.player.playerName, null, null)
connection.player.entity = entity
connection.player.world.addEntity(entity)
connection.player.world.hashedSeed = hashedSeed
connection.player.world.biomeAccessor = if (connection.version.versionId < ProtocolVersions.V_19W36A) {
BlockBiomeAccessor(connection.player.world)
} else {
NoiseBiomeAccessor(connection.player.world)
}
connection.sender.sendChatMessage("I am alive! ~ Minosoft")
}

View File

@ -62,4 +62,8 @@ object MMath {
}
return intValue
}
fun square(d: Double): Double {
return d * d
}
}

View File

@ -18,7 +18,7 @@ import de.bixilon.minosoft.data.mappings.biomes.Biome
import de.bixilon.minosoft.data.mappings.blocks.BlockState
import de.bixilon.minosoft.data.world.ChunkData
import de.bixilon.minosoft.data.world.ChunkSection
import de.bixilon.minosoft.data.world.biome.XZBiomeAccessor
import de.bixilon.minosoft.data.world.biome.source.XZBiomeArray
import de.bixilon.minosoft.data.world.light.DummyLightAccessor
import de.bixilon.minosoft.data.world.palette.Palette.Companion.choosePalette
import de.bixilon.minosoft.protocol.protocol.InByteBuffer
@ -53,7 +53,7 @@ object ChunkUtil {
}
val addBlockData = buffer.readBytes(addBitMask.cardinality() * (ProtocolDefinition.BLOCKS_PER_SECTION / 2))
if (isFullChunk) {
chunkData.biomeAccessor = readLegacyBiomeArray(buffer)
chunkData.biomeSource = readLegacyBiomeArray(buffer)
}
// parse data
@ -123,7 +123,7 @@ object ChunkUtil {
skyLight = buffer.readBytes(totalHalfEntries)
}
if (isFullChunk) {
chunkData.biomeAccessor = readLegacyBiomeArray(buffer)
chunkData.biomeSource = readLegacyBiomeArray(buffer)
}
var arrayPos = 0
@ -200,14 +200,14 @@ object ChunkUtil {
chunkData.blocks = sectionMap
if (buffer.versionId < V_19W36A && isFullChunk) {
chunkData.biomeAccessor = readLegacyBiomeArray(buffer)
chunkData.biomeSource = readLegacyBiomeArray(buffer)
}
return chunkData
}
private fun readLegacyBiomeArray(buffer: InByteBuffer): XZBiomeAccessor {
private fun readLegacyBiomeArray(buffer: InByteBuffer): XZBiomeArray {
val biomes: MutableList<Biome> = mutableListOf()
for (i in 0 until ProtocolDefinition.SECTION_WIDTH_X * ProtocolDefinition.SECTION_WIDTH_Z) {
biomes.add(i, buffer.connection.mapping.biomeRegistry.get(if (buffer.versionId < V_15W35A) {
@ -216,6 +216,6 @@ object ChunkUtil {
buffer.readInt()
}))
}
return XZBiomeAccessor(biomes.toTypedArray())
return XZBiomeArray(biomes.toTypedArray())
}
}