refactor noise biome accessor, tests

This commit is contained in:
Moritz Zwerger 2023-12-06 16:08:54 +01:00
parent d4682fe0dc
commit cc5b2f8006
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
25 changed files with 332 additions and 83 deletions

View File

@ -20,6 +20,8 @@ import de.bixilon.kutil.observer.DataObserver
import de.bixilon.kutil.reflection.ReflectionUtil.forceSet
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.registries.dimension.DimensionProperties
import de.bixilon.minosoft.data.world.biome.WorldBiomes
import de.bixilon.minosoft.data.world.biome.source.BiomeSource
import de.bixilon.minosoft.data.world.border.WorldBorder
import de.bixilon.minosoft.data.world.chunk.ChunkSection.Companion.getIndex
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
@ -48,14 +50,16 @@ object WorldTestUtil {
world::entities.forceSet(WorldEntities())
world::view.forceSet(TEST_WORLD_VIEW)
world::time.forceSet(DataObserver(WorldTime()))
world::biomes.forceSet(WorldBiomes(world))
return world
}
fun World.initialize(size: Int) {
fun World.initialize(size: Int, biome: (ChunkPosition) -> BiomeSource) {
for (x in -size..size) {
for (z in -size..size) {
chunks.create(ChunkPosition(x, z))
val position = ChunkPosition(x, z)
chunks.create(position, biome.invoke(position))
}
}
}

View File

@ -0,0 +1,149 @@
/*
* Minosoft
* Copyright (C) 2020-2023 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
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.registries.identified.Namespaces.minosoft
import de.bixilon.minosoft.data.world.World
import de.bixilon.minosoft.data.world.WorldTestUtil.initialize
import de.bixilon.minosoft.data.world.biome.accessor.noise.NoiseBiomeAccessor
import de.bixilon.minosoft.data.world.biome.source.BiomeSource
import de.bixilon.minosoft.data.world.biome.source.DummyBiomeSource
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
import de.bixilon.minosoft.data.world.positions.BlockPosition
import de.bixilon.minosoft.data.world.positions.ChunkPosition
import de.bixilon.minosoft.data.world.positions.InChunkPosition
import de.bixilon.minosoft.protocol.network.connection.play.ConnectionTestUtil.createConnection
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import org.testng.Assert.*
import org.testng.annotations.Test
@Test(groups = ["biomes"])
class WorldBiomesTest {
private val b1 = Biome(minosoft("b1"), 0.0f, 0.0f)
private val b2 = Biome(minosoft("b2"), 0.0f, 0.0f)
private val b3 = Biome(minosoft("b3"), 0.0f, 0.0f)
private fun create(noise: ((PlayConnection) -> NoiseBiomeAccessor)?, source: (ChunkPosition) -> BiomeSource): World {
val connection = createConnection(0)
connection.world.biomes.noise = noise?.invoke(connection)
connection.world.initialize(1, source)
return connection.world
}
fun `simple biome getting at origin chunk`() {
val world = create(null) { if (it.x == 0 && it.y == 0) PositionedSource(InChunkPosition(1, 2, 3), b1, b3) else DummyBiomeSource(b2) }
assertEquals(world.biomes[1, 2, 3], b1)
assertEquals(world.biomes[1, 2, 4], b3)
assertEquals(world.biomes[16, 2, 4], b2)
assertEquals(world.biomes[BlockPosition(1, 2, 3)], b1)
assertEquals(world.biomes[BlockPosition(1, 2, 4)], b3)
assertEquals(world.biomes[BlockPosition(16, 2, 4)], b2)
val chunk = world.chunks[0, 0]!!
assertEquals(world.biomes.getBiome(1, 2, 3, chunk), b1)
assertEquals(world.biomes.getBiome(1, 2, 4, chunk), b3)
assertEquals(chunk.getBiome(1, 2, 3), b1)
assertEquals(chunk.getBiome(1, 2, 4), b3)
}
fun `simple biome getting at chunk -1, -1`() {
val world = create(null) { if (it.x == -1 && it.y == -1) PositionedSource(InChunkPosition(15, 2, 14), b1, b3) else DummyBiomeSource(b2) }
assertEquals(world.biomes[-1, 2, -2], b1)
assertEquals(world.biomes[-1, 2, -6], b3)
assertEquals(world.biomes[1, 2, 6], b2)
assertEquals(world.biomes[BlockPosition(-1, 2, -2)], b1)
val chunk = world.chunks[-1, -1]!!
assertEquals(world.biomes.getBiome(15, 2, 14, chunk), b1)
assertEquals(world.biomes.getBiome(15, 2, 13, chunk), b3)
assertEquals(chunk.getBiome(15, 2, 14), b1)
assertEquals(chunk.getBiome(15, 2, 13), b3)
}
fun `ensure no caching is done without noise`() {
val source = CounterSource(b1)
val world = create(null) { source }
val chunk = world.chunks[0, 0]!!
chunk.getOrPut(0)
assertEquals(source.counter, 0)
assertEquals(world.biomes[1, 2, 3], b1)
assertEquals(source.counter, 1)
assertFalse(chunk.cacheBiomes)
assertEquals(chunk[0]!!.biomes[1, 2, 3], null)
}
fun `ensure caching is done with noise`() {
val source = CounterSource(b1)
val world = create({ FastNoiseAccessor(it) }) { source }
val chunk = world.chunks[0, 0]!!
chunk.getOrPut(0)
assertEquals(source.counter, 4096)
assertEquals(world.biomes[1, 2, 3], b1)
assertEquals(source.counter, 4096)
assertTrue(chunk.cacheBiomes)
assertEquals(chunk[0]!!.biomes[1, 2, 3], b1)
}
fun `ensure world position is converted to chunk position`() {
val source = VerifyPositionSource(b1)
val world = create(null) { source }
assertEquals(world.biomes[1, 2, 3], b1)
assertEquals(world.biomes[16, 2, 4], b1)
assertEquals(world.biomes[-4, 2, -4], b1)
assertEquals(world.biomes[-4, -2, -4], b1)
assertEquals(world.biomes[-4, 1024, -4], b1)
}
private class PositionedSource(
val position: InChunkPosition,
val biome: Biome?,
val fallback: Biome?,
) : BiomeSource {
override fun getBiome(x: Int, y: Int, z: Int): Biome? {
if (x != position.x || y != position.y || z != position.z) return fallback
return biome
}
}
private class CounterSource(val biome: Biome?) : BiomeSource {
var counter = 0
override fun getBiome(x: Int, y: Int, z: Int): Biome? {
counter++
return biome
}
}
private class VerifyPositionSource(val biome: Biome?) : BiomeSource {
override fun getBiome(x: Int, y: Int, z: Int): Biome? {
if (x < 0 || x > 15) throw IllegalArgumentException("Invalid x: $x")
if (y < 0 || y > 255) throw IllegalArgumentException("Invalid y: $y")
if (z < 0 || z > 15) throw IllegalArgumentException("Invalid z: $z")
return biome
}
}
private class FastNoiseAccessor(connection: PlayConnection) : NoiseBiomeAccessor(connection, 0L) {
override fun get(x: Int, y: Int, z: Int, chunkPositionX: Int, chunkPositionZ: Int, chunk: Chunk, neighbours: Array<Chunk>?): Biome? {
return chunk.biomeSource.getBiome(x, y, z)
}
}
}

View File

@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
* Copyright (C) 2020-2023 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.
*
@ -14,12 +14,14 @@
package de.bixilon.minosoft.data.world.biome.accessor
import de.bixilon.kotlinglm.vec3.Vec3i
import de.bixilon.minosoft.data.world.biome.accessor.noise.VoronoiBiomeAccessor
import de.bixilon.minosoft.test.ITUtil.allocate
import org.objenesis.ObjenesisStd
import org.testng.Assert.assertEquals
import org.testng.annotations.Test
@Test(groups = ["biome"])
class NoiseBiomeAccessorTest {
class VoronoiBiomeAccessorTest {
private val OBJENESIS = ObjenesisStd()
fun testBiomeNoise1() {
@ -51,7 +53,7 @@ class NoiseBiomeAccessorTest {
}
private fun calculate(x: Int, y: Int, z: Int, seed: Long): Vec3i {
val accessor = OBJENESIS.newInstance(NoiseBiomeAccessor::class.java)
val accessor = VoronoiBiomeAccessor::class.java.allocate()
return accessor.getBiomePosition(seed, x, y, z)
}
}

View File

@ -21,6 +21,7 @@ import de.bixilon.kutil.reflection.ReflectionUtil.jvmField
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.registries.dimension.DimensionProperties
import de.bixilon.minosoft.data.world.World
import de.bixilon.minosoft.data.world.biome.WorldBiomes
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
import de.bixilon.minosoft.data.world.chunk.light.ChunkLight
import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours
@ -48,6 +49,7 @@ object LightTestingUtil {
val world = objenesis.newInstance(World::class.java)
world::dimension.forceSet(DataObserver(DimensionProperties(skyLight = true)))
world::connection.forceSet(createConnection())
world::biomes.forceSet(WorldBiomes(world))
return world
}

View File

@ -17,7 +17,7 @@ import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.registries.blocks.types.stone.StoneTest0
import de.bixilon.minosoft.data.registries.identified.Namespaces.minosoft
import de.bixilon.minosoft.data.world.biome.accessor.NoiseBiomeAccessor
import de.bixilon.minosoft.data.world.biome.accessor.noise.VoronoiBiomeAccessor
import de.bixilon.minosoft.data.world.biome.source.BiomeSource
import de.bixilon.minosoft.data.world.biome.source.DummyBiomeSource
import de.bixilon.minosoft.data.world.biome.source.SpatialBiomeArray
@ -428,7 +428,7 @@ class ChunkManagerTest {
fun noBiomeCache() {
val manager = create()
manager.world.cacheBiomeAccessor = null
manager.world.biomes.noise = null
val matrix = manager.createMatrix()
val chunk = matrix[1][1]
@ -438,7 +438,7 @@ class ChunkManagerTest {
fun noiseBiomeCache() {
val manager = create()
val biome = Biome(minosoft("test"), 0.0f, 0.0f, null, null, null, null, null)
manager.world.cacheBiomeAccessor = NoiseBiomeAccessor(manager.world.connection, 0L)
manager.world.biomes.noise = VoronoiBiomeAccessor(manager.world.connection, 0L)
val source = SpatialBiomeArray(Array(1024) { biome })
val matrix = manager.createMatrix(source)
@ -446,7 +446,7 @@ class ChunkManagerTest {
assertEquals(chunk.getOrPut(0)!!.biomes.count, 4096)
assertEquals(chunk.getOrPut(0)!!.biomes[0], biome)
assertEquals(manager.world.getBiome(BlockPosition(5, 5, 5)), biome)
assertEquals(manager.world.biomes.getBiome(BlockPosition(5, 5, 5)), biome)
assertEquals(chunk.getBiome(BlockPosition(5, 5, 5)), biome)
}
}

View File

@ -30,6 +30,7 @@ import de.bixilon.minosoft.data.language.lang.LanguageList
import de.bixilon.minosoft.data.registries.registries.Registries
import de.bixilon.minosoft.data.world.WorldTestUtil.createWorld
import de.bixilon.minosoft.data.world.WorldTestUtil.initialize
import de.bixilon.minosoft.data.world.biome.source.DummyBiomeSource
import de.bixilon.minosoft.modding.event.master.EventMaster
import de.bixilon.minosoft.protocol.network.network.client.test.TestNetwork
import de.bixilon.minosoft.protocol.versions.Versions
@ -93,7 +94,7 @@ object ConnectionTestUtil {
CAMERA.forceSet(connection, ConnectionCamera(connection))
connection.camera.init()
if (worldSize > 0) {
connection.world.initialize(worldSize)
connection.world.initialize(worldSize) { DummyBiomeSource(null) }
}
return connection
}

View File

@ -120,7 +120,7 @@ abstract class DoublePlant(identifier: ResourceLocation, settings: BlockSettings
override val tintProvider: TintProvider? = null
override fun initTint(manager: TintManager) {
this::tintProvider.forceSet(TallGrassTintCalculator(manager.grassTintCalculator))
this::tintProvider.forceSet(TallGrassTintCalculator(manager.grass))
}
companion object : BlockFactory<TallGrass> {
@ -135,7 +135,7 @@ abstract class DoublePlant(identifier: ResourceLocation, settings: BlockSettings
override val tintProvider: TintProvider? = null
override fun initTint(manager: TintManager) {
this::tintProvider.forceSet(TallGrassTintCalculator(manager.grassTintCalculator))
this::tintProvider.forceSet(TallGrassTintCalculator(manager.grass))
}
companion object : BlockFactory<LargeFern> {
@ -173,7 +173,7 @@ abstract class DoublePlant(identifier: ResourceLocation, settings: BlockSettings
}
override fun initTint(manager: TintManager) {
this::tintProvider.forceSet(TallGrassTintCalculator(manager.grassTintCalculator)) // TODO: only tint if lower block is tinted
this::tintProvider.forceSet(TallGrassTintCalculator(manager.grass)) // TODO: only tint if lower block is tinted
}
private class Model : PickedBlockRender {

View File

@ -47,7 +47,7 @@ abstract class LeavesBlock(identifier: ResourceLocation, settings: BlockSettings
override val tintProvider: TintProvider? = null
override fun initTint(manager: TintManager) {
this::tintProvider.forceSet(manager.foliageTintCalculator)
this::tintProvider.forceSet(manager.foliage)
}
override fun buildState(version: Version, settings: BlockStateSettings): BlockState {

View File

@ -20,7 +20,6 @@ import de.bixilon.kutil.observer.DataObserver.Companion.observed
import de.bixilon.kutil.random.RandomUtil.nextInt
import de.bixilon.minosoft.data.entities.block.BlockEntity
import de.bixilon.minosoft.data.entities.entities.Entity
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.registries.blocks.types.properties.rendering.RandomDisplayTickable
import de.bixilon.minosoft.data.registries.dimension.DimensionProperties
@ -28,8 +27,7 @@ import de.bixilon.minosoft.data.registries.identified.ResourceLocation
import de.bixilon.minosoft.data.registries.shapes.aabb.AABB
import de.bixilon.minosoft.data.world.audio.AbstractAudioPlayer
import de.bixilon.minosoft.data.world.audio.WorldAudioPlayer
import de.bixilon.minosoft.data.world.biome.accessor.BiomeAccessor
import de.bixilon.minosoft.data.world.biome.accessor.NoiseBiomeAccessor
import de.bixilon.minosoft.data.world.biome.WorldBiomes
import de.bixilon.minosoft.data.world.border.WorldBorder
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
import de.bixilon.minosoft.data.world.chunk.light.ChunkLightUtil.hasSkyLight
@ -57,10 +55,10 @@ import java.util.*
*/
class World(
val connection: PlayConnection,
) : BiomeAccessor, WorldAudioPlayer, WorldParticleRenderer {
) : WorldAudioPlayer, WorldParticleRenderer {
val lock = SimpleLock()
val random = Random()
var cacheBiomeAccessor: NoiseBiomeAccessor? = null
val biomes = WorldBiomes(this)
val chunks = ChunkManager(this, 1000, 100)
val entities = WorldEntities()
var hardcore by observed(false)
@ -149,14 +147,6 @@ class World(
chunks[position.chunkPosition]?.set(position.inChunkPosition, entity) // TODO: fire event if needed
}
override fun getBiome(blockPosition: BlockPosition): Biome? {
return chunks[blockPosition.chunkPosition]?.getBiome(blockPosition.inChunkPosition)
}
override fun getBiome(x: Int, y: Int, z: Int): Biome? {
return this.chunks[Vec2i(x shr 4, z shr 4)]?.getBiome(x and 0x0F, y, z and 0x0F)
}
fun tick() {
val simulationDistance = view.simulationDistance
val cameraPosition = connection.player.physics.positionInfo.chunkPosition

View File

@ -16,7 +16,7 @@ package de.bixilon.minosoft.data.world.audio
import de.bixilon.kotlinglm.vec3.Vec3d
import de.bixilon.minosoft.data.registries.identified.ResourceLocation
@Deprecated("")
@Deprecated("use world.audio")
interface WorldAudioPlayer : AbstractAudioPlayer {
val audioPlayer: AbstractAudioPlayer?

View File

@ -0,0 +1,52 @@
/*
* Minosoft
* Copyright (C) 2020-2023 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
import de.bixilon.kotlinglm.func.common.clamp
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.world.World
import de.bixilon.minosoft.data.world.biome.accessor.BiomeAccessor
import de.bixilon.minosoft.data.world.biome.accessor.noise.NoiseBiomeAccessor
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
import de.bixilon.minosoft.data.world.positions.BlockPosition
import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight
class WorldBiomes(val world: World) : BiomeAccessor {
var noise: NoiseBiomeAccessor? = null
set(value) {
field = value
resetCache()
}
operator fun get(position: BlockPosition) = getBiome(position)
override fun getBiome(position: BlockPosition) = getBiome(position.x, position.y, position.z)
operator fun get(x: Int, y: Int, z: Int) = getBiome(x, y, z)
override fun getBiome(x: Int, y: Int, z: Int): Biome? {
val chunk = world.chunks[x shr 4, z shr 4] ?: return null
return getBiome(x and 0x0F, y.clamp(world.dimension.minY, world.dimension.maxY), z and 0x0F, chunk)
}
fun getBiome(x: Int, y: Int, z: Int, chunk: Chunk): Biome? {
val noise = this.noise ?: return chunk.biomeSource.getBiome(x, y, z)
chunk[y.sectionHeight]?.let { return it.biomes[x, y, z] } // access cache
return noise.get(x, y, z, chunk)
}
fun resetCache() {
// TODO
}
}

View File

@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
* Copyright (C) 2020-2023 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.
*
@ -19,8 +19,8 @@ import de.bixilon.minosoft.data.registries.biomes.Biome
interface BiomeAccessor {
fun getBiome(blockPosition: Vec3i): Biome? {
return getBiome(blockPosition.x, blockPosition.y, blockPosition.z)
fun getBiome(position: Vec3i): Biome? {
return getBiome(position.x, position.y, position.z)
}
fun getBiome(x: Int, y: Int, z: Int): Biome?

View File

@ -1,6 +1,6 @@
/*
* Minosoft
* Copyright (C) 2020-2022 Moritz Zwerger
* Copyright (C) 2020-2023 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.
*
@ -19,7 +19,7 @@ import de.bixilon.minosoft.data.registries.biomes.Biome
object NullBiomeAccessor : BiomeAccessor {
override fun getBiome(blockPosition: Vec3i): Biome? {
override fun getBiome(position: Vec3i): Biome? {
return null
}

View File

@ -0,0 +1,49 @@
/*
* Minosoft
* Copyright (C) 2020-2023 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.noise
import de.bixilon.kutil.observer.DataObserver.Companion.observe
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions.V_19W36A
abstract class NoiseBiomeAccessor(
connection: PlayConnection,
val seed: Long = 0L,
) {
protected val world = connection.world
protected var fastNoise = false
init {
val profile = connection.profiles.rendering
profile.performance::fastBiomeNoise.observe(this, true) { fastNoise = it }
}
abstract fun get(x: Int, y: Int, z: Int, chunkPositionX: Int, chunkPositionZ: Int, chunk: Chunk, neighbours: Array<Chunk>?): Biome?
fun get(x: Int, y: Int, z: Int, chunk: Chunk): Biome? {
val neighbours = chunk.neighbours.get() ?: return null
return get((chunk.chunkPosition.x shl 4) or x, y, (chunk.chunkPosition.y shl 4) or z, chunk.chunkPosition.x, chunk.chunkPosition.y, chunk, neighbours)
}
companion object {
fun get(connection: PlayConnection, seed: Long): NoiseBiomeAccessor? = when {
connection.version < V_19W36A -> null
else -> VoronoiBiomeAccessor(connection, seed)
}
}
}

View File

@ -11,30 +11,22 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.data.world.biome.accessor
package de.bixilon.minosoft.data.world.biome.accessor.noise
import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.kotlinglm.vec3.Vec3i
import de.bixilon.kutil.math.simple.DoubleMath.square
import de.bixilon.kutil.observer.DataObserver.Companion.observe
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.world.biome.source.SpatialBiomeArray
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
class NoiseBiomeAccessor(
private val connection: PlayConnection,
var hashedSeed: Long = 0L,
) {
private val world = connection.world
private var fastNoise = false
open class VoronoiBiomeAccessor(
connection: PlayConnection,
seed: Long = 0L,
) : NoiseBiomeAccessor(connection, seed) {
init {
val profile = connection.profiles.rendering
profile.performance::fastBiomeNoise.observe(this, true) { fastNoise = it }
}
fun getBiome(x: Int, y: Int, z: Int, chunkPositionX: Int, chunkPositionZ: Int, chunk: Chunk, neighbours: Array<Chunk>?): Biome? {
override fun get(x: Int, y: Int, z: Int, chunkPositionX: Int, chunkPositionZ: Int, chunk: Chunk, neighbours: Array<Chunk>?): Biome? {
val biomeY = if (world.dimension.supports3DBiomes) y else 0
if (fastNoise) {
@ -44,7 +36,7 @@ class NoiseBiomeAccessor(
return source.data[(biomeY and 0x0F) shr 2 and 0x3F shl 4 or ((z and 0x0F) shr 2 and 0x03 shl 2) or ((x and 0x0F) shr 2 and 0x03)]
}
return getBiome(hashedSeed, x, biomeY, z, chunkPositionX, chunkPositionZ, chunk, neighbours)
return getBiome(seed, x, biomeY, z, chunkPositionX, chunkPositionZ, chunk, neighbours)
}
@ -156,17 +148,17 @@ class NoiseBiomeAccessor(
ret = next(ret, y)
ret = next(ret, z)
val xFractionSalt = distribute(ret)
val xSalt = distribute(ret)
ret = next(ret, seed)
val yFractionSalt = distribute(ret)
val ySalt = distribute(ret)
ret = next(ret, seed)
val zFractionSalt = distribute(ret)
val zSalt = distribute(ret)
return (xFraction + xFractionSalt).square() + (yFraction + yFractionSalt).square() + (zFraction + zFractionSalt).square()
return (xFraction + xSalt).square() + (yFraction + ySalt).square() + (zFraction + zSalt).square()
}
private fun distribute(seed: Long): Double {

View File

@ -19,7 +19,7 @@ import de.bixilon.kutil.cast.CastUtil.unsafeNull
import de.bixilon.kutil.reflection.ReflectionUtil.jvmField
import de.bixilon.minosoft.data.entities.block.BlockEntity
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.world.biome.accessor.NoiseBiomeAccessor
import de.bixilon.minosoft.data.world.biome.accessor.noise.NoiseBiomeAccessor
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
import de.bixilon.minosoft.data.world.chunk.light.SectionLight
import de.bixilon.minosoft.data.world.container.SectionDataProvider
@ -67,7 +67,7 @@ class ChunkSection(
}
}
fun buildBiomeCache(neighbours: Array<Chunk>, biomeAccessor: NoiseBiomeAccessor) {
fun buildBiomeCache(neighbours: Array<Chunk>, noise: NoiseBiomeAccessor) {
val chunkPositionX = chunk.chunkPosition.x
val chunkPositionZ = chunk.chunkPosition.y
val blockOffset = Vec3i.of(chunk.chunkPosition, sectionHeight)
@ -76,7 +76,7 @@ class ChunkSection(
val z = blockOffset.z
val biomes: Array<Biome?> = arrayOfNulls(ProtocolDefinition.BLOCKS_PER_SECTION)
for (index in 0 until ProtocolDefinition.BLOCKS_PER_SECTION) {
biomes[index] = biomeAccessor.getBiome(x + (index and 0x0F), y + ((index shr 8) and 0x0F), z + ((index shr 4) and 0x0F), chunkPositionX, chunkPositionZ, chunk, neighbours)
biomes[index] = noise.get(x + (index and 0x0F), y + ((index shr 8) and 0x0F), z + ((index shr 4) and 0x0F), chunkPositionX, chunkPositionZ, chunk, neighbours)
}
this.biomes.setData(biomes.cast())
}

View File

@ -52,7 +52,7 @@ class Chunk(
val light = ChunkLight(this)
val minSection = world.dimension.minSection
val maxSection = world.dimension.maxSection
val cacheBiomes = world.cacheBiomeAccessor != null
val cacheBiomes = world.biomes.noise != null
val neighbours = ChunkNeighbours(this)
@ -187,7 +187,7 @@ class Chunk(
section.blocks.unsafeSetSection(section)
val neighbours = this.neighbours.get()
if (neighbours != null) {
this.neighbours.completeSection(neighbours, section, sectionHeight, world.cacheBiomeAccessor)
this.neighbours.completeSection(neighbours, section, sectionHeight, world.biomes.noise)
}
sections[index] = section
@ -230,11 +230,10 @@ class Chunk(
override fun getBiome(x: Int, y: Int, z: Int): Biome? {
val y = y.clamp(world.dimension.minY, world.dimension.maxY)
if (cacheBiomes) {
val section = this[y.sectionHeight] ?: return connection.world.cacheBiomeAccessor?.getBiome((chunkPosition.x shl 4) or x, y, (chunkPosition.y shl 4) or z, chunkPosition.x, chunkPosition.y, this, this.neighbours.get())
return section.biomes[x, y.inSectionHeight, z]
if (!cacheBiomes) {
return biomeSource.getBiome(x, y, z)
}
return biomeSource.getBiome(x and 0x0F, y, z and 0x0F)
return connection.world.biomes.getBiome(x, y, z, this)
}
}

View File

@ -18,7 +18,7 @@ import de.bixilon.kotlinglm.vec3.Vec3i
import de.bixilon.kutil.cast.CastUtil.unsafeCast
import de.bixilon.kutil.exception.Broken
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.world.biome.accessor.NoiseBiomeAccessor
import de.bixilon.minosoft.data.world.biome.accessor.noise.NoiseBiomeAccessor
import de.bixilon.minosoft.data.world.chunk.ChunkSection
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
import de.bixilon.minosoft.data.world.positions.ChunkPositionUtil.chunkPosition
@ -70,19 +70,19 @@ class ChunkNeighbours(val chunk: Chunk) : Iterable<Chunk?> {
remove(getIndex(offset))
}
fun completeSection(neighbours: Array<Chunk>, section: ChunkSection, sectionHeight: SectionHeight, biomeCacheAccessor: NoiseBiomeAccessor?) {
fun completeSection(neighbours: Array<Chunk>, section: ChunkSection, sectionHeight: SectionHeight, noise: NoiseBiomeAccessor?) {
section.neighbours = ChunkUtil.getDirectNeighbours(neighbours, chunk, sectionHeight)
if (biomeCacheAccessor != null) {
section.buildBiomeCache(neighbours, biomeCacheAccessor)
if (noise != null) {
section.buildBiomeCache(neighbours, noise)
}
}
private fun complete(neighbours: Array<Chunk>) {
val biomeCacheAccessor = chunk.world.cacheBiomeAccessor
val noise = chunk.world.biomes.noise
for ((index, section) in chunk.sections.withIndex()) {
if (section == null) continue
val sectionHeight = index + chunk.minSection
completeSection(neighbours, section, sectionHeight, biomeCacheAccessor)
completeSection(neighbours, section, sectionHeight, noise)
}
chunk.light.recalculate(false)
chunk.light.propagateFromNeighbours(fireEvent = false, fireSameChunkEvent = false)

View File

@ -15,7 +15,7 @@ package de.bixilon.minosoft.data.world.particle
import de.bixilon.minosoft.gui.rendering.particle.types.Particle
@Deprecated("")
@Deprecated("use world.particle")
interface WorldParticleRenderer : AbstractParticleRenderer {
val particleRenderer: AbstractParticleRenderer?

View File

@ -28,10 +28,10 @@ import de.bixilon.minosoft.gui.rendering.tint.tints.redstone.RedstoneWireTintCal
object DefaultTints {
fun init(manager: TintManager) {
manager.applyTo(setOf(MinecraftBlocks.POTTED_FERN), manager.grassTintCalculator)
manager.applyTo(setOf(MinecraftBlocks.POTTED_FERN), manager.grass)
manager.applyTo(setOf(MinecraftBlocks.REDSTONE_WIRE), RedstoneWireTintCalculator)
manager.applyTo(setOf(MinecraftBlocks.WATER_CAULDRON, MinecraftBlocks.CAULDRON), WaterTintProvider)
manager.applyTo(setOf(MinecraftBlocks.SUGAR_CANE), SugarCaneTintCalculator(manager.grassTintCalculator))
manager.applyTo(setOf(MinecraftBlocks.SUGAR_CANE), SugarCaneTintCalculator(manager.grass))
manager.applyTo(setOf(MinecraftBlocks.ATTACHED_MELON_STEM, MinecraftBlocks.ATTACHED_PUMPKIN_STEM), StaticTintProvider(0xE0C71C))
manager.applyTo(setOf(MinecraftBlocks.MELON_STEM, MinecraftBlocks.PUMPKIN_STEM), StemTintCalculator)
manager.applyTo(setOf(MinecraftBlocks.LILY_PAD), StaticTintProvider(block = 0x208030, item = 0x71C35C))

View File

@ -28,12 +28,12 @@ import de.bixilon.minosoft.gui.rendering.tint.tints.plants.FoliageTintCalculator
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
class TintManager(val connection: PlayConnection) {
val grassTintCalculator = GrassTintCalculator()
val foliageTintCalculator = FoliageTintCalculator()
val grass = GrassTintCalculator()
val foliage = FoliageTintCalculator()
fun init(assetsManager: AssetsManager) {
grassTintCalculator.init(assetsManager)
foliageTintCalculator.init(assetsManager)
grass.init(assetsManager)
foliage.init(assetsManager)
for (block in connection.registries.block) {
if (block !is TintedBlock) continue
@ -61,7 +61,8 @@ class TintManager(val connection: PlayConnection) {
fun getParticleTint(state: BlockState, x: Int, y: Int, z: Int): Int? {
if (state.block !is TintedBlock) return null
val tintProvider = state.block.tintProvider ?: return null
val biome = connection.world.getBiome(x, y, z)
// TODO: cache chunk
val biome = connection.world.biomes.getBiome(x, y, z)
return tintProvider.getParticleColor(state, biome, x, y, z)
}

View File

@ -20,6 +20,6 @@ import de.bixilon.minosoft.gui.rendering.tint.TintedBlock
interface GrassTinted : TintedBlock {
override fun initTint(manager: TintManager) {
this::class.java.getDeclaredField("tintProvider").forceSet(this, manager.grassTintCalculator)
this::class.java.getDeclaredField("tintProvider").forceSet(this, manager.grass)
}
}

View File

@ -21,7 +21,7 @@ import de.bixilon.minosoft.data.entities.GlobalPosition
import de.bixilon.minosoft.data.entities.data.types.GlobalPositionEntityDataType
import de.bixilon.minosoft.data.registries.dimension.DimensionProperties
import de.bixilon.minosoft.data.registries.identified.ResourceLocation
import de.bixilon.minosoft.data.world.biome.accessor.NoiseBiomeAccessor
import de.bixilon.minosoft.data.world.biome.accessor.noise.NoiseBiomeAccessor
import de.bixilon.minosoft.data.world.difficulty.Difficulties
import de.bixilon.minosoft.modding.event.events.DimensionChangeEvent
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
@ -180,9 +180,7 @@ class InitializeS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
connection.world.entities.clear(connection, local = true)
connection.world.entities.add(entityId, null, playerEntity)
if (connection.version.versionId >= ProtocolVersions.V_19W36A && !connection.profiles.rendering.performance.fastBiomeNoise) {
connection.world.cacheBiomeAccessor = NoiseBiomeAccessor(connection, hashedSeed)
}
connection.world.biomes.noise = NoiseBiomeAccessor.get(connection, hashedSeed)
connection.world.border.reset()
if (connection.version < V_1_20_2_PRE1) {

View File

@ -18,6 +18,7 @@ import de.bixilon.minosoft.data.entities.GlobalPosition
import de.bixilon.minosoft.data.entities.data.types.GlobalPositionEntityDataType
import de.bixilon.minosoft.data.registries.dimension.DimensionProperties
import de.bixilon.minosoft.data.registries.identified.ResourceLocation
import de.bixilon.minosoft.data.world.biome.accessor.noise.NoiseBiomeAccessor
import de.bixilon.minosoft.data.world.difficulty.Difficulties
import de.bixilon.minosoft.modding.event.events.DimensionChangeEvent
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
@ -114,7 +115,7 @@ class RespawnS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
}
connection.world.dimension = dimension
connection.world.name = world
connection.world.cacheBiomeAccessor?.hashedSeed = hashedSeed
connection.world.biomes.noise = NoiseBiomeAccessor.get(connection, hashedSeed)
connection.state = PlayConnectionStates.SPAWNING
if (dimensionChange) {

View File

@ -13,6 +13,7 @@
package de.bixilon.minosoft.protocol.packets.s2c.play.block.chunk
import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.packets.s2c.PlayS2CPacket
import de.bixilon.minosoft.protocol.protocol.buffers.play.PlayInByteBuffer
import de.bixilon.minosoft.util.logging.Log
@ -23,7 +24,7 @@ class ChunkBiomeS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
val data = buffer.readArray { ChunkBiomeData(buffer.readLongChunkPosition(), buffer.readByteArray()) }
class ChunkBiomeData(
data class ChunkBiomeData(
val position: Vec2i,
val data: ByteArray,
)
@ -33,7 +34,15 @@ class ChunkBiomeS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket {
return Vec2i(long.toInt(), (long shr 32).toInt())
}
override fun handle(connection: PlayConnection) {
if (data.isEmpty()) return
for ((position, data) in data) {
val chunk = connection.world.chunks[position] ?: continue
// TODO: handle
}
connection.world.biomes.resetCache()
}
override fun log(reducedLog: Boolean) {
Log.log(LogMessageType.NETWORK_IN, level = LogLevels.VERBOSE) { "Chunk biome (data=$data)" }