diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/ChunkMesher.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/ChunkMesher.kt new file mode 100644 index 000000000..070d6b1a7 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/ChunkMesher.kt @@ -0,0 +1,69 @@ +/* + * Minosoft + * Copyright (C) 2020-2022 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.world + +import de.bixilon.kutil.concurrent.pool.ThreadPoolRunnable +import de.bixilon.minosoft.gui.rendering.world.mesh.WorldMesh +import de.bixilon.minosoft.gui.rendering.world.preparer.FluidSectionPreparer +import de.bixilon.minosoft.gui.rendering.world.preparer.SolidSectionPreparer +import de.bixilon.minosoft.gui.rendering.world.preparer.cull.FluidCullSectionPreparer +import de.bixilon.minosoft.gui.rendering.world.preparer.cull.SolidCullSectionPreparer +import de.bixilon.minosoft.gui.rendering.world.queue.meshing.tasks.MeshPrepareTask +import de.bixilon.minosoft.gui.rendering.world.util.WorldRendererUtil.smallMesh + +class ChunkMesher( + private val renderer: WorldRenderer, +) { + private val solidSectionPreparer: SolidSectionPreparer = SolidCullSectionPreparer(renderer.renderWindow) + private val fluidSectionPreparer: FluidSectionPreparer = FluidCullSectionPreparer(renderer.renderWindow) + + private fun mesh(item: WorldQueueItem): WorldMesh? { + if (item.section.blocks.isEmpty) { + renderer.queueItemUnload(item) + return null + } + val mesh = WorldMesh(renderer.renderWindow, item.chunkPosition, item.sectionHeight, item.section.smallMesh) + solidSectionPreparer.prepareSolid(item.chunkPosition, item.sectionHeight, item.chunk, item.section, item.neighbours, item.chunkNeighbours, mesh) + + if (item.section.blocks.fluidCount > 0) { + fluidSectionPreparer.prepareFluid(item.chunkPosition, item.sectionHeight, item.chunk, item.section, item.neighbours, item.chunkNeighbours, mesh) + } + + return mesh + } + + private fun mesh(item: WorldQueueItem, runnable: ThreadPoolRunnable) { + val mesh = mesh(item) ?: return + runnable.interruptable = false + if (Thread.interrupted()) return + if (mesh.clearEmpty() == 0) { + return renderer.queueItemUnload(item) + } + mesh.finish() + item.mesh = mesh + renderer.loadingQueue.queue(mesh) + } + + fun tryMesh(item: WorldQueueItem, task: MeshPrepareTask, runnable: ThreadPoolRunnable) { + try { + mesh(item, runnable) + } catch (ignored: InterruptedException) { + } finally { + task.runnable.interruptable = false + if (Thread.interrupted()) throw InterruptedException() + renderer.queue.tasks -= task + renderer.queue.work() + } + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldQueueItem.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldQueueItem.kt index 350caf52b..0e91ed035 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldQueueItem.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldQueueItem.kt @@ -24,10 +24,11 @@ import java.util.* class WorldQueueItem( val chunkPosition: Vec2i, val sectionHeight: Int, - val chunk: Chunk?, - val section: ChunkSection?, + val chunk: Chunk, + val section: ChunkSection, val center: Vec3, - var neighbours: Array?, + val chunkNeighbours: Array, + var neighbours: Array, ) { val sectionPosition = Vec3i(chunkPosition.x, sectionHeight, chunkPosition.y) var mesh: WorldMesh? = null diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldRenderer.kt index f63844977..a0dc9dbdf 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldRenderer.kt @@ -17,13 +17,11 @@ import de.bixilon.kotlinglm.vec2.Vec2i import de.bixilon.kotlinglm.vec3.Vec3 import de.bixilon.kotlinglm.vec3.Vec3i import de.bixilon.kutil.concurrent.lock.simple.SimpleLock -import de.bixilon.kutil.concurrent.pool.ThreadPoolRunnable import de.bixilon.kutil.latch.CountUpAndDownLatch import de.bixilon.kutil.observer.DataObserver.Companion.observe import de.bixilon.minosoft.config.key.KeyActions import de.bixilon.minosoft.config.key.KeyBinding import de.bixilon.minosoft.config.key.KeyCodes -import de.bixilon.minosoft.data.direction.Directions import de.bixilon.minosoft.data.registries.ResourceLocation import de.bixilon.minosoft.data.world.World import de.bixilon.minosoft.data.world.chunk.Chunk @@ -39,41 +37,24 @@ import de.bixilon.minosoft.gui.rendering.system.base.RenderingCapabilities import de.bixilon.minosoft.gui.rendering.system.base.phases.OpaqueDrawable import de.bixilon.minosoft.gui.rendering.system.base.phases.TranslucentDrawable import de.bixilon.minosoft.gui.rendering.system.base.phases.TransparentDrawable -import de.bixilon.minosoft.gui.rendering.util.VecUtil.inSectionHeight import de.bixilon.minosoft.gui.rendering.util.VecUtil.of import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3Util.EMPTY import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3Util.blockPosition import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.chunkPosition -import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.inChunkSectionPosition -import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.sectionHeight import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.toVec3 import de.bixilon.minosoft.gui.rendering.world.mesh.VisibleMeshes -import de.bixilon.minosoft.gui.rendering.world.mesh.WorldMesh -import de.bixilon.minosoft.gui.rendering.world.preparer.FluidSectionPreparer -import de.bixilon.minosoft.gui.rendering.world.preparer.SolidSectionPreparer -import de.bixilon.minosoft.gui.rendering.world.preparer.cull.FluidCullSectionPreparer -import de.bixilon.minosoft.gui.rendering.world.preparer.cull.SolidCullSectionPreparer import de.bixilon.minosoft.gui.rendering.world.queue.MeshLoadingQueue import de.bixilon.minosoft.gui.rendering.world.queue.MeshUnloadingQueue import de.bixilon.minosoft.gui.rendering.world.queue.meshing.ChunkMeshingQueue -import de.bixilon.minosoft.gui.rendering.world.queue.meshing.tasks.MeshPrepareTask import de.bixilon.minosoft.gui.rendering.world.shader.WorldShader import de.bixilon.minosoft.gui.rendering.world.shader.WorldTextShader -import de.bixilon.minosoft.modding.event.events.RespawnEvent -import de.bixilon.minosoft.modding.event.events.blocks.BlockDataChangeEvent -import de.bixilon.minosoft.modding.event.events.blocks.BlockSetEvent -import de.bixilon.minosoft.modding.event.events.blocks.BlocksSetEvent -import de.bixilon.minosoft.modding.event.events.blocks.chunk.ChunkDataChangeEvent -import de.bixilon.minosoft.modding.event.events.blocks.chunk.ChunkUnloadEvent -import de.bixilon.minosoft.modding.event.events.blocks.chunk.LightChangeEvent +import de.bixilon.minosoft.gui.rendering.world.util.WorldRendererChangeListener import de.bixilon.minosoft.modding.event.listener.CallbackEventListener.Companion.listen import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection -import de.bixilon.minosoft.protocol.network.connection.play.PlayConnectionStates import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.util.KUtil.toResourceLocation import de.bixilon.minosoft.util.chunk.ChunkUtil -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import it.unimi.dsi.fastutil.ints.IntOpenHashSet class WorldRenderer( @@ -86,9 +67,7 @@ class WorldRenderer( private val shader = renderSystem.createShader("minosoft:world".toResourceLocation()) { WorldShader(it, false) } private val transparentShader = renderSystem.createShader("minosoft:world".toResourceLocation()) { WorldShader(it, true) } private val textShader = renderSystem.createShader("minosoft:world/text".toResourceLocation()) { WorldTextShader(it) } - private val world: World = connection.world - private val solidSectionPreparer: SolidSectionPreparer = SolidCullSectionPreparer(renderWindow) - private val fluidSectionPreparer: FluidSectionPreparer = FluidCullSectionPreparer(renderWindow) + val world: World = connection.world val loaded = LoadedMeshes(this) @@ -99,6 +78,8 @@ class WorldRenderer( val loadingQueue = MeshLoadingQueue(this) val unloadingQueue = MeshUnloadingQueue(this) + val mesher = ChunkMesher(this) + // all meshes that will be rendered in the next frame (might be changed, when the frustum changes or a chunk gets loaded, ...) private var clearVisibleNextFrame = false var visible = VisibleMeshes() // This name might be confusing. Those faces are from blocks. @@ -109,12 +90,7 @@ class WorldRenderer( var cameraChunkPosition = Vec2i.EMPTY var cameraSectionHeight = 0 - @Deprecated("alias?", ReplaceWith("visible.sizeString")) val visibleSize: String get() = visible.sizeString - @Deprecated("alias?", ReplaceWith("loaded.size")) val loadedMeshesSize: Int get() = loaded.size @Deprecated("alias?") val culledQueuedSize: Int get() = culledQueue.size - @Deprecated("alias?", ReplaceWith("loadingQueue.size")) val meshesToLoadSize: Int get() = loadingQueue.size - @Deprecated("alias?", ReplaceWith("queue.size")) val queueSize: Int get() = queue.size - @Deprecated("alias?", ReplaceWith("queue.tasks.size")) val preparingTasksSize: Int get() = queue.tasks.size override fun init(latch: CountUpAndDownLatch) { renderWindow.modelLoader.load(latch) @@ -128,93 +104,7 @@ class WorldRenderer( connection.events.listen { onFrustumChange() } - connection.events.listen { if (it.dimensionChange) unloadWorld() } - connection.events.listen { queueChunk(it.chunkPosition, it.chunk) } - connection.events.listen { - val chunkPosition = it.blockPosition.chunkPosition - val sectionHeight = it.blockPosition.sectionHeight - val chunk = world[chunkPosition] ?: return@listen - val neighbours = chunk.neighbours.get() ?: return@listen - queueSection(chunkPosition, sectionHeight, chunk, neighbours = neighbours) - val inChunkSectionPosition = it.blockPosition.inChunkSectionPosition - - if (inChunkSectionPosition.y == 0) { - queueSection(chunkPosition, sectionHeight - 1, chunk, neighbours = neighbours) - } else if (inChunkSectionPosition.y == ProtocolDefinition.SECTION_MAX_Y) { - queueSection(chunkPosition, sectionHeight + 1, chunk, neighbours = neighbours) - } - if (inChunkSectionPosition.z == 0) { - queueSection(Vec2i(chunkPosition.x, chunkPosition.y - 1), sectionHeight, chunk = neighbours[3]) - } else if (inChunkSectionPosition.z == ProtocolDefinition.SECTION_MAX_Z) { - queueSection(Vec2i(chunkPosition.x, chunkPosition.y + 1), sectionHeight, chunk = neighbours[4]) - } - if (inChunkSectionPosition.x == 0) { - queueSection(Vec2i(chunkPosition.x - 1, chunkPosition.y), sectionHeight, chunk = neighbours[1]) - } else if (inChunkSectionPosition.x == ProtocolDefinition.SECTION_MAX_X) { - queueSection(Vec2i(chunkPosition.x + 1, chunkPosition.y), sectionHeight, chunk = neighbours[6]) - } - } - - connection.events.listen { - if (it.blockChange) { - // change is already covered - return@listen - } - queueSection(it.chunkPosition, it.sectionHeight, it.chunk) - } - - connection.events.listen { - val chunk = world[it.chunkPosition] ?: return@listen // should not happen - if (!chunk.isFullyLoaded) { - return@listen - } - val sectionHeights: Int2ObjectOpenHashMap = Int2ObjectOpenHashMap() - for (blockPosition in it.blocks.keys) { - val neighbours = sectionHeights.getOrPut(blockPosition.sectionHeight) { BooleanArray(Directions.SIZE) } - val inSectionHeight = blockPosition.y.inSectionHeight - if (inSectionHeight == 0) { - neighbours[0] = true - } else if (inSectionHeight == ProtocolDefinition.SECTION_MAX_Y) { - neighbours[1] = true - } - if (blockPosition.z == 0) { - neighbours[2] = true - } else if (blockPosition.z == ProtocolDefinition.SECTION_MAX_Z) { - neighbours[3] = true - } - if (blockPosition.x == 0) { - neighbours[4] = true - } else if (blockPosition.x == ProtocolDefinition.SECTION_MAX_X) { - neighbours[5] = true - } - } - val neighbours = chunk.neighbours.get() ?: return@listen - for ((sectionHeight, neighbourUpdates) in sectionHeights) { - queueSection(it.chunkPosition, sectionHeight, chunk, neighbours = neighbours) - - if (neighbourUpdates[0]) { - queueSection(it.chunkPosition, sectionHeight - 1, chunk, neighbours = neighbours) - } - if (neighbourUpdates[1]) { - queueSection(it.chunkPosition, sectionHeight + 1, chunk, neighbours = neighbours) - } - if (neighbourUpdates[2]) { - queueSection(Vec2i(it.chunkPosition.x, it.chunkPosition.y - 1), sectionHeight, chunk = neighbours[3]) - } - if (neighbourUpdates[3]) { - queueSection(Vec2i(it.chunkPosition.x, it.chunkPosition.y + 1), sectionHeight, chunk = neighbours[4]) - } - if (neighbourUpdates[4]) { - queueSection(Vec2i(it.chunkPosition.x - 1, it.chunkPosition.y), sectionHeight, chunk = neighbours[1]) - } - if (neighbourUpdates[5]) { - queueSection(Vec2i(it.chunkPosition.x + 1, it.chunkPosition.y), sectionHeight, chunk = neighbours[6]) - } - } - } - - connection.events.listen { unloadChunk(it.chunkPosition) } - connection::state.observe(this) { if (it == PlayConnectionStates.DISCONNECTED) unloadWorld() } + WorldRendererChangeListener.register(this) var paused = false renderWindow::state.observe(this) { @@ -226,7 +116,6 @@ class WorldRenderer( paused = false } } - connection.events.listen { queueSection(it.blockPosition.chunkPosition, it.blockPosition.sectionHeight) } renderWindow.inputHandler.registerKeyCallback( "minosoft:clear_chunk_cache".toResourceLocation(), @@ -298,7 +187,7 @@ class WorldRenderer( world.lock.release() } - private fun unloadWorld() { + fun unloadWorld() { culledQueueLock.lock() queue.lock() loadingQueue.lock() @@ -323,7 +212,7 @@ class WorldRenderer( loadingQueue.unlock() } - private fun unloadChunk(chunkPosition: Vec2i) { + fun unloadChunk(chunkPosition: Vec2i) { culledQueueLock.lock() queue.lock() loadingQueue.lock() @@ -349,42 +238,8 @@ class WorldRenderer( queue.unlock() } - fun prepareItem(item: WorldQueueItem, task: MeshPrepareTask, runnable: ThreadPoolRunnable) { - try { - val chunk = item.chunk ?: world[item.chunkPosition] ?: return - val section = chunk[item.sectionHeight] ?: return - if (section.blocks.isEmpty) { - return queueItemUnload(item) - } - val neighbourChunks: Array = chunk.neighbours.get() ?: return queueSection(item.chunkPosition, item.sectionHeight, chunk, section, neighbours = null) - val neighbours = item.neighbours ?: ChunkUtil.getDirectNeighbours(neighbourChunks, chunk, item.sectionHeight) - val mesh = WorldMesh(renderWindow, item.chunkPosition, item.sectionHeight, section.blocks.count < ProtocolDefinition.SECTION_MAX_X * ProtocolDefinition.SECTION_MAX_Z) - solidSectionPreparer.prepareSolid(item.chunkPosition, item.sectionHeight, chunk, section, neighbours, neighbourChunks, mesh) - if (section.blocks.fluidCount > 0) { - fluidSectionPreparer.prepareFluid(item.chunkPosition, item.sectionHeight, chunk, section, neighbours, neighbourChunks, mesh) - } - runnable.interruptable = false - if (Thread.interrupted()) return - if (mesh.clearEmpty() == 0) { - return queueItemUnload(item) - } - mesh.finish() - item.mesh = mesh - loadingQueue.queue(mesh) - } catch (exception: Throwable) { - if (exception !is InterruptedException) { - // otherwise task got interrupted (probably because of chunk unload) - throw exception - } - } finally { - task.runnable.interruptable = false - if (Thread.interrupted()) throw InterruptedException() - queue.tasks -= task - queue.work() - } - } - private fun queueItemUnload(item: WorldQueueItem) { + fun queueItemUnload(item: WorldQueueItem) { culledQueueLock.lock() queue.lock() loadingQueue.lock() @@ -438,7 +293,7 @@ class WorldRenderer( return false } - private fun queueSection(chunkPosition: Vec2i, sectionHeight: Int, chunk: Chunk? = world.chunks[chunkPosition], section: ChunkSection? = chunk?.get(sectionHeight), ignoreFrustum: Boolean = false, neighbours: Array? = chunk?.neighbours?.get()) { + fun queueSection(chunkPosition: Vec2i, sectionHeight: Int, chunk: Chunk? = world.chunks[chunkPosition], section: ChunkSection? = chunk?.get(sectionHeight), ignoreFrustum: Boolean = false, neighbours: Array? = chunk?.neighbours?.get()) { if (chunk == null || neighbours == null || section == null || renderWindow.state == RenderingStates.PAUSED) { return } @@ -450,7 +305,7 @@ class WorldRenderer( } } - private fun queueChunk(chunkPosition: Vec2i, chunk: Chunk) { + fun queueChunk(chunkPosition: Vec2i, chunk: Chunk) { val neighbours = chunk.neighbours.get() if (neighbours == null || !chunk.isFullyLoaded || renderWindow.state == RenderingStates.PAUSED) { return diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/MeshLoadingQueue.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/MeshLoadingQueue.kt index 450a25961..ebb9a5705 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/MeshLoadingQueue.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/MeshLoadingQueue.kt @@ -18,8 +18,8 @@ import de.bixilon.kutil.concurrent.lock.simple.SimpleLock import de.bixilon.kutil.time.TimeUtil import de.bixilon.minosoft.data.world.positions.ChunkPosition import de.bixilon.minosoft.gui.rendering.world.WorldRenderer -import de.bixilon.minosoft.gui.rendering.world.WorldRendererUtil.maxBusyTime import de.bixilon.minosoft.gui.rendering.world.mesh.WorldMesh +import de.bixilon.minosoft.gui.rendering.world.util.WorldRendererUtil.maxBusyTime import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap class MeshLoadingQueue( diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/MeshUnloadingQueue.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/MeshUnloadingQueue.kt index 321cecdc5..76ade190e 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/MeshUnloadingQueue.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/MeshUnloadingQueue.kt @@ -16,8 +16,8 @@ package de.bixilon.minosoft.gui.rendering.world.queue import de.bixilon.kutil.concurrent.lock.simple.SimpleLock import de.bixilon.kutil.time.TimeUtil import de.bixilon.minosoft.gui.rendering.world.WorldRenderer -import de.bixilon.minosoft.gui.rendering.world.WorldRendererUtil.maxBusyTime import de.bixilon.minosoft.gui.rendering.world.mesh.WorldMesh +import de.bixilon.minosoft.gui.rendering.world.util.WorldRendererUtil.maxBusyTime class MeshUnloadingQueue( private val renderer: WorldRenderer, diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/meshing/ChunkMeshingQueue.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/meshing/ChunkMeshingQueue.kt index ed3de48d2..8e911fa17 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/meshing/ChunkMeshingQueue.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/queue/meshing/ChunkMeshingQueue.kt @@ -81,7 +81,7 @@ class ChunkMeshingQueue( for (item in items) { val runnable = ThreadPoolRunnable(if (item.chunkPosition == renderer.cameraChunkPosition) ThreadPool.HIGH else ThreadPool.LOW, interruptable = true) // Our own chunk is the most important one ToDo: Also make neighbour chunks important val task = MeshPrepareTask(item.chunkPosition, item.sectionHeight, runnable) - task.runnable.runnable = Runnable { renderer.prepareItem(item, task, task.runnable) } + task.runnable.runnable = Runnable { renderer.mesher.prepareItem(item, task, task.runnable) } tasks += task } working = false diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/util/WorldRendererChangeListener.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/util/WorldRendererChangeListener.kt new file mode 100644 index 000000000..c28a92418 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/util/WorldRendererChangeListener.kt @@ -0,0 +1,139 @@ +/* + * Minosoft + * Copyright (C) 2020-2022 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.world.util + +import de.bixilon.kotlinglm.vec2.Vec2i +import de.bixilon.kutil.observer.DataObserver.Companion.observe +import de.bixilon.minosoft.data.direction.Directions +import de.bixilon.minosoft.gui.rendering.util.VecUtil.inSectionHeight +import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.chunkPosition +import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.inChunkSectionPosition +import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.sectionHeight +import de.bixilon.minosoft.gui.rendering.world.WorldRenderer +import de.bixilon.minosoft.modding.event.events.RespawnEvent +import de.bixilon.minosoft.modding.event.events.blocks.BlockDataChangeEvent +import de.bixilon.minosoft.modding.event.events.blocks.BlockSetEvent +import de.bixilon.minosoft.modding.event.events.blocks.BlocksSetEvent +import de.bixilon.minosoft.modding.event.events.blocks.chunk.ChunkDataChangeEvent +import de.bixilon.minosoft.modding.event.events.blocks.chunk.ChunkUnloadEvent +import de.bixilon.minosoft.modding.event.events.blocks.chunk.LightChangeEvent +import de.bixilon.minosoft.modding.event.listener.CallbackEventListener.Companion.listen +import de.bixilon.minosoft.protocol.network.connection.play.PlayConnectionStates +import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap + +object WorldRendererChangeListener { + + private fun listenBlockSet(renderer: WorldRenderer) { + renderer.connection.events.listen { + val chunkPosition = it.blockPosition.chunkPosition + val sectionHeight = it.blockPosition.sectionHeight + val chunk = renderer.world[chunkPosition] ?: return@listen + val neighbours = chunk.neighbours.get() ?: return@listen + renderer.queueSection(chunkPosition, sectionHeight, chunk, neighbours = neighbours) + val inChunkSectionPosition = it.blockPosition.inChunkSectionPosition + + if (inChunkSectionPosition.y == 0) { + renderer.queueSection(chunkPosition, sectionHeight - 1, chunk, neighbours = neighbours) + } else if (inChunkSectionPosition.y == ProtocolDefinition.SECTION_MAX_Y) { + renderer.queueSection(chunkPosition, sectionHeight + 1, chunk, neighbours = neighbours) + } + if (inChunkSectionPosition.z == 0) { + renderer.queueSection(Vec2i(chunkPosition.x, chunkPosition.y - 1), sectionHeight, chunk = neighbours[3]) + } else if (inChunkSectionPosition.z == ProtocolDefinition.SECTION_MAX_Z) { + renderer.queueSection(Vec2i(chunkPosition.x, chunkPosition.y + 1), sectionHeight, chunk = neighbours[4]) + } + if (inChunkSectionPosition.x == 0) { + renderer.queueSection(Vec2i(chunkPosition.x - 1, chunkPosition.y), sectionHeight, chunk = neighbours[1]) + } else if (inChunkSectionPosition.x == ProtocolDefinition.SECTION_MAX_X) { + renderer.queueSection(Vec2i(chunkPosition.x + 1, chunkPosition.y), sectionHeight, chunk = neighbours[6]) + } + } + } + + fun listenBlocksSet(renderer: WorldRenderer) { + renderer.connection.events.listen { + val chunk = renderer.world[it.chunkPosition] ?: return@listen // should not happen + if (!chunk.isFullyLoaded) { + return@listen + } + val sectionHeights: Int2ObjectOpenHashMap = Int2ObjectOpenHashMap() + for (blockPosition in it.blocks.keys) { + val neighbours = sectionHeights.getOrPut(blockPosition.sectionHeight) { BooleanArray(Directions.SIZE) } + val inSectionHeight = blockPosition.y.inSectionHeight + if (inSectionHeight == 0) { + neighbours[0] = true + } else if (inSectionHeight == ProtocolDefinition.SECTION_MAX_Y) { + neighbours[1] = true + } + if (blockPosition.z == 0) { + neighbours[2] = true + } else if (blockPosition.z == ProtocolDefinition.SECTION_MAX_Z) { + neighbours[3] = true + } + if (blockPosition.x == 0) { + neighbours[4] = true + } else if (blockPosition.x == ProtocolDefinition.SECTION_MAX_X) { + neighbours[5] = true + } + } + val neighbours = chunk.neighbours.get() ?: return@listen + for ((sectionHeight, neighbourUpdates) in sectionHeights) { + renderer.queueSection(it.chunkPosition, sectionHeight, chunk, neighbours = neighbours) + + if (neighbourUpdates[0]) { + renderer.queueSection(it.chunkPosition, sectionHeight - 1, chunk, neighbours = neighbours) + } + if (neighbourUpdates[1]) { + renderer.queueSection(it.chunkPosition, sectionHeight + 1, chunk, neighbours = neighbours) + } + if (neighbourUpdates[2]) { + renderer.queueSection(Vec2i(it.chunkPosition.x, it.chunkPosition.y - 1), sectionHeight, chunk = neighbours[3]) + } + if (neighbourUpdates[3]) { + renderer.queueSection(Vec2i(it.chunkPosition.x, it.chunkPosition.y + 1), sectionHeight, chunk = neighbours[4]) + } + if (neighbourUpdates[4]) { + renderer.queueSection(Vec2i(it.chunkPosition.x - 1, it.chunkPosition.y), sectionHeight, chunk = neighbours[1]) + } + if (neighbourUpdates[5]) { + renderer.queueSection(Vec2i(it.chunkPosition.x + 1, it.chunkPosition.y), sectionHeight, chunk = neighbours[6]) + } + } + } + } + + fun register(renderer: WorldRenderer) { + val events = renderer.connection.events + + listenBlockSet(renderer) + listenBlocksSet(renderer) + + events.listen { if (it.dimensionChange) renderer.unloadWorld() } + events.listen { renderer.queueChunk(it.chunkPosition, it.chunk) } + + events.listen { + if (it.blockChange) { + // change is already covered + return@listen + } + renderer.queueSection(it.chunkPosition, it.sectionHeight, it.chunk) + } + + + events.listen { renderer.unloadChunk(it.chunkPosition) } + renderer.connection::state.observe(this) { if (it == PlayConnectionStates.DISCONNECTED) renderer.unloadWorld() } + events.listen { renderer.queueSection(it.blockPosition.chunkPosition, it.blockPosition.sectionHeight) } + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldRendererUtil.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/world/util/WorldRendererUtil.kt similarity index 76% rename from src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldRendererUtil.kt rename to src/main/java/de/bixilon/minosoft/gui/rendering/world/util/WorldRendererUtil.kt index cbc79191c..7c4e36f48 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/world/WorldRendererUtil.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/world/util/WorldRendererUtil.kt @@ -11,9 +11,12 @@ * This software is not affiliated with Mojang AB, the original developer of Minecraft. */ -package de.bixilon.minosoft.gui.rendering.world +package de.bixilon.minosoft.gui.rendering.world.util +import de.bixilon.minosoft.data.world.chunk.ChunkSection import de.bixilon.minosoft.gui.rendering.util.VecUtil.empty +import de.bixilon.minosoft.gui.rendering.world.WorldRenderer +import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition object WorldRendererUtil { const val STILL_LOADING_TIME = 50L @@ -22,4 +25,7 @@ object WorldRendererUtil { // If the player is still, then we can load more chunks (to not cause lags) val WorldRenderer.maxBusyTime: Long get() = if (connection.player.velocity.empty) STILL_LOADING_TIME else MOVING_LOADING_TIME // TODO: get of camera + + + val ChunkSection.smallMesh: Boolean get() = blocks.count < ProtocolDefinition.SECTION_MAX_X * ProtocolDefinition.SECTION_MAX_Z }