calculate biome cache on demand, refactor chunk section constructor

This makes things way faster and uses less memory. Biome is just needed for tinted blocks (and maybe sky color), most blocks don't require it. Calculating the biome is expensive (at least when joining).
This commit is contained in:
Moritz Zwerger 2023-12-07 23:49:12 +01:00
parent c4cd6c4856
commit 7f32520f6e
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
15 changed files with 127 additions and 124 deletions

View File

@ -90,9 +90,11 @@ class WorldBiomesTest {
val chunk = world.chunks[0, 0]!!
chunk.getOrPut(0)
assertEquals(source.counter, 4096)
assertEquals(source.counter, 0) // biomes ore on demand
assertEquals(world.biomes[1, 2, 3], b1)
assertEquals(source.counter, 4096)
assertEquals(source.counter, 1)
assertEquals(world.biomes[1, 2, 3], b1)
assertEquals(source.counter, 1) // don't query again
assertTrue(chunk.cacheBiomes)
assertEquals(chunk[0]!!.biomes[1, 2, 3], b1)
}

View File

@ -15,12 +15,14 @@ package de.bixilon.minosoft.data.world.chunk.manager
import de.bixilon.kotlinglm.vec2.Vec2i
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.stone.StoneTest0
import de.bixilon.minosoft.data.registries.identified.Namespaces.minosoft
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
import de.bixilon.minosoft.data.world.chunk.ChunkSection.Companion.getIndex
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
import de.bixilon.minosoft.data.world.chunk.chunk.ChunkPrototype
import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours
@ -31,13 +33,13 @@ import de.bixilon.minosoft.data.world.chunk.update.chunk.ChunkCreateUpdate
import de.bixilon.minosoft.data.world.chunk.update.chunk.ChunkUnloadUpdate
import de.bixilon.minosoft.data.world.chunk.update.chunk.NeighbourChangeUpdate
import de.bixilon.minosoft.data.world.chunk.update.chunk.prototype.PrototypeChangeUpdate
import de.bixilon.minosoft.data.world.container.block.BlockSectionDataProvider
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.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY
import de.bixilon.minosoft.modding.event.listener.CallbackEventListener.Companion.listen
import de.bixilon.minosoft.protocol.network.connection.play.ConnectionTestUtil.createConnection
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import org.testng.Assert.*
import org.testng.annotations.Test
@ -191,9 +193,9 @@ class ChunkManagerTest {
manager[ChunkPosition(0, 1)]!![3, 16, 3] = StoneTest0.state
manager[ChunkPosition(0, 0)] = ChunkPrototype(blocks = arrayOf(
BlockSectionDataProvider(null).apply { this[3, 3, 3] = StoneTest0.state },
BlockSectionDataProvider(null).apply { this[3, 3, 3] = StoneTest0.state },
BlockSectionDataProvider(null).apply { this[3, 3, 3] = StoneTest0.state },
arrayOfNulls<BlockState>(ProtocolDefinition.BLOCKS_PER_SECTION).apply { this[getIndex(3, 3, 3)] = StoneTest0.state },
arrayOfNulls<BlockState>(ProtocolDefinition.BLOCKS_PER_SECTION).apply { this[getIndex(3, 3, 3)] = StoneTest0.state },
arrayOfNulls<BlockState>(ProtocolDefinition.BLOCKS_PER_SECTION).apply { this[getIndex(3, 3, 3)] = StoneTest0.state },
null, null, null,
),
blockEntities = emptyMap(),
@ -399,7 +401,7 @@ class ChunkManagerTest {
fired++
}
manager.set(ChunkPosition(1, 1), ChunkPrototype(blocks = Array(16) { if (it == 0) BlockSectionDataProvider(null) else null }), false)
manager.set(ChunkPosition(1, 1), ChunkPrototype(blocks = Array(16) { if (it == 0) arrayOfNulls(ProtocolDefinition.BLOCKS_PER_SECTION) else null }), false)
assertNotNull(manager[ChunkPosition(1, 1)])
@ -443,8 +445,10 @@ class ChunkManagerTest {
val matrix = manager.createMatrix(source)
val chunk = matrix[1][1]
assertEquals(chunk.getOrPut(0)!!.biomes.count, 4096)
assertEquals(chunk.getOrPut(0)!!.biomes[0], biome)
val section = chunk.getOrPut(0)!!.biomes
assertEquals(section[3, 3, 3], biome)
assertEquals(section[3, 3, 3], biome)
assertEquals(section[0], biome)
assertEquals(manager.world.biomes.getBiome(BlockPosition(5, 5, 5)), biome)
assertEquals(chunk.getBiome(BlockPosition(5, 5, 5)), biome)

View File

@ -1,7 +1,22 @@
/*
* 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.container.block
import de.bixilon.kotlinglm.vec3.Vec3i
import de.bixilon.minosoft.data.registries.blocks.types.stone.StoneTest0
import de.bixilon.minosoft.data.world.chunk.ChunkSection
import de.bixilon.minosoft.test.ITUtil.allocate
import org.testng.Assert.*
import org.testng.annotations.Test
@ -9,7 +24,8 @@ import org.testng.annotations.Test
class BlockSectionDataProviderTest {
private fun create(): BlockSectionDataProvider {
return BlockSectionDataProvider(null)
val section = ChunkSection::class.java.allocate()
return BlockSectionDataProvider(null, section)
}
fun `initial empty`() {

View File

@ -19,8 +19,10 @@ import de.bixilon.kutil.observer.DataObserver
import de.bixilon.kutil.reflection.ReflectionUtil.forceSet
import de.bixilon.mbf.MBFBinaryReader
import de.bixilon.minosoft.data.registries.blocks.MinecraftBlocks
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.registries.blocks.types.building.stone.StoneBlock
import de.bixilon.minosoft.data.registries.dimension.DimensionProperties
import de.bixilon.minosoft.data.world.chunk.ChunkSection.Companion.getIndex
import de.bixilon.minosoft.protocol.network.connection.play.ConnectionTestUtil.createConnection
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.packets.s2c.play.block.chunk.ChunkS2CP
@ -37,6 +39,10 @@ class ChunkS2CPTest {
registries.update(version, mbf.data.unsafeCast())
}
private operator fun Array<BlockState?>.get(x: Int, y: Int, z: Int): BlockState? {
return this[getIndex(x, y, z)]
}
private fun read(name: String, version: String, connection: PlayConnection = createConnection(version = version), dimension: DimensionProperties): ChunkS2CP {
val data = ChunkS2CPTest::class.java.getResourceAsStream("/packets/chunk/$name.bin")!!.readAllBytes()
connection.world::dimension.forceSet(DataObserver(dimension))

View File

@ -14,19 +14,14 @@ package de.bixilon.minosoft.data.world.chunk
import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.kotlinglm.vec3.Vec3i
import de.bixilon.kutil.array.ArrayUtil.cast
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.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
import de.bixilon.minosoft.data.world.container.biome.BiomeSectionDataProvider
import de.bixilon.minosoft.data.world.container.block.BlockSectionDataProvider
import de.bixilon.minosoft.gui.rendering.util.VecUtil.of
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import java.util.*
/**
@ -34,12 +29,12 @@ import java.util.*
*/
class ChunkSection(
val sectionHeight: Int,
chunk: Chunk? = null,
var blocks: BlockSectionDataProvider = BlockSectionDataProvider(chunk?.lock),
var biomes: SectionDataProvider<Biome> = SectionDataProvider(chunk?.lock, checkSize = false),
var blockEntities: SectionDataProvider<BlockEntity?> = SectionDataProvider(chunk?.lock, checkSize = true),
val chunk: Chunk,
) {
val chunk: Chunk = chunk ?: unsafeNull()
var blocks = BlockSectionDataProvider(chunk.lock, this)
var biomes = BiomeSectionDataProvider(chunk.lock, this)
var blockEntities: SectionDataProvider<BlockEntity?> = SectionDataProvider(chunk.lock, checkSize = true)
var light = SectionLight(this)
var neighbours: Array<ChunkSection?>? = null
@ -66,14 +61,6 @@ class ChunkSection(
}
}
fun buildBiomeCache(noise: NoiseBiomeAccessor) {
val biomes: Array<Biome?> = arrayOfNulls(ProtocolDefinition.BLOCKS_PER_SECTION)
for (index in 0 until ProtocolDefinition.BLOCKS_PER_SECTION) {
biomes[index] = noise.get(index and 0x0F, (index shr 8) and 0x0F, (index shr 4) and 0x0F, chunk)
}
this.biomes.setData(biomes.cast())
}
fun clear() {
blocks.clear()
@ -81,16 +68,7 @@ class ChunkSection(
blockEntities.clear()
}
fun updateChunk(chunk: Chunk) {
CHUNK[this] = chunk
blocks.lock = chunk.lock
// biomes?
blockEntities.lock = chunk.lock
}
companion object {
private val CHUNK = ChunkSection::chunk.jvmField
inline val Vec3i.index: Int
get() = getIndex(x, y, z)

View File

@ -28,7 +28,6 @@ import de.bixilon.minosoft.data.world.chunk.light.ChunkLight
import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours
import de.bixilon.minosoft.data.world.chunk.update.block.ChunkLocalBlockUpdate
import de.bixilon.minosoft.data.world.chunk.update.block.SingleBlockUpdate
import de.bixilon.minosoft.data.world.container.block.BlockSectionDataProvider.Companion.unsafeSetSection
import de.bixilon.minosoft.data.world.positions.ChunkPosition
import de.bixilon.minosoft.data.world.positions.InChunkPosition
import de.bixilon.minosoft.data.world.positions.SectionHeight
@ -44,7 +43,6 @@ import java.util.*
class Chunk(
val connection: PlayConnection,
val chunkPosition: ChunkPosition,
var sections: Array<ChunkSection?>,
var biomeSource: BiomeSource,
) : Iterable<ChunkSection?>, BiomeAccessor {
val lock = ThreadLock()
@ -53,6 +51,7 @@ class Chunk(
val minSection = world.dimension.minSection
val maxSection = world.dimension.maxSection
val cacheBiomes = world.biomes.noise != null
var sections: Array<ChunkSection?> = arrayOfNulls(world.dimension.sections)
val neighbours = ChunkNeighbours(this)
@ -184,7 +183,6 @@ class Chunk(
var section = sections[index] // get another time, it might have changed already
if (section == null) {
section = ChunkSection(sectionHeight, chunk = this)
section.blocks.unsafeSetSection(section)
val neighbours = this.neighbours.get()
if (neighbours != null) {
this.neighbours.completeSection(neighbours, section, sectionHeight, world.biomes.noise)

View File

@ -15,20 +15,18 @@ package de.bixilon.minosoft.data.world.chunk.chunk
import de.bixilon.kotlinglm.vec3.Vec3i
import de.bixilon.kutil.json.JsonObject
import de.bixilon.kutil.reflection.ReflectionUtil.forceSet
import de.bixilon.kutil.reflection.ReflectionUtil.jvmField
import de.bixilon.minosoft.config.StaticConfiguration
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.registries.blocks.types.entity.BlockWithEntity
import de.bixilon.minosoft.data.world.biome.source.BiomeSource
import de.bixilon.minosoft.data.world.chunk.ChunkSection
import de.bixilon.minosoft.data.world.container.block.BlockSectionDataProvider
import de.bixilon.minosoft.data.world.positions.ChunkPosition
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import it.unimi.dsi.fastutil.ints.IntOpenHashSet
class ChunkPrototype(
var blocks: Array<BlockSectionDataProvider?>? = null,
var blocks: Array<Array<BlockState?>?>? = null,
var blockEntities: Map<Vec3i, JsonObject>? = null,
var biomeSource: BiomeSource? = null,
var light: Array<ByteArray?>? = null,
@ -53,28 +51,23 @@ class ChunkPrototype(
val dimension = connection.world.dimension
val sections: Array<ChunkSection?> = arrayOfNulls(dimension.sections)
val light = this.light
for ((index, provider) in blocks.withIndex()) {
if (provider == null) continue
val section = ChunkSection(index + dimension.minSection, null, provider)
SECTION[provider] = section
val chunk = Chunk(connection, position, biomeSource)
for ((index, blockData) in blocks.withIndex()) {
if (blockData == null) continue
val section = ChunkSection(index + dimension.minSection, chunk)
section.blocks.setData(blockData)
if (!StaticConfiguration.IGNORE_SERVER_LIGHT) {
light?.get(index)?.let { section.light.light = it }
}
sections[index] = section
chunk.sections[index] = section
}
this.blockEntities.update(dimension.minSection, sections, null, connection)
this.blockEntities.update(dimension.minSection, chunk, null, connection)
val chunk = Chunk(connection, position, sections, biomeSource)
for (section in sections) {
if (section == null) continue
section.updateChunk(chunk)
}
if (!StaticConfiguration.IGNORE_SERVER_LIGHT) {
this.topLight?.let { chunk.light.top.update(it) }
this.bottomLight?.let { chunk.light.bottom.update(it) }
@ -84,7 +77,7 @@ class ChunkPrototype(
return chunk
}
private fun Array<BlockSectionDataProvider?>.update(chunk: Chunk, replace: Boolean, affected: IntOpenHashSet) {
private fun Array<Array<BlockState?>?>.update(chunk: Chunk, replace: Boolean, affected: IntOpenHashSet) {
for ((index, provider) in this.withIndex()) {
var section = chunk.sections[index]
val sectionHeight = index - chunk.minSection
@ -101,16 +94,15 @@ class ChunkPrototype(
} else {
section.blockEntities.clear()
}
SECTION.forceSet(provider, section)
section.blocks = provider
section.blocks.setData(provider)
affected += sectionHeight
}
}
private fun Map<Vec3i, JsonObject>?.update(minSection: Int, sections: Array<ChunkSection?>, affected: IntOpenHashSet?, connection: PlayConnection) {
private fun Map<Vec3i, JsonObject>?.update(minSection: Int, chunk: Chunk, affected: IntOpenHashSet?, connection: PlayConnection) {
val position = Vec3i()
for ((index, section) in sections.withIndex()) {
for ((index, section) in chunk.sections.withIndex()) {
if (section == null || section.blocks.isEmpty) continue
val blocks = section.blocks
val sectionHeight = (index + minSection)
@ -142,7 +134,7 @@ class ChunkPrototype(
fun updateChunk(chunk: Chunk, replace: Boolean): IntOpenHashSet? {
val affected = IntOpenHashSet()
this.blocks?.update(chunk, replace, affected)
this.blockEntities?.update(chunk.minSection, chunk.sections, affected, chunk.connection)
this.blockEntities?.update(chunk.minSection, chunk, affected, chunk.connection)
this.biomeSource?.let { chunk.biomeSource = it } // TODO: invalidate cache
@ -155,8 +147,4 @@ class ChunkPrototype(
if (affected.isEmpty()) return null
return affected
}
private companion object {
private val SECTION = BlockSectionDataProvider::section.jvmField
}
}

View File

@ -161,7 +161,7 @@ class ChunkManager(val world: World, chunkCapacity: Int = 0, prototypeCapacity:
fun create(position: ChunkPosition, biome: BiomeSource = DummyBiomeSource(null)): Chunk {
if (!world.isValidPosition(position)) throw IllegalArgumentException("Chunk position $position is not valid!")
chunks.lock.lock()
val chunk = chunks.unsafe.getOrPut(position) { Chunk(world.connection, position, arrayOfNulls(world.dimension.sections), biome) }
val chunk = chunks.unsafe.getOrPut(position) { Chunk(world.connection, position, biome) }
val updates = onChunkCreate(chunk)
chunks.lock.unlock()

View File

@ -72,9 +72,6 @@ class ChunkNeighbours(val chunk: Chunk) : Iterable<Chunk?> {
fun completeSection(neighbours: Array<Chunk>, section: ChunkSection, sectionHeight: SectionHeight, noise: NoiseBiomeAccessor?) {
section.neighbours = ChunkUtil.getDirectNeighbours(neighbours, chunk, sectionHeight)
if (noise != null) {
section.buildBiomeCache(noise)
}
}
private fun complete(neighbours: Array<Chunk>) {

View File

@ -23,36 +23,25 @@ import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
open class SectionDataProvider<T>(
var lock: Lock?,
data: Array<T>? = null,
val checkSize: Boolean = false,
calculateInitial: Boolean = true,
) : Iterable<T> {
protected var data: Array<Any?>? = data?.unsafeCast()
protected var data: Array<Any?>? = null
private set
var count: Int = 0
private set
val isEmpty: Boolean
get() = count == 0
lateinit var minPosition: Vec3i
var minPosition = Vec3i(ProtocolDefinition.CHUNK_SECTION_SIZE)
private set
lateinit var maxPosition: Vec3i
var maxPosition = Vec3i.EMPTY
private set
init {
if (data != null && calculateInitial) {
recalculate()
} else {
minPosition = Vec3i(ProtocolDefinition.CHUNK_SECTION_SIZE)
maxPosition = Vec3i.EMPTY
}
}
@Suppress("UNCHECKED_CAST")
operator fun get(index: Int): T {
open operator fun get(index: Int): T {
return data?.get(index) as T
}
operator fun get(x: Int, y: Int, z: Int): T {
open operator fun get(x: Int, y: Int, z: Int): T {
return get(ChunkSection.getIndex(x, y, z))
}
@ -193,13 +182,6 @@ open class SectionDataProvider<T>(
lock?.unlock()
}
fun copy(): SectionDataProvider<T> {
lock?.acquire()
val clone = SectionDataProvider<T>(null, data?.clone()?.unsafeCast())
lock?.release()
return clone
}
fun clear() {
lock?.lock()

View File

@ -0,0 +1,41 @@
/*
* 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.container.biome
import de.bixilon.kutil.concurrent.lock.Lock
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.world.chunk.ChunkSection
import de.bixilon.minosoft.data.world.container.SectionDataProvider
class BiomeSectionDataProvider(
lock: Lock? = null,
val section: ChunkSection,
) : SectionDataProvider<Biome?>(lock, false) {
override fun get(index: Int): Biome? {
var biome = super.get(index)
if (biome != null) return biome
biome = section.chunk.world.biomes.noise?.get(index and 0x0F, (index shr 8) and 0x0F, (index shr 4) and 0x0F, section.chunk)
unsafeSet(index, biome)
return biome
}
override fun get(x: Int, y: Int, z: Int): Biome? {
var biome = super.get(x, y, z)
if (biome != null) return biome
biome = section.chunk.world.biomes.noise?.get(x, y, z, section.chunk)
unsafeSet(x, y, z, biome)
return biome
}
}

View File

@ -13,9 +13,7 @@
package de.bixilon.minosoft.data.world.container.block
import de.bixilon.kutil.cast.CastUtil.unsafeNull
import de.bixilon.kutil.concurrent.lock.Lock
import de.bixilon.kutil.reflection.ReflectionUtil.jvmField
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.registries.blocks.types.fluid.FluidHolder
import de.bixilon.minosoft.data.registries.fluid.fluids.WaterFluid.Companion.isWaterlogged
@ -25,9 +23,8 @@ import de.bixilon.minosoft.data.world.container.SectionDataProvider
class BlockSectionDataProvider(
lock: Lock? = null,
data: Array<BlockState?>? = null,
) : SectionDataProvider<BlockState?>(lock, data, true, false) {
val section: ChunkSection = unsafeNull()
val section: ChunkSection,
) : SectionDataProvider<BlockState?>(lock, true) {
val occlusion = SectionOcclusion(this)
var fluidCount = 0
private set
@ -89,13 +86,4 @@ class BlockSectionDataProvider(
}
return this.isWaterlogged()
}
companion object {
private val sections = BlockSectionDataProvider::section.jvmField
@Deprecated("properly integrate in constructor")
fun BlockSectionDataProvider.unsafeSetSection(section: ChunkSection) {
this@Companion.sections[this] = section
}
}
}

View File

@ -131,7 +131,7 @@ class SolidSectionMesher(
}
val tints = tints.getBlockTint(state, chunk.getBiome(position.x and 0x0F, y, position.z and 0x0F), x, y, z)
val tints = tints.getBlockTint(state, chunk, x, position.y, z)
var rendered = false
model?.render(position, floatOffset, mesh, random, state, neighbourBlocks, light, tints, blockEntity.unsafeCast())?.let { if (it) rendered = true }

View File

@ -17,7 +17,6 @@ import de.bixilon.kotlinglm.vec3.Vec3i
import de.bixilon.kutil.primitive.IntUtil.toInt
import de.bixilon.minosoft.assets.AssetsManager
import de.bixilon.minosoft.data.container.stack.ItemStack
import de.bixilon.minosoft.data.registries.biomes.Biome
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.registries.fluid.Fluid
import de.bixilon.minosoft.data.text.formatting.color.RGBColor
@ -47,10 +46,11 @@ class TintManager(val connection: PlayConnection) {
DefaultTints.init(this)
}
fun getBlockTint(state: BlockState, biome: Biome?, x: Int, y: Int, z: Int): IntArray? {
fun getBlockTint(state: BlockState, chunk: Chunk?, x: Int, y: Int, z: Int): IntArray? {
if (state.block !is TintedBlock) return null
val tintProvider = state.block.tintProvider ?: return null
val tints = IntArray(if (tintProvider is MultiTintProvider) tintProvider.tints else 1)
val biome = chunk?.getBiome(x, y, z)
for (tintIndex in tints.indices) {
tints[tintIndex] = tintProvider.getBlockColor(state, biome, x, y, z, tintIndex)
@ -70,10 +70,6 @@ class TintManager(val connection: PlayConnection) {
return getParticleTint(blockState, position.x, position.y, position.z)
}
fun getBlockTint(blockState: BlockState, biome: Biome? = null, blockPosition: Vec3i): IntArray? {
return getBlockTint(blockState, biome, blockPosition.x, blockPosition.y, blockPosition.z)
}
fun getFluidTint(chunk: Chunk, fluid: Fluid, height: Float, x: Int, y: Int, z: Int): Int? {
val biome = chunk.getBiome(x and 0x0F, y, z and 0x0F)
return fluid.model?.tint?.getFluidTint(fluid, biome, height, x, y, z)

View File

@ -24,7 +24,6 @@ import de.bixilon.minosoft.data.world.chunk.ChunkSection
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
import de.bixilon.minosoft.data.world.chunk.chunk.ChunkPrototype
import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours
import de.bixilon.minosoft.data.world.container.block.BlockSectionDataProvider
import de.bixilon.minosoft.data.world.container.palette.PalettedContainer
import de.bixilon.minosoft.data.world.container.palette.PalettedContainerReader
import de.bixilon.minosoft.data.world.container.palette.palettes.BiomePaletteFactory
@ -74,7 +73,7 @@ object ChunkUtil {
// parse data
var index = 0
val sections: Array<BlockSectionDataProvider?> = arrayOfNulls(dimension.sections)
val sections: Array<Array<BlockState?>?> = arrayOfNulls(dimension.sections)
for ((sectionIndex, sectionHeight) in (dimension.minSection..dimension.maxSection).withIndex()) {
if (!sectionBitMask[sectionIndex]) {
continue
@ -106,7 +105,7 @@ object ChunkUtil {
blocks[yzx] = buffer.connection.registries.blockState.getOrNull(blockId) ?: continue
}
sections[sectionHeight] = BlockSectionDataProvider(data = blocks)
sections[sectionHeight] = blocks
}
chunkData.blocks = sections
return chunkData
@ -139,7 +138,7 @@ object ChunkUtil {
}
var index = 0
val sections: Array<BlockSectionDataProvider?> = arrayOfNulls(dimension.sections)
val sections: Array<Array<BlockState?>?> = arrayOfNulls(dimension.sections)
for ((sectionIndex, sectionHeight) in (dimension.minSection..dimension.maxSection).withIndex()) { // max sections per chunks in chunk column
if (!sectionBitMask[sectionIndex]) {
continue
@ -150,7 +149,7 @@ object ChunkUtil {
val block = buffer.connection.registries.blockState.getOrNull(blockId) ?: continue
blocks[yzx] = block
}
sections[sectionHeight] = BlockSectionDataProvider(data = blocks)
sections[sectionHeight] = blocks
}
chunkData.blocks = sections
return chunkData
@ -158,7 +157,7 @@ object ChunkUtil {
fun readPaletteChunk(buffer: PlayInByteBuffer, dimension: DimensionProperties, sectionBitMask: BitSet?, complete: Boolean, skylight: Boolean = false): ChunkPrototype {
val chunkData = ChunkPrototype()
val sectionBlocks: Array<BlockSectionDataProvider?> = arrayOfNulls(dimension.sections)
val sectionBlocks: Array<Array<BlockState?>?> = arrayOfNulls(dimension.sections)
val light: Array<ByteArray?> = arrayOfNulls(dimension.sections)
var lightReceived = 0
val biomes: Array<Array<Biome?>?> = arrayOfNulls(dimension.sections)
@ -176,8 +175,8 @@ object ChunkUtil {
val blockContainer: PalettedContainer<BlockState?> = PalettedContainerReader.read(buffer, buffer.connection.registries.blockState, paletteFactory = BlockStatePaletteFactory)
if (!blockContainer.isEmpty) {
val unpacked = BlockSectionDataProvider(data = blockContainer.unpack())
if (!unpacked.isEmpty) {
val unpacked = blockContainer.unpack<BlockState?>()
if (!unpacked.isEmpty()) {
sectionBlocks[sectionHeight - dimension.minSection] = unpacked
}
}
@ -311,4 +310,12 @@ object ChunkUtil {
fun ChunkPosition.isInViewDistance(viewDistance: Int, cameraPosition: ChunkPosition): Boolean {
return abs(this.x - cameraPosition.x) <= viewDistance && abs(this.y - cameraPosition.y) <= viewDistance
}
private fun Array<BlockState?>.isEmpty(): Boolean {
for (entry in this) {
if (entry == null) continue
return false
}
return true
}
}