From 4d02696bc50aff013a01aa91f6a317e89b607a7f Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Thu, 27 Jul 2023 23:06:23 +0200 Subject: [PATCH 01/17] refactor heightmap --- .../world/chunk/light/GeneralHeightmapTest.kt | 41 +++--- .../data/world/chunk/light/SkyLightTraceIT.kt | 59 +++++++++ .../chunk/light/place/SkyLightPlaceIT.kt | 2 +- .../de/bixilon/minosoft/data/world/World.kt | 2 +- .../minosoft/data/world/chunk/chunk/Chunk.kt | 4 +- .../chunk/heightmap/AbstractHeightmap.kt | 26 ++++ .../world/chunk/heightmap/FixedHeightmap.kt | 30 +++++ .../data/world/chunk/heightmap/Heightmap.kt | 117 +++++++++++++++++ .../world/chunk/heightmap/LightHeightmap.kt | 63 +++++++++ .../data/world/chunk/light/ChunkLight.kt | 122 +----------------- 10 files changed, 326 insertions(+), 140 deletions(-) create mode 100644 src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/SkyLightTraceIT.kt create mode 100644 src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/AbstractHeightmap.kt create mode 100644 src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/FixedHeightmap.kt create mode 100644 src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/Heightmap.kt create mode 100644 src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/GeneralHeightmapTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/GeneralHeightmapTest.kt index 0390fc61d..15a369502 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/GeneralHeightmapTest.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/GeneralHeightmapTest.kt @@ -14,6 +14,8 @@ package de.bixilon.minosoft.data.world.chunk.light import de.bixilon.kotlinglm.vec3.Vec3i +import de.bixilon.minosoft.data.registries.blocks.GlassTest0 +import de.bixilon.minosoft.data.registries.blocks.LeavesTest0 import de.bixilon.minosoft.data.registries.blocks.types.stone.StoneTest0 import de.bixilon.minosoft.data.world.chunk.ChunkTestingUtil.createChunkWithNeighbours import de.bixilon.minosoft.data.world.chunk.chunk.Chunk @@ -25,15 +27,6 @@ import org.testng.annotations.Test @Test(groups = ["light"], dependsOnGroups = ["block"]) class GeneralHeightmapTest { - fun testMaxHeightEast() { - val chunk: Chunk = createChunkWithNeighbours() - chunk[Vec3i(2, 10, 3)] = StoneTest0.state - chunk[Vec3i(3, 11, 2)] = StoneTest0.state - chunk[Vec3i(3, 12, 4)] = StoneTest0.state - chunk[Vec3i(4, 13, 3)] = StoneTest0.state - assertEquals(chunk.light.getNeighbourMaxHeight(chunk.neighbours.get()!!, 3, 3), 14) - } - fun testMinHeightEast() { val chunk: Chunk = createChunkWithNeighbours() chunk[Vec3i(2, 10, 3)] = StoneTest0.state @@ -43,16 +36,6 @@ class GeneralHeightmapTest { assertEquals(chunk.light.getNeighbourMinHeight(chunk.neighbours.get()!!, 3, 3), 11) } - fun testMaxHeightNeighbourEast() { - val chunk: Chunk = createChunkWithNeighbours() - val neighbours = chunk.neighbours.get()!! - chunk[Vec3i(14, 10, 3)] = StoneTest0.state - chunk[Vec3i(15, 11, 2)] = StoneTest0.state - chunk[Vec3i(15, 12, 4)] = StoneTest0.state - neighbours[ChunkNeighbours.EAST][Vec3i(0, 13, 3)] = StoneTest0.state - assertEquals(chunk.light.getNeighbourMaxHeight(neighbours, 15, 3), 14) - } - fun testMinHeightNeighbourEast() { val chunk: Chunk = createChunkWithNeighbours() val neighbours = chunk.neighbours.get()!! @@ -62,6 +45,24 @@ class GeneralHeightmapTest { neighbours[ChunkNeighbours.EAST][Vec3i(0, 10, 3)] = StoneTest0.state assertEquals(chunk.light.getNeighbourMinHeight(neighbours, 15, 3), 11) } - // TODO: Test other directions + + fun `top of the world and not passing`() { + val chunk: Chunk = createChunkWithNeighbours() + chunk[Vec3i(2, 255, 3)] = StoneTest0.state + assertEquals(chunk.light.heightmap[2, 3], 256) + } + + fun `top of the world and entering`() { + val chunk: Chunk = createChunkWithNeighbours() + chunk[Vec3i(2, 255, 3)] = LeavesTest0.state + assertEquals(chunk.light.heightmap[2, 3], 255) + } + + fun `top of the world and passing`() { + val chunk: Chunk = createChunkWithNeighbours() + chunk[Vec3i(2, 255, 3)] = GlassTest0.state + assertEquals(chunk.light.heightmap[2, 3], Int.MIN_VALUE) + } + } diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/SkyLightTraceIT.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/SkyLightTraceIT.kt new file mode 100644 index 000000000..50418fd20 --- /dev/null +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/SkyLightTraceIT.kt @@ -0,0 +1,59 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.world.chunk.light + +import de.bixilon.kotlinglm.vec3.Vec3i +import de.bixilon.minosoft.data.registries.blocks.types.stone.StoneTest0 +import de.bixilon.minosoft.data.world.chunk.light.LightTestUtil.assertLight +import de.bixilon.minosoft.protocol.network.connection.play.ConnectionTestUtil.createConnection +import org.testng.annotations.Test + + +@Test(groups = ["light"], dependsOnGroups = ["block"], threadPoolSize = 8, priority = 1000) +class SkyLightTraceIT { + + fun `check level below block`() { + val world = createConnection(3, light = true).world + world[Vec3i(8, 10, 8)] = StoneTest0.state + world.assertLight(8, 9, 8, 0xD0) + } + + fun `heightmap optimization west, upper block set`() { + val world = createConnection(3, light = true).world + world[Vec3i(8, 10, 8)] = StoneTest0.state + world[Vec3i(7, 12, 8)] = StoneTest0.state + world.assertLight(7, 11, 8, 0xD0) + } + + fun `heightmap optimization east, upper block set`() { + val world = createConnection(3, light = true).world + world[Vec3i(8, 10, 8)] = StoneTest0.state + world[Vec3i(9, 12, 8)] = StoneTest0.state + world.assertLight(9, 11, 8, 0xD0) + } + + fun `heightmap optimization north, upper block set`() { + val world = createConnection(3, light = true).world + world[Vec3i(8, 10, 8)] = StoneTest0.state + world[Vec3i(8, 12, 7)] = StoneTest0.state + world.assertLight(8, 11, 7, 0xD0) + } + + fun `heightmap optimization south, upper block set`() { + val world = createConnection(3, light = true).world + world[Vec3i(8, 10, 8)] = StoneTest0.state + world[Vec3i(8, 12, 9)] = StoneTest0.state + world.assertLight(8, 11, 9, 0xD0) + } +} diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/SkyLightPlaceIT.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/SkyLightPlaceIT.kt index 34e9d7317..131ff0d67 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/SkyLightPlaceIT.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/SkyLightPlaceIT.kt @@ -23,7 +23,7 @@ import de.bixilon.minosoft.protocol.network.connection.play.ConnectionTestUtil.c import org.testng.annotations.Test -@Test(groups = ["light"], dependsOnGroups = ["block"], threadPoolSize = 8, priority = -100) +@Test(groups = ["light"], dependsOnGroups = ["block"], threadPoolSize = 8, priority = 1000) class SkyLightPlaceIT { fun aboveBlock() { diff --git a/src/main/java/de/bixilon/minosoft/data/world/World.kt b/src/main/java/de/bixilon/minosoft/data/world/World.kt index 9d5d8a3fa..b1d7fe616 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/World.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/World.kt @@ -228,7 +228,7 @@ class World( reset += { chunk.light.reset() } calculate += { if (heightmap) { - chunk.light.recalculateHeightmap() + chunk.light.heightmap.recalculate() } chunk.light.calculate() } diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt index 6c6053ca7..ed2c01de4 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt @@ -62,7 +62,7 @@ class Chunk( init { - light.recalculateHeightmap() + light.heightmap.recalculate() } @Deprecated("neighbours.complete", ReplaceWith("neighbours.complete")) @@ -162,7 +162,7 @@ class Chunk( if (executed.isEmpty()) { return lock.unlock() } - light.recalculateHeightmap() + light.heightmap.recalculate() light.recalculate() for (section in sections) { diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/AbstractHeightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/AbstractHeightmap.kt new file mode 100644 index 000000000..abb57c339 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/AbstractHeightmap.kt @@ -0,0 +1,26 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.world.chunk.heightmap + +import de.bixilon.minosoft.data.registries.blocks.state.BlockState + +interface AbstractHeightmap { + + fun recalculate() + + operator fun get(x: Int, z: Int): Int + operator fun get(index: Int): Int + + fun onBlockChange(x: Int, y: Int, z: Int, next: BlockState?) +} diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/FixedHeightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/FixedHeightmap.kt new file mode 100644 index 000000000..0381936bc --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/FixedHeightmap.kt @@ -0,0 +1,30 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.world.chunk.heightmap + +import de.bixilon.minosoft.data.registries.blocks.state.BlockState + +class FixedHeightmap(val value: Int) : AbstractHeightmap { + + override fun recalculate() = Unit + + override fun get(x: Int, z: Int) = value + override fun get(index: Int) = value + + override fun onBlockChange(x: Int, y: Int, z: Int, next: BlockState?) = Unit + + companion object { + val MAX_VALUE = FixedHeightmap(Int.MAX_VALUE) + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/Heightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/Heightmap.kt new file mode 100644 index 000000000..7a8825d9d --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/Heightmap.kt @@ -0,0 +1,117 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.world.chunk.heightmap + +import de.bixilon.minosoft.data.registries.blocks.state.BlockState +import de.bixilon.minosoft.data.world.chunk.chunk.Chunk +import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight +import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition + +abstract class Heightmap(protected val chunk: Chunk) : AbstractHeightmap { + protected val heightmap = IntArray(ProtocolDefinition.SECTION_WIDTH_X * ProtocolDefinition.SECTION_WIDTH_Z) { Int.MIN_VALUE } + + override fun get(index: Int) = heightmap[index] + override fun get(x: Int, z: Int) = heightmap[(z shl 4) or x] + + + protected abstract fun passes(state: BlockState): HeightmapPass + protected abstract fun onHeightmapUpdate(x: Int, z: Int, previous: Int, now: Int) + + + override fun recalculate() { + chunk.lock.lock() + val maxY = chunk.maxSection * ProtocolDefinition.SECTION_HEIGHT_Y + + for (x in 0 until ProtocolDefinition.SECTION_WIDTH_X) { + for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) { + trace(x, maxY, z, false) + } + } + chunk.lock.unlock() + } + + + private fun trace(x: Int, startY: Int, z: Int, notify: Boolean) { + val sections = chunk.sections + + var y = Int.MIN_VALUE + + sectionLoop@ for (sectionIndex in (startY.sectionHeight - chunk.minSection) downTo 0) { + if (sectionIndex >= sections.size) { + // starting from above world + continue + } + val section = sections[sectionIndex] ?: continue + if (section.blocks.isEmpty) continue + + section.acquire() + for (sectionY in ProtocolDefinition.SECTION_MAX_Y downTo 0) { + val state = section.blocks[x, sectionY, z] ?: continue + val pass = passes(state) + if (pass == HeightmapPass.PASSES) continue + + y = (sectionIndex + chunk.minSection) * ProtocolDefinition.SECTION_HEIGHT_Y + sectionY + 1 + if (pass == HeightmapPass.ABOVE) y++ + + section.release() + break@sectionLoop + } + section.release() + } + val index = (z shl 4) or x + val previous = heightmap[index] + + if (previous == y) return + + heightmap[index] = y + + if (notify) { + onHeightmapUpdate(x, z, previous, y) + } + } + + + override fun onBlockChange(x: Int, y: Int, z: Int, next: BlockState?) { + chunk.lock.lock() + val index = (z shl 4) or x + + val current = heightmap[index] + + if (current > y + 1) { + // our block is/was not the highest, ignore everything + chunk.lock.unlock() + return + } + if (next == null) { + trace(x, y, z, true) + chunk.lock.unlock() + return + } + + when (passes(next)) { + HeightmapPass.ABOVE -> heightmap[index] = y + 1 + HeightmapPass.IN -> heightmap[index] = y + HeightmapPass.PASSES -> Unit + } + + chunk.lock.unlock() + } + + protected enum class HeightmapPass { + ABOVE, + IN, + PASSES, + ; + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt new file mode 100644 index 000000000..49cf5a3b0 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt @@ -0,0 +1,63 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.world.chunk.heightmap + +import de.bixilon.minosoft.data.direction.Directions +import de.bixilon.minosoft.data.registries.blocks.state.BlockState +import de.bixilon.minosoft.data.world.chunk.chunk.Chunk +import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight + +class LightHeightmap(chunk: Chunk) : Heightmap(chunk) { + + + override fun recalculate() { + super.recalculate() + chunk.light.calculateSkylight() + } + + override fun onHeightmapUpdate(x: Int, z: Int, previous: Int, now: Int) { + if (previous > now) { + // block is lower + return chunk.light.startSkylightFloodFill(x, z) + } + // block is now higher + // ToDo: Neighbours + val sections = chunk.sections + val maxIndex = previous.sectionHeight - chunk.minSection + val minIndex = now.sectionHeight - chunk.minSection + chunk.light.bottom.reset() + for (index in maxIndex downTo minIndex) { + val section = sections[index] ?: continue + section.light.reset() + } + for (index in maxIndex downTo minIndex) { + val section = sections[index] ?: continue + section.light.calculate() + } + chunk.light.calculateSkylight() + } + + + override fun passes(state: BlockState): HeightmapPass { + val light = state.block.getLightProperties(state) + if (!light.skylightEnters) return HeightmapPass.ABOVE + + if (!light.filtersSkylight && light.propagatesLight(Directions.DOWN)) { + // can go through block + return HeightmapPass.PASSES + } + + return HeightmapPass.IN + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLight.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLight.kt index 63ded434d..a48ff7d50 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLight.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLight.kt @@ -20,6 +20,8 @@ import de.bixilon.minosoft.data.registries.blocks.state.BlockState import de.bixilon.minosoft.data.registries.dimension.DimensionProperties import de.bixilon.minosoft.data.world.chunk.ChunkSection import de.bixilon.minosoft.data.world.chunk.chunk.Chunk +import de.bixilon.minosoft.data.world.chunk.heightmap.FixedHeightmap +import de.bixilon.minosoft.data.world.chunk.heightmap.LightHeightmap import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours import de.bixilon.minosoft.data.world.chunk.update.AbstractWorldUpdate import de.bixilon.minosoft.data.world.chunk.update.chunk.ChunkLightUpdate @@ -29,7 +31,7 @@ import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition class ChunkLight(private val chunk: Chunk) { private val connection = chunk.connection - val heightmap = IntArray(ProtocolDefinition.SECTION_WIDTH_X * ProtocolDefinition.SECTION_WIDTH_Z) { if (chunk.world.dimension.canSkylight()) Int.MIN_VALUE else Int.MAX_VALUE } + val heightmap = if (chunk.world.dimension.canSkylight()) LightHeightmap(chunk) else FixedHeightmap.MAX_VALUE val bottom = BorderSectionLight(false, chunk) val top = BorderSectionLight(true, chunk) @@ -39,10 +41,7 @@ class ChunkLight(private val chunk: Chunk) { if (!chunk.world.dimension.light) { return } - val heightmapIndex = (z shl 4) or x - val previous = heightmap[heightmapIndex] - recalculateHeightmap(x, y, z, next) - onHeightmapUpdate(x, y, z, previous, heightmap[heightmapIndex]) + heightmap.onBlockChange(x, y, z, next) val neighbours = chunk.neighbours.get() ?: return @@ -180,118 +179,8 @@ class ChunkLight(private val chunk: Chunk) { } } - fun recalculateHeightmap() { - if (!chunk.world.dimension.canSkylight()) { - return - } - chunk.lock.lock() - val maxY = chunk.maxSection * ProtocolDefinition.SECTION_HEIGHT_Y - for (x in 0 until ProtocolDefinition.SECTION_WIDTH_X) { - for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) { - checkHeightmapY(x, maxY, z) - } - } - chunk.lock.unlock() - calculateSkylight() - } - - private fun checkHeightmapY(x: Int, startY: Int, z: Int) { - val sections = chunk.sections - - var y = Int.MIN_VALUE - - sectionLoop@ for (sectionIndex in (startY.sectionHeight - chunk.minSection) downTo 0) { - if (sectionIndex >= sections.size) { - // starting from above world - continue - } - val section = sections[sectionIndex] ?: continue - if (section.blocks.isEmpty) continue - - section.acquire() - for (sectionY in ProtocolDefinition.SECTION_MAX_Y downTo 0) { - val state = section.blocks[x, sectionY, z] ?: continue - val light = state.block.getLightProperties(state) - - if (light.skylightEnters && !light.filtersSkylight && light.propagatesLight(Directions.DOWN)) { - // can go through block - continue - } - y = (sectionIndex + chunk.minSection) * ProtocolDefinition.SECTION_HEIGHT_Y + sectionY - if (!light.skylightEnters) { - y++ - } - section.release() - break@sectionLoop - } - section.release() - } - val heightmapIndex = (z shl 4) or x - heightmap[heightmapIndex] = y - } - - private fun onHeightmapUpdate(x: Int, y: Int, z: Int, previous: Int, now: Int) { - if (previous == now) { - return - } - - if (previous < y) { - // block is now higher - // ToDo: Neighbours - val sections = chunk.sections - val maxIndex = previous.sectionHeight - chunk.minSection - val minIndex = now.sectionHeight - chunk.minSection - bottom.reset() - for (index in maxIndex downTo minIndex) { - val section = sections[index] ?: continue - section.light.reset() - } - for (index in maxIndex downTo minIndex) { - val section = sections[index] ?: continue - section.light.calculate() - } - calculateSkylight() - } else if (previous > y && chunk.world.dimension.canSkylight()) { - // block is lower - startSkylightFloodFill(x, z) - } - } - - private fun recalculateHeightmap(x: Int, y: Int, z: Int, blockState: BlockState?) { - if (!chunk.world.dimension.canSkylight()) { - return - } - chunk.lock.lock() - val index = (z shl 4) or x - - val current = heightmap[index] - - if (current > y + 1) { - // our block is/was not the highest, ignore everything - chunk.lock.unlock() - return - } - if (blockState == null) { - checkHeightmapY(x, y, z) - chunk.lock.unlock() - return - } - - // we are the highest block now - // check if light can pass - val light = blockState.block.getLightProperties(blockState) - if (!light.skylightEnters) { - heightmap[index] = y + 1 - } else if (light.filtersSkylight || !light.propagatesLight(Directions.DOWN)) { - heightmap[index] = y - } - - chunk.lock.unlock() - return - } - - private fun calculateSkylight() { + fun calculateSkylight() { if (!chunk.world.dimension.canSkylight() || !chunk.neighbours.complete) { // no need to calculate it return @@ -400,6 +289,7 @@ class ChunkLight(private val chunk: Chunk) { } } + @Deprecated("heightmap") inline fun getMaxHeight(x: Int, z: Int): Int { return heightmap[(z shl 4) or x] } From b7ac8e778f5fb73ba1322398518e8605fc8e4120 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Thu, 27 Jul 2023 23:17:02 +0200 Subject: [PATCH 02/17] fix some heightmap bugs, improve heightmap performance --- build.gradle.kts | 2 +- .../data/world/chunk/light/GeneralHeightmapTest.kt | 4 ++-- .../minosoft/data/world/chunk/heightmap/Heightmap.kt | 9 +++++++-- .../data/world/chunk/heightmap/LightHeightmap.kt | 9 ++++----- .../minosoft/data/world/chunk/light/ChunkLight.kt | 2 +- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e11c9a5eb..97eda7c0b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -222,7 +222,7 @@ testing { options { val options = this as TestNGOptions options.preserveOrder = true - // options.excludeGroups("input", "font", "command", "registry", "biome", "version", "fluid", "world", "raycasting", "pixlyzer", "item", "block", "physics", "light", "packet", "container", "item_stack", "signature", "private_key", "interaction", "item_digging", "world_renderer", "rendering") + options.excludeGroups("input", "font", "command", "registry", "biome", "version", "fluid", "world", "raycasting", "pixlyzer", "item", "physics", "packet", "container", "item_stack", "signature", "private_key", "interaction", "item_digging", "world_renderer", "rendering") } } } diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/GeneralHeightmapTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/GeneralHeightmapTest.kt index 15a369502..4e72a20ee 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/GeneralHeightmapTest.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/GeneralHeightmapTest.kt @@ -15,7 +15,7 @@ package de.bixilon.minosoft.data.world.chunk.light import de.bixilon.kotlinglm.vec3.Vec3i import de.bixilon.minosoft.data.registries.blocks.GlassTest0 -import de.bixilon.minosoft.data.registries.blocks.LeavesTest0 +import de.bixilon.minosoft.data.registries.blocks.StairsTest0 import de.bixilon.minosoft.data.registries.blocks.types.stone.StoneTest0 import de.bixilon.minosoft.data.world.chunk.ChunkTestingUtil.createChunkWithNeighbours import de.bixilon.minosoft.data.world.chunk.chunk.Chunk @@ -55,7 +55,7 @@ class GeneralHeightmapTest { fun `top of the world and entering`() { val chunk: Chunk = createChunkWithNeighbours() - chunk[Vec3i(2, 255, 3)] = LeavesTest0.state + chunk[Vec3i(2, 255, 3)] = StairsTest0.state assertEquals(chunk.light.heightmap[2, 3], 255) } diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/Heightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/Heightmap.kt index 7a8825d9d..9767a6115 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/Heightmap.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/Heightmap.kt @@ -55,13 +55,18 @@ abstract class Heightmap(protected val chunk: Chunk) : AbstractHeightmap { val section = sections[sectionIndex] ?: continue if (section.blocks.isEmpty) continue + val min = section.blocks.minPosition + val max = section.blocks.maxPosition + + if (x < min.x || x > max.x || z < min.z || z > max.z) continue // out of section + section.acquire() - for (sectionY in ProtocolDefinition.SECTION_MAX_Y downTo 0) { + for (sectionY in max.y downTo min.y) { val state = section.blocks[x, sectionY, z] ?: continue val pass = passes(state) if (pass == HeightmapPass.PASSES) continue - y = (sectionIndex + chunk.minSection) * ProtocolDefinition.SECTION_HEIGHT_Y + sectionY + 1 + y = (sectionIndex + chunk.minSection) * ProtocolDefinition.SECTION_HEIGHT_Y + sectionY if (pass == HeightmapPass.ABOVE) y++ section.release() diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt index 49cf5a3b0..ffd7f195f 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt @@ -53,11 +53,10 @@ class LightHeightmap(chunk: Chunk) : Heightmap(chunk) { val light = state.block.getLightProperties(state) if (!light.skylightEnters) return HeightmapPass.ABOVE - if (!light.filtersSkylight && light.propagatesLight(Directions.DOWN)) { - // can go through block - return HeightmapPass.PASSES - } + if (light.filtersSkylight) return HeightmapPass.IN + if (!light.propagatesLight(Directions.DOWN)) return HeightmapPass.IN - return HeightmapPass.IN + + return HeightmapPass.PASSES } } diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLight.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLight.kt index a48ff7d50..9a6649e3a 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLight.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLight.kt @@ -248,7 +248,7 @@ class ChunkLight(private val chunk: Chunk) { ) } - private fun startSkylightFloodFill(x: Int, z: Int) { + fun startSkylightFloodFill(x: Int, z: Int) { val neighbours = chunk.neighbours.get() ?: return val heightmapIndex = (z shl 4) or x val maxHeight = heightmap[heightmapIndex] From b5bb290727e58a9fa6387e2356542745254ad744 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Thu, 27 Jul 2023 23:27:04 +0200 Subject: [PATCH 03/17] heightmap: properly call update when block is placed --- .../chunk/heightmap/AbstractHeightmap.kt | 2 +- .../world/chunk/heightmap/FixedHeightmap.kt | 2 +- .../data/world/chunk/heightmap/Heightmap.kt | 22 +++++++++++-------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/AbstractHeightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/AbstractHeightmap.kt index abb57c339..5dd6004b6 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/AbstractHeightmap.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/AbstractHeightmap.kt @@ -22,5 +22,5 @@ interface AbstractHeightmap { operator fun get(x: Int, z: Int): Int operator fun get(index: Int): Int - fun onBlockChange(x: Int, y: Int, z: Int, next: BlockState?) + fun onBlockChange(x: Int, y: Int, z: Int, state: BlockState?) } diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/FixedHeightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/FixedHeightmap.kt index 0381936bc..4cf8bfb6c 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/FixedHeightmap.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/FixedHeightmap.kt @@ -22,7 +22,7 @@ class FixedHeightmap(val value: Int) : AbstractHeightmap { override fun get(x: Int, z: Int) = value override fun get(index: Int) = value - override fun onBlockChange(x: Int, y: Int, z: Int, next: BlockState?) = Unit + override fun onBlockChange(x: Int, y: Int, z: Int, state: BlockState?) = Unit companion object { val MAX_VALUE = FixedHeightmap(Int.MAX_VALUE) diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/Heightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/Heightmap.kt index 9767a6115..c8a75f2d5 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/Heightmap.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/Heightmap.kt @@ -41,7 +41,6 @@ abstract class Heightmap(protected val chunk: Chunk) : AbstractHeightmap { chunk.lock.unlock() } - private fun trace(x: Int, startY: Int, z: Int, notify: Boolean) { val sections = chunk.sections @@ -87,30 +86,35 @@ abstract class Heightmap(protected val chunk: Chunk) : AbstractHeightmap { } - override fun onBlockChange(x: Int, y: Int, z: Int, next: BlockState?) { + override fun onBlockChange(x: Int, y: Int, z: Int, state: BlockState?) { chunk.lock.lock() val index = (z shl 4) or x - val current = heightmap[index] + val previous = heightmap[index] - if (current > y + 1) { + if (previous > y + 1) { // our block is/was not the highest, ignore everything chunk.lock.unlock() return } - if (next == null) { + if (state == null) { trace(x, y, z, true) chunk.lock.unlock() return } - when (passes(next)) { - HeightmapPass.ABOVE -> heightmap[index] = y + 1 - HeightmapPass.IN -> heightmap[index] = y - HeightmapPass.PASSES -> Unit + val next = when (passes(state)) { + HeightmapPass.ABOVE -> y + 1 + HeightmapPass.IN -> y + HeightmapPass.PASSES -> previous } chunk.lock.unlock() + + if (previous != next) { + heightmap[index] = next + onHeightmapUpdate(x, z, previous, next) + } } protected enum class HeightmapPass { From 8ccb362ef6e091dc9f00947ac1e0467b6a1ee7e5 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Thu, 27 Jul 2023 23:57:28 +0200 Subject: [PATCH 04/17] fix double press key combination, tests Now we can fly again! --- .../binding/actions/KeyActionFilterTest.kt | 97 +++++++++++++++++++ .../binding/actions/KeyActionFilter.kt | 2 +- 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/input/key/manager/binding/actions/KeyActionFilterTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/input/key/manager/binding/actions/KeyActionFilterTest.kt index 8418c882b..c0534b202 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/input/key/manager/binding/actions/KeyActionFilterTest.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/input/key/manager/binding/actions/KeyActionFilterTest.kt @@ -26,20 +26,27 @@ import de.bixilon.minosoft.gui.rendering.input.key.manager.binding.KeyBindingFil import de.bixilon.minosoft.gui.rendering.input.key.manager.binding.KeyBindingState import de.bixilon.minosoft.test.IT.OBJENESIS import de.bixilon.minosoft.util.KUtil.set +import it.unimi.dsi.fastutil.objects.Object2LongMap +import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap import org.testng.Assert.assertFalse import org.testng.Assert.assertTrue import org.testng.annotations.Test +val times = InputManager::class.java.getDeclaredField("times").apply { isAccessible = true } val keysPressed = InputManager::class.java.getDeclaredField("pressed").apply { isAccessible = true } val bindingsPressed = BindingsManager::class.java.getDeclaredField("pressed").apply { isAccessible = true } val name = minosoft("dummy") +val InputManager.times: Object2LongMap get() = de.bixilon.minosoft.gui.rendering.input.key.manager.binding.actions.times.get(this).unsafeCast() + + fun input(): InputManager { val manager = OBJENESIS.newInstance(InputManager::class.java) val bindings = OBJENESIS.newInstance(BindingsManager::class.java) bindingsPressed[bindings] = mutableSetOf() manager::bindings.forceSet(bindings) + times[manager] = Object2LongOpenHashMap() keysPressed[manager] = KeyCodes.set() @@ -353,3 +360,93 @@ class Sticky { assertTrue(state.satisfied) } } + + +@Test(groups = ["input"]) +class DoublePress { + + fun `press single`() { + val state = KeyBindingFilterState(false) + KeyActionFilter.DoublePress.check( + state, setOf(KeyCodes.KEY_0), input(), name, + KeyBindingState(KeyBinding(mapOf(KeyActions.DOUBLE_PRESS to setOf(KeyCodes.KEY_0)))), + KeyCodes.KEY_0, + pressed = true, + 0L, + ) + + assertFalse(state.satisfied) + } + + fun `double press`() { + val state = KeyBindingFilterState(false) + val input = input() + input.times.put(KeyCodes.KEY_0, 1000L) + + KeyActionFilter.DoublePress.check( + state, setOf(KeyCodes.KEY_0), input, name, + KeyBindingState(KeyBinding(mapOf(KeyActions.DOUBLE_PRESS to setOf(KeyCodes.KEY_0)))), + KeyCodes.KEY_0, + pressed = true, + 1100L, + ) + + assertTrue(state.satisfied) + assertTrue(state.result) + assertTrue(state.store) + } + + fun `double press second time`() { + val state = KeyBindingFilterState(false) + val input = input() + + val pressed = bindingsPressed[input.bindings].unsafeCast>() + pressed += name + + input.times.put(KeyCodes.KEY_0, 1000L) + + KeyActionFilter.DoublePress.check( + state, setOf(KeyCodes.KEY_0), input, name, + KeyBindingState(KeyBinding(mapOf(KeyActions.DOUBLE_PRESS to setOf(KeyCodes.KEY_0)))), + KeyCodes.KEY_0, + pressed = true, + 1100L, + ) + + assertTrue(state.satisfied) + assertFalse(state.result) + assertTrue(state.store) + } + + fun `too long ago press`() { + val state = KeyBindingFilterState(false) + val input = input() + input.times.put(KeyCodes.KEY_0, 1000L) + + KeyActionFilter.DoublePress.check( + state, setOf(KeyCodes.KEY_0), input, name, + KeyBindingState(KeyBinding(mapOf(KeyActions.DOUBLE_PRESS to setOf(KeyCodes.KEY_0)))), + KeyCodes.KEY_0, + pressed = true, + 10000L, + ) + + assertFalse(state.satisfied) + } + + fun `delay between`() { + val state = KeyBindingFilterState(false) + val input = input() + input.times.put(KeyCodes.KEY_0, 10L) + + KeyActionFilter.DoublePress.check( + state, setOf(KeyCodes.KEY_0), input, name, + KeyBindingState(KeyBinding(mapOf(KeyActions.DOUBLE_PRESS to setOf(KeyCodes.KEY_0)))), + KeyCodes.KEY_0, + pressed = true, + 100L, + ) + + assertFalse(state.satisfied) + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/input/key/manager/binding/actions/KeyActionFilter.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/input/key/manager/binding/actions/KeyActionFilter.kt index 6ef3b53bd..487801073 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/input/key/manager/binding/actions/KeyActionFilter.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/input/key/manager/binding/actions/KeyActionFilter.kt @@ -118,7 +118,7 @@ interface KeyActionFilter { filter.satisfied = false return } - filter.result = input.bindings.isDown(name) + filter.result = !input.bindings.isDown(name) } } From fa3e45043ddef8368c3e0c8a27232bfe01d81529 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Fri, 28 Jul 2023 00:02:44 +0200 Subject: [PATCH 05/17] fix some skylight tests --- .../minosoft/data/world/chunk/light/SkyLightTraceIT.kt | 10 +++++----- .../minosoft/gui/rendering/chunk/light/RenderLight.kt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/SkyLightTraceIT.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/SkyLightTraceIT.kt index 50418fd20..485636830 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/SkyLightTraceIT.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/SkyLightTraceIT.kt @@ -26,34 +26,34 @@ class SkyLightTraceIT { fun `check level below block`() { val world = createConnection(3, light = true).world world[Vec3i(8, 10, 8)] = StoneTest0.state - world.assertLight(8, 9, 8, 0xD0) + world.assertLight(8, 9, 8, 0xE0) } fun `heightmap optimization west, upper block set`() { val world = createConnection(3, light = true).world world[Vec3i(8, 10, 8)] = StoneTest0.state world[Vec3i(7, 12, 8)] = StoneTest0.state - world.assertLight(7, 11, 8, 0xD0) + world.assertLight(7, 11, 8, 0xE0) } fun `heightmap optimization east, upper block set`() { val world = createConnection(3, light = true).world world[Vec3i(8, 10, 8)] = StoneTest0.state world[Vec3i(9, 12, 8)] = StoneTest0.state - world.assertLight(9, 11, 8, 0xD0) + world.assertLight(9, 11, 8, 0xE0) } fun `heightmap optimization north, upper block set`() { val world = createConnection(3, light = true).world world[Vec3i(8, 10, 8)] = StoneTest0.state world[Vec3i(8, 12, 7)] = StoneTest0.state - world.assertLight(8, 11, 7, 0xD0) + world.assertLight(8, 11, 7, 0xE0) } fun `heightmap optimization south, upper block set`() { val world = createConnection(3, light = true).world world[Vec3i(8, 10, 8)] = StoneTest0.state world[Vec3i(8, 12, 9)] = StoneTest0.state - world.assertLight(8, 11, 9, 0xD0) + world.assertLight(8, 11, 9, 0xE0) } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/light/RenderLight.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/light/RenderLight.kt index 5d24d58c9..0c5b40ed7 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/light/RenderLight.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/light/RenderLight.kt @@ -76,7 +76,7 @@ class RenderLight(val context: RenderContext) { } private companion object { - val RECALCULATE = minosoft("recalculate") + val RECALCULATE = minosoft("recalculate_light") val FULLBRIGHT = minosoft("fullbright") } } From fbf95e4f8d9bfd336674ee96bdb10617ae05a1af Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Fri, 28 Jul 2023 00:33:56 +0200 Subject: [PATCH 06/17] move chunk neighbour tracing to chunk neighbours --- .../minosoft/data/world/WorldTestUtil.kt | 4 +- .../minosoft/camera/target/TargetHandler.kt | 2 +- .../minosoft/data/registries/fluid/Fluid.kt | 2 +- .../de/bixilon/minosoft/data/world/World.kt | 2 +- .../minosoft/data/world/chunk/chunk/Chunk.kt | 71 +------------------ .../world/chunk/neighbours/ChunkNeighbours.kt | 66 +++++++++++++++++ .../data/world/iterator/WorldIterator.kt | 2 +- .../particle/types/render/RenderParticle.kt | 2 +- .../gui/rendering/sky/box/SkyboxRenderer.kt | 2 +- .../minosoft/physics/EntityPositionInfo.kt | 2 +- .../packets/s2c/play/ExplosionS2CP.kt | 2 +- 11 files changed, 77 insertions(+), 80 deletions(-) diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/WorldTestUtil.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/WorldTestUtil.kt index 46520f0ef..ea71d7826 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/WorldTestUtil.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/WorldTestUtil.kt @@ -73,7 +73,7 @@ object WorldTestUtil { if (chunk == null) { chunk = this.chunks[chunkPosition] ?: continue } else if (chunk.chunkPosition != chunkPosition) { - chunk = chunk.traceChunk(chunkPosition - chunk.chunkPosition) ?: continue + chunk = chunk.neighbours.trace(chunkPosition - chunk.chunkPosition) ?: continue } for (y in start.y..end.y) { val section = chunk.getOrPut(y.sectionHeight) ?: continue @@ -96,7 +96,7 @@ object WorldTestUtil { for (z in (start.z shr 4)..(end.z shr 4)) { val chunkPosition = Vec2i(x, z) chunk = if (chunk != null) { - chunk.traceChunk(chunkPosition - chunk.chunkPosition) ?: continue + chunk.neighbours.trace(chunkPosition - chunk.chunkPosition) ?: continue } else { this.chunks[chunkPosition] ?: continue } diff --git a/src/main/java/de/bixilon/minosoft/camera/target/TargetHandler.kt b/src/main/java/de/bixilon/minosoft/camera/target/TargetHandler.kt index 5bf68eb30..3e85646df 100644 --- a/src/main/java/de/bixilon/minosoft/camera/target/TargetHandler.kt +++ b/src/main/java/de/bixilon/minosoft/camera/target/TargetHandler.kt @@ -124,7 +124,7 @@ class TargetHandler( if (chunk == null) { chunk = camera.connection.world.chunks[chunkPosition] ?: break } else if (chunk.chunkPosition != chunkPosition) { - chunk = chunk.traceChunk(chunkPosition - chunk.chunkPosition) ?: break + chunk = chunk.neighbours.trace(chunkPosition - chunk.chunkPosition) ?: break } val state = chunk[blockPosition.inChunkPosition] ?: continue if (state.block is FluidBlock) { diff --git a/src/main/java/de/bixilon/minosoft/data/registries/fluid/Fluid.kt b/src/main/java/de/bixilon/minosoft/data/registries/fluid/Fluid.kt index 197a93b38..04403e8e5 100644 --- a/src/main/java/de/bixilon/minosoft/data/registries/fluid/Fluid.kt +++ b/src/main/java/de/bixilon/minosoft/data/registries/fluid/Fluid.kt @@ -54,7 +54,7 @@ abstract class Fluid(override val identifier: ResourceLocation) : RegistryItem() val offset = blockPosition.inChunkPosition for (direction in Directions.SIDES) { - val neighbour = chunk.traceBlock(offset + direction) ?: continue + val neighbour = chunk.neighbours.traceBlock(offset + direction) ?: continue if (!this.matches(neighbour)) { continue } diff --git a/src/main/java/de/bixilon/minosoft/data/world/World.kt b/src/main/java/de/bixilon/minosoft/data/world/World.kt index b1d7fe616..d2930237c 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/World.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/World.kt @@ -187,7 +187,7 @@ class World( chunkDelta.x = (origin.x - position.x) shr 4 chunkDelta.y = (origin.z - position.z) shr 4 - val state = chunk.traceBlock(position.x and 0x0F, position.y, position.z and 0x0F, chunkDelta) ?: return + val state = chunk.neighbours.traceBlock(position.x and 0x0F, position.y, position.z and 0x0F, chunkDelta) ?: return if (state.block !is RandomDisplayTickable) return if (!state.block.hasRandomTicks(connection, state, position)) return diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt index ed2c01de4..1841788c9 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt @@ -15,7 +15,6 @@ package de.bixilon.minosoft.data.world.chunk.chunk import de.bixilon.kotlinglm.vec2.Vec2i import de.bixilon.kotlinglm.vec3.Vec3i import de.bixilon.kutil.concurrent.lock.thread.ThreadLock -import de.bixilon.kutil.exception.Broken import de.bixilon.kutil.math.simple.IntMath.clamp import de.bixilon.kutil.reflection.ReflectionUtil.forceSet import de.bixilon.minosoft.data.direction.Directions @@ -31,15 +30,12 @@ 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.positions.ChunkPosition -import de.bixilon.minosoft.data.world.positions.ChunkPositionUtil.chunkPosition import de.bixilon.minosoft.data.world.positions.InChunkPosition import de.bixilon.minosoft.data.world.positions.SectionHeight import de.bixilon.minosoft.gui.rendering.util.VecUtil.inSectionHeight import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight -import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.inChunkPosition import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition -import de.bixilon.minosoft.util.chunk.ChunkUtil import java.util.* /** @@ -65,10 +61,6 @@ class Chunk( light.heightmap.recalculate() } - @Deprecated("neighbours.complete", ReplaceWith("neighbours.complete")) - val isFullyLoaded: Boolean - get() = neighbours.complete - operator fun get(sectionHeight: SectionHeight): ChunkSection? = sections.getOrNull(sectionHeight - minSection) operator fun get(x: Int, y: Int, z: Int): BlockState? { @@ -203,7 +195,7 @@ class Chunk( if (neighbours != null) { for (neighbour in neighbours) { val neighbourNeighbours = neighbour.neighbours.get() ?: continue - neighbour.updateNeighbours(neighbourNeighbours, sectionHeight) + neighbour.neighbours.update(neighbourNeighbours, sectionHeight) } } @@ -235,67 +227,6 @@ class Chunk( } return biomeSource.getBiome(x and 0x0F, y, z and 0x0F) } - - @Deprecated("") - private fun updateNeighbours(neighbours: Array, sectionHeight: Int) { - for (nextSectionHeight in sectionHeight - 1..sectionHeight + 1) { - if (nextSectionHeight < minSection || nextSectionHeight > maxSection) { - continue - } - - val section = this[nextSectionHeight] ?: continue - val sectionNeighbours = ChunkUtil.getDirectNeighbours(neighbours, this, nextSectionHeight) - section.neighbours = sectionNeighbours - } - } - - @Deprecated("neighbours") - fun traceBlock(offset: Vec3i, origin: Vec3i, blockPosition: Vec3i = origin + offset): BlockState? { - val chunkDelta = (origin - blockPosition).chunkPosition - - return traceBlock(blockPosition.x and 0x0F, blockPosition.y, blockPosition.z and 0x0F, chunkDelta) - } - - @Deprecated("neighbours") - fun traceBlock(offset: Vec3i): BlockState? { - return traceBlock(offset.inChunkPosition, offset.chunkPosition) - } - - @Deprecated("neighbours") - fun traceChunk(offset: Vec2i): Chunk? { - if (offset.x == 0 && offset.y == 0) { - return this - } - - if (offset.x > 0) { - offset.x-- - return neighbours[6]?.traceChunk(offset) - } - if (offset.x < 0) { - offset.x++ - return neighbours[1]?.traceChunk(offset) - } - if (offset.y > 0) { - offset.y-- - return neighbours[4]?.traceChunk(offset) - } - if (offset.y < 0) { - offset.y++ - return neighbours[3]?.traceChunk(offset) - } - - Broken("Can not get chunk from offset: $offset") - } - - @Deprecated("neighbours") - private fun traceBlock(inChunkPosition: Vec3i, chunkOffset: Vec2i): BlockState? { - return traceChunk(chunkOffset)?.get(inChunkPosition) - } - - @Deprecated("neighbours") - fun traceBlock(x: Int, y: Int, z: Int, chunkOffset: Vec2i): BlockState? { - return traceChunk(chunkOffset)?.get(x, y, z) - } } diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/neighbours/ChunkNeighbours.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/neighbours/ChunkNeighbours.kt index 0038b5f39..0a51514a2 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/neighbours/ChunkNeighbours.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/neighbours/ChunkNeighbours.kt @@ -14,12 +14,16 @@ package de.bixilon.minosoft.data.world.chunk.neighbours import de.bixilon.kotlinglm.vec2.Vec2i +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.chunk.ChunkSection import de.bixilon.minosoft.data.world.chunk.chunk.Chunk +import de.bixilon.minosoft.data.world.positions.ChunkPositionUtil.chunkPosition import de.bixilon.minosoft.data.world.positions.SectionHeight +import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.inChunkPosition import de.bixilon.minosoft.util.chunk.ChunkUtil class ChunkNeighbours(val chunk: Chunk) : Iterable { @@ -100,6 +104,68 @@ class ChunkNeighbours(val chunk: Chunk) : Iterable { return neighbours.iterator() } + + fun update(neighbours: Array, sectionHeight: Int) { + for (nextSectionHeight in sectionHeight - 1..sectionHeight + 1) { + if (nextSectionHeight < chunk.minSection || nextSectionHeight > chunk.maxSection) { + continue + } + + val section = chunk[nextSectionHeight] ?: continue + val sectionNeighbours = ChunkUtil.getDirectNeighbours(neighbours, chunk, nextSectionHeight) + section.neighbours = sectionNeighbours + } + } + + fun trace(offset: Vec2i): Chunk? { + if (offset.x == 0 && offset.y == 0) { + return chunk + } + + val chunk = when { + offset.x > 0 -> { + offset.x-- + this[6] + } + + offset.x < 0 -> { + offset.x++ + this[1] + } + + offset.y < 0 -> { + offset.y-- + this[4] + } + + offset.y > 0 -> { + offset.y++ + this[3] + } + + else -> Broken("Can not get chunk from offset: $offset") + } + return chunk?.neighbours?.trace(offset) + } + + fun traceBlock(offset: Vec3i, origin: Vec3i, blockPosition: Vec3i = origin + offset): BlockState? { + val chunkDelta = (origin - blockPosition).chunkPosition + + return traceBlock(blockPosition.x and 0x0F, blockPosition.y, blockPosition.z and 0x0F, chunkDelta) + } + + fun traceBlock(offset: Vec3i): BlockState? { + return traceBlock(offset.inChunkPosition, offset.chunkPosition) + } + + private fun traceBlock(inChunkPosition: Vec3i, chunkOffset: Vec2i): BlockState? { + return trace(chunkOffset)?.get(inChunkPosition) + } + + fun traceBlock(x: Int, y: Int, z: Int, chunkOffset: Vec2i): BlockState? { + return trace(chunkOffset)?.get(x, y, z) + } + companion object { const val COUNT = 8 const val NORTH = 3 diff --git a/src/main/java/de/bixilon/minosoft/data/world/iterator/WorldIterator.kt b/src/main/java/de/bixilon/minosoft/data/world/iterator/WorldIterator.kt index d484f46d7..a413ad8cc 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/iterator/WorldIterator.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/iterator/WorldIterator.kt @@ -57,7 +57,7 @@ class WorldIterator( } else if (chunk.chunkPosition != chunkPosition) { offset.x = chunkPosition.x - chunk.chunkPosition.x offset.y = chunkPosition.y - chunk.chunkPosition.y - chunk = chunk.traceChunk(offset) ?: continue + chunk = chunk.neighbours.trace(offset) ?: continue } if (this.chunk !== chunk) { this.chunk = chunk diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/RenderParticle.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/RenderParticle.kt index 8009e629d..997fed166 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/RenderParticle.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/particle/types/render/RenderParticle.kt @@ -55,7 +55,7 @@ abstract class RenderParticle(connection: PlayConnection, position: Vec3d, veloc inChunk.y = position.y inChunk.z = position.z and 0x0F - val light = chunk.traceChunk(offset)?.light?.get(inChunk) ?: SectionLight.SKY_LIGHT_MASK + val light = chunk.neighbours.trace(offset)?.light?.get(inChunk) ?: SectionLight.SKY_LIGHT_MASK if (light and SectionLight.BLOCK_LIGHT_MASK > maxBlockLight) { maxBlockLight = light and SectionLight.BLOCK_LIGHT_MASK } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/sky/box/SkyboxRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/sky/box/SkyboxRenderer.kt index 272cc3e66..f3b028aa1 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/sky/box/SkyboxRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/sky/box/SkyboxRenderer.kt @@ -232,7 +232,7 @@ class SkyboxRenderer( val x = offset.x + xOffset val y = offset.y + yOffset val z = offset.z + zOffset - val neighbour = chunk.traceChunk(Vec2i(x shr 4, z shr 4)) ?: continue + val neighbour = chunk.neighbours.trace(Vec2i(x shr 4, z shr 4)) ?: continue val biome = neighbour.getBiome(x and 0x0F, y, z and 0x0F) ?: continue count++ diff --git a/src/main/java/de/bixilon/minosoft/physics/EntityPositionInfo.kt b/src/main/java/de/bixilon/minosoft/physics/EntityPositionInfo.kt index 023eca229..622e2df1e 100644 --- a/src/main/java/de/bixilon/minosoft/physics/EntityPositionInfo.kt +++ b/src/main/java/de/bixilon/minosoft/physics/EntityPositionInfo.kt @@ -63,7 +63,7 @@ class EntityPositionInfo( val chunks = physics.entity.connection.world.chunks val revision = chunks.revision - var chunk = if (previous.revision == revision) previous.chunk?.traceChunk(chunkPosition - previous.chunkPosition) else null + var chunk = if (previous.revision == revision) previous.chunk?.neighbours?.trace(chunkPosition - previous.chunkPosition) else null if (chunk == null) { chunk = chunks[chunkPosition] diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/ExplosionS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/ExplosionS2CP.kt index 9b7bf3be2..12f9e4601 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/ExplosionS2CP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/ExplosionS2CP.kt @@ -72,7 +72,7 @@ class ExplosionS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket { } else if (chunk.chunkPosition != chunkPosition) { chunkOffset.x = chunkPosition.x - chunk.chunkPosition.x chunkOffset.y = chunkPosition.y - chunk.chunkPosition.y - chunk = chunk.traceChunk(chunkOffset) ?: continue + chunk = chunk.neighbours.trace(chunkOffset) ?: continue } val inChunkPosition = total.inChunkPosition From f00f47e2d32f5ef46bcdc9b7368fe4b1bad3bf33 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Fri, 28 Jul 2023 00:43:35 +0200 Subject: [PATCH 07/17] outsource sky light to ChunkSkylight, rename, changes Breaks the entire light engine, did some experimental changes too... --- .../world/chunk/light/GeneralHeightmapTest.kt | 4 +- .../dimension/effects/DimensionEffects.kt | 2 +- .../dimension/effects/EndEffects.kt | 2 +- .../dimension/effects/NetherEffects.kt | 2 +- .../dimension/effects/OverworldEffects.kt | 2 +- .../de/bixilon/minosoft/data/world/World.kt | 4 +- .../world/chunk/heightmap/LightHeightmap.kt | 6 +- .../world/chunk/light/BorderSectionLight.kt | 4 +- .../data/world/chunk/light/ChunkLight.kt | 146 ++---------------- .../data/world/chunk/light/ChunkLightUtil.kt | 23 +++ .../data/world/chunk/light/ChunkSkyLight.kt | 139 +++++++++++++++++ .../data/world/chunk/light/SectionLight.kt | 59 +++---- 12 files changed, 214 insertions(+), 179 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLightUtil.kt create mode 100644 src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkSkyLight.kt diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/GeneralHeightmapTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/GeneralHeightmapTest.kt index 4e72a20ee..26c297f8f 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/GeneralHeightmapTest.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/GeneralHeightmapTest.kt @@ -33,7 +33,7 @@ class GeneralHeightmapTest { chunk[Vec3i(3, 11, 2)] = StoneTest0.state chunk[Vec3i(3, 12, 4)] = StoneTest0.state chunk[Vec3i(4, 13, 3)] = StoneTest0.state - assertEquals(chunk.light.getNeighbourMinHeight(chunk.neighbours.get()!!, 3, 3), 11) + assertEquals(chunk.light.sky.getNeighbourMinHeight(chunk.neighbours.get()!!, 3, 3), 11) } fun testMinHeightNeighbourEast() { @@ -43,7 +43,7 @@ class GeneralHeightmapTest { chunk[Vec3i(15, 12, 2)] = StoneTest0.state chunk[Vec3i(15, 13, 4)] = StoneTest0.state neighbours[ChunkNeighbours.EAST][Vec3i(0, 10, 3)] = StoneTest0.state - assertEquals(chunk.light.getNeighbourMinHeight(neighbours, 15, 3), 11) + assertEquals(chunk.light.sky.getNeighbourMinHeight(neighbours, 15, 3), 11) } // TODO: Test other directions diff --git a/src/main/java/de/bixilon/minosoft/data/registries/dimension/effects/DimensionEffects.kt b/src/main/java/de/bixilon/minosoft/data/registries/dimension/effects/DimensionEffects.kt index a989ff5d7..be523f5e7 100644 --- a/src/main/java/de/bixilon/minosoft/data/registries/dimension/effects/DimensionEffects.kt +++ b/src/main/java/de/bixilon/minosoft/data/registries/dimension/effects/DimensionEffects.kt @@ -20,7 +20,7 @@ import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection interface DimensionEffects : Identified { val daylightCycle: Boolean - val skylight: Boolean + val skyLight: Boolean val fixedTexture: ResourceLocation? get() = null val weather: Boolean diff --git a/src/main/java/de/bixilon/minosoft/data/registries/dimension/effects/EndEffects.kt b/src/main/java/de/bixilon/minosoft/data/registries/dimension/effects/EndEffects.kt index b38d769aa..172a62215 100644 --- a/src/main/java/de/bixilon/minosoft/data/registries/dimension/effects/EndEffects.kt +++ b/src/main/java/de/bixilon/minosoft/data/registries/dimension/effects/EndEffects.kt @@ -24,7 +24,7 @@ object EndEffects : DimensionEffects { override val identifier = minecraft("the_end") override val daylightCycle: Boolean get() = false - override val skylight: Boolean get() = false + override val skyLight: Boolean get() = false override val fixedTexture: ResourceLocation = minecraft("environment/end_sky").texture() override val weather: Boolean get() = false diff --git a/src/main/java/de/bixilon/minosoft/data/registries/dimension/effects/NetherEffects.kt b/src/main/java/de/bixilon/minosoft/data/registries/dimension/effects/NetherEffects.kt index 79348850a..ba64b16bf 100644 --- a/src/main/java/de/bixilon/minosoft/data/registries/dimension/effects/NetherEffects.kt +++ b/src/main/java/de/bixilon/minosoft/data/registries/dimension/effects/NetherEffects.kt @@ -21,7 +21,7 @@ object NetherEffects : DimensionEffects { override val identifier = minecraft("the_nether") override val daylightCycle: Boolean get() = false - override val skylight: Boolean get() = false + override val skyLight: Boolean get() = false override val weather: Boolean get() = false override val sun: Boolean get() = false diff --git a/src/main/java/de/bixilon/minosoft/data/registries/dimension/effects/OverworldEffects.kt b/src/main/java/de/bixilon/minosoft/data/registries/dimension/effects/OverworldEffects.kt index 586f00ccb..1ea96b7df 100644 --- a/src/main/java/de/bixilon/minosoft/data/registries/dimension/effects/OverworldEffects.kt +++ b/src/main/java/de/bixilon/minosoft/data/registries/dimension/effects/OverworldEffects.kt @@ -21,7 +21,7 @@ object OverworldEffects : DimensionEffects { override val identifier = minecraft("overworld") override val daylightCycle: Boolean get() = true - override val skylight: Boolean get() = true + override val skyLight: Boolean get() = true override val weather: Boolean get() = true override val sun: Boolean get() = true diff --git a/src/main/java/de/bixilon/minosoft/data/world/World.kt b/src/main/java/de/bixilon/minosoft/data/world/World.kt index d2930237c..ba2204fae 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/World.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/World.kt @@ -32,7 +32,7 @@ import de.bixilon.minosoft.data.world.biome.accessor.BiomeAccessor import de.bixilon.minosoft.data.world.biome.accessor.NoiseBiomeAccessor 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.ChunkLight.Companion.canSkylight +import de.bixilon.minosoft.data.world.chunk.light.ChunkLightUtil.hasSkyLight import de.bixilon.minosoft.data.world.chunk.light.SectionLight import de.bixilon.minosoft.data.world.chunk.manager.ChunkManager import de.bixilon.minosoft.data.world.difficulty.WorldDifficulty @@ -214,7 +214,7 @@ class World( fun getBrightness(position: BlockPosition): Float { val light = getLight(position) var level = light and SectionLight.BLOCK_LIGHT_MASK - if (dimension.canSkylight()) { + if (dimension.hasSkyLight()) { level = maxOf(level, light and SectionLight.SKY_LIGHT_MASK shr 4) } return dimension.ambientLight[level] diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt index ffd7f195f..b412b7c97 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt @@ -23,13 +23,13 @@ class LightHeightmap(chunk: Chunk) : Heightmap(chunk) { override fun recalculate() { super.recalculate() - chunk.light.calculateSkylight() + chunk.light.sky.calculate() } override fun onHeightmapUpdate(x: Int, z: Int, previous: Int, now: Int) { if (previous > now) { // block is lower - return chunk.light.startSkylightFloodFill(x, z) + return chunk.light.sky.startFloodFill(x, z) } // block is now higher // ToDo: Neighbours @@ -45,7 +45,7 @@ class LightHeightmap(chunk: Chunk) : Heightmap(chunk) { val section = sections[index] ?: continue section.light.calculate() } - chunk.light.calculateSkylight() + chunk.light.sky.calculate() } diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/BorderSectionLight.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/BorderSectionLight.kt index 987b4df36..fda4720c3 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/BorderSectionLight.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/BorderSectionLight.kt @@ -128,9 +128,9 @@ class BorderSectionLight( val neighbourLevel = nextLevel - 1 if (top) { - chunk.sections.getLast()?.light?.traceSkylightIncrease(x, ProtocolDefinition.SECTION_MAX_Y, z, neighbourLevel, Directions.DOWN, chunk.maxSection * ProtocolDefinition.SECTION_HEIGHT_Y + ProtocolDefinition.SECTION_MAX_Y) + chunk.sections.getLast()?.light?.traceSkyLightIncrease(x, ProtocolDefinition.SECTION_MAX_Y, z, neighbourLevel, Directions.DOWN, chunk.maxSection * ProtocolDefinition.SECTION_HEIGHT_Y + ProtocolDefinition.SECTION_MAX_Y) } else { - chunk.sections.getFirst()?.light?.traceSkylightIncrease(x, 0, z, neighbourLevel, Directions.UP, chunk.minSection * ProtocolDefinition.SECTION_HEIGHT_Y) + chunk.sections.getFirst()?.light?.traceSkyLightIncrease(x, 0, z, neighbourLevel, Directions.UP, chunk.minSection * ProtocolDefinition.SECTION_HEIGHT_Y) } if (z > 0) { diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLight.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLight.kt index 9a6649e3a..3200bd87a 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLight.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLight.kt @@ -17,25 +17,25 @@ import de.bixilon.kotlinglm.vec2.Vec2i import de.bixilon.kotlinglm.vec3.Vec3i import de.bixilon.minosoft.data.direction.Directions import de.bixilon.minosoft.data.registries.blocks.state.BlockState -import de.bixilon.minosoft.data.registries.dimension.DimensionProperties import de.bixilon.minosoft.data.world.chunk.ChunkSection import de.bixilon.minosoft.data.world.chunk.chunk.Chunk import de.bixilon.minosoft.data.world.chunk.heightmap.FixedHeightmap import de.bixilon.minosoft.data.world.chunk.heightmap.LightHeightmap -import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours +import de.bixilon.minosoft.data.world.chunk.light.ChunkLightUtil.hasSkyLight import de.bixilon.minosoft.data.world.chunk.update.AbstractWorldUpdate import de.bixilon.minosoft.data.world.chunk.update.chunk.ChunkLightUpdate import de.bixilon.minosoft.gui.rendering.util.VecUtil.inSectionHeight import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight -import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition -class ChunkLight(private val chunk: Chunk) { +class ChunkLight(val chunk: Chunk) { private val connection = chunk.connection - val heightmap = if (chunk.world.dimension.canSkylight()) LightHeightmap(chunk) else FixedHeightmap.MAX_VALUE + val heightmap = if (chunk.world.dimension.hasSkyLight()) LightHeightmap(chunk) else FixedHeightmap.MAX_VALUE val bottom = BorderSectionLight(false, chunk) val top = BorderSectionLight(true, chunk) + val sky = ChunkSkyLight(this) + fun onBlockChange(x: Int, y: Int, z: Int, section: ChunkSection, next: BlockState?) { if (!chunk.world.dimension.light) { @@ -134,7 +134,7 @@ class ChunkLight(private val chunk: Chunk) { } section.light.recalculate() } - calculateSkylight() + sky.calculate() if (fireEvent) { fireLightChange(sections, fireSameChunkEvent) } @@ -148,7 +148,7 @@ class ChunkLight(private val chunk: Chunk) { } section.light.calculate() } - calculateSkylight() + sky.calculate() if (fireEvent) { fireLightChange(sections, fireSameChunkEvent) } @@ -179,136 +179,8 @@ class ChunkLight(private val chunk: Chunk) { } } - - fun calculateSkylight() { - if (!chunk.world.dimension.canSkylight() || !chunk.neighbours.complete) { - // no need to calculate it - return - } - for (x in 0 until ProtocolDefinition.SECTION_WIDTH_X) { - for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) { - startSkylightFloodFill(x, z) - } - } - } - - fun getNeighbourMaxHeight(neighbours: Array, x: Int, z: Int, heightmapIndex: Int = (z shl 4) or x): Int { - return maxOf( - if (x > 0) { - heightmap[heightmapIndex - 1] - } else { - neighbours[ChunkNeighbours.WEST].light.heightmap[(z shl 4) or ProtocolDefinition.SECTION_MAX_X] - }, - - if (x < ProtocolDefinition.SECTION_MAX_X) { - heightmap[heightmapIndex + 1] - } else { - neighbours[ChunkNeighbours.EAST].light.heightmap[(z shl 4) or 0] - }, - - if (z > 0) { - heightmap[((z - 1) shl 4) or x] - } else { - neighbours[ChunkNeighbours.NORTH].light.heightmap[(ProtocolDefinition.SECTION_MAX_Z shl 4) or x] - }, - - if (z < ProtocolDefinition.SECTION_MAX_Z) { - heightmap[((z + 1) shl 4) or x] - } else { - neighbours[ChunkNeighbours.SOUTH].light.heightmap[(0 shl 4) or x] - } - ) - } - - fun getNeighbourMinHeight(neighbours: Array, x: Int, z: Int, heightmapIndex: Int = (z shl 4) or x): Int { - return minOf( - if (x > 0) { - heightmap[heightmapIndex - 1] - } else { - neighbours[ChunkNeighbours.WEST].light.heightmap[(z shl 4) or ProtocolDefinition.SECTION_MAX_X] - }, - - if (x < ProtocolDefinition.SECTION_MAX_X) { - heightmap[heightmapIndex + 1] - } else { - neighbours[ChunkNeighbours.EAST].light.heightmap[(z shl 4) or 0] - }, - - if (z > 0) { - heightmap[((z - 1) shl 4) or x] - } else { - neighbours[ChunkNeighbours.NORTH].light.heightmap[(ProtocolDefinition.SECTION_MAX_Z shl 4) or x] - }, - - if (z < ProtocolDefinition.SECTION_MAX_Z) { - heightmap[((z + 1) shl 4) or x] - } else { - neighbours[ChunkNeighbours.SOUTH].light.heightmap[(0 shl 4) or x] - } - ) - } - - fun startSkylightFloodFill(x: Int, z: Int) { - val neighbours = chunk.neighbours.get() ?: return - val heightmapIndex = (z shl 4) or x - val maxHeight = heightmap[heightmapIndex] - val maxHeightSection = maxHeight.sectionHeight - val skylightStart = getNeighbourMaxHeight(neighbours, x, z, heightmapIndex) - - if (maxHeight == Int.MIN_VALUE && skylightStart == Int.MIN_VALUE) return - - val skylightStartSectionHeight = skylightStart.sectionHeight - if (skylightStart.inSectionHeight == 1) { - // Create section below max section - chunk.getOrPut(skylightStartSectionHeight - 1) - } - - for (sectionHeight in minOf(skylightStartSectionHeight, chunk.maxSection) downTo maxOf(maxHeightSection + 1, chunk.minSection)) { - val section = chunk.sections.get(sectionHeight - chunk.minSection) ?: continue - - // ToDo: Only update if affected by heightmap change - section.light.update = true - // ToDo: bare tracing - val baseY = sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y - for (y in ProtocolDefinition.SECTION_MAX_Y downTo 0) { - section.light.traceSkylightIncrease(x, y, z, ProtocolDefinition.MAX_LIGHT_LEVEL_I, null, baseY + y, true) - } - } - if (maxHeight.sectionHeight < chunk.minSection) { - // bottom section - bottom.traceSkyIncrease(x, z, ProtocolDefinition.MAX_LIGHT_LEVEL_I) - } else { - val maxSection = chunk.getOrPut(maxHeightSection) - val baseY = maxHeightSection * ProtocolDefinition.SECTION_HEIGHT_Y - if (maxSection != null) { - for (y in (if (skylightStartSectionHeight != maxHeightSection) ProtocolDefinition.SECTION_MAX_Y else skylightStart.inSectionHeight) downTo maxHeight.inSectionHeight) { - maxSection.light.traceSkylightIncrease(x, y, z, ProtocolDefinition.MAX_LIGHT_LEVEL_I, null, baseY + y, true) - } - maxSection.light.update = true - } - } - } - - @Deprecated("heightmap") + @Deprecated("heightmap", ReplaceWith("heightmap[x, z]")) inline fun getMaxHeight(x: Int, z: Int): Int { - return heightmap[(z shl 4) or x] - } - - fun recalculateSkylight(sectionHeight: Int) { - val minY = sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y - - // TODO: clear neighbours and let them propagate? - // TODO: Optimize for specific section height (i.e. not trace everything above) - calculateSkylight() - } - - companion object { - - fun DimensionProperties?.canSkylight(): Boolean { - if (this == null) { - return false - } - return !(!this.skyLight || !this.effects.skylight) - } + return heightmap[x, z] } } diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLightUtil.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLightUtil.kt new file mode 100644 index 000000000..2a536f358 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLightUtil.kt @@ -0,0 +1,23 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.world.chunk.light + +import de.bixilon.minosoft.data.registries.dimension.DimensionProperties + +object ChunkLightUtil { + + fun DimensionProperties.hasSkyLight(): Boolean { + return this.skyLight || this.effects.skyLight + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkSkyLight.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkSkyLight.kt new file mode 100644 index 000000000..e8dca6eb8 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkSkyLight.kt @@ -0,0 +1,139 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.world.chunk.light + +import de.bixilon.minosoft.data.direction.Directions +import de.bixilon.minosoft.data.world.chunk.chunk.Chunk +import de.bixilon.minosoft.data.world.chunk.light.ChunkLightUtil.hasSkyLight +import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours +import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight +import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition + +class ChunkSkyLight(val light: ChunkLight) { + private val chunk = light.chunk + + fun calculate() { + if (!chunk.world.dimension.hasSkyLight() || !chunk.neighbours.complete) { + // no need to calculate it + return + } + val neighbours = this.chunk.neighbours.get() ?: return + for (x in 0 until ProtocolDefinition.SECTION_WIDTH_X) { + for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) { + floodFill(neighbours, x, z) + } + } + } + + @Deprecated("unused") + private fun getNeighbourMaxHeight(neighbours: Array, x: Int, z: Int, heightmapIndex: Int = (z shl 4) or x): IntArray { + return intArrayOf( + if (x > 0) { + light.heightmap[heightmapIndex - 1] + } else { + neighbours[ChunkNeighbours.WEST].light.heightmap[(z shl 4) or ProtocolDefinition.SECTION_MAX_X] + }, + + if (x < ProtocolDefinition.SECTION_MAX_X) { + light.heightmap[heightmapIndex + 1] + } else { + neighbours[ChunkNeighbours.EAST].light.heightmap[(z shl 4) or 0] + }, + + if (z > 0) { + light.heightmap[((z - 1) shl 4) or x] + } else { + neighbours[ChunkNeighbours.NORTH].light.heightmap[(ProtocolDefinition.SECTION_MAX_Z shl 4) or x] + }, + + if (z < ProtocolDefinition.SECTION_MAX_Z) { + light.heightmap[((z + 1) shl 4) or x] + } else { + neighbours[ChunkNeighbours.SOUTH].light.heightmap[(0 shl 4) or x] + } + ) + } + + fun getNeighbourMinHeight(neighbours: Array, x: Int, z: Int, heightmapIndex: Int = (z shl 4) or x): Int { + return minOf( + if (x > 0) { + light.heightmap[heightmapIndex - 1] + } else { + neighbours[ChunkNeighbours.WEST].light.heightmap[(z shl 4) or ProtocolDefinition.SECTION_MAX_X] + }, + + if (x < ProtocolDefinition.SECTION_MAX_X) { + light.heightmap[heightmapIndex + 1] + } else { + neighbours[ChunkNeighbours.EAST].light.heightmap[(z shl 4) or 0] + }, + + if (z > 0) { + light.heightmap[((z - 1) shl 4) or x] + } else { + neighbours[ChunkNeighbours.NORTH].light.heightmap[(ProtocolDefinition.SECTION_MAX_Z shl 4) or x] + }, + + if (z < ProtocolDefinition.SECTION_MAX_Z) { + light.heightmap[((z + 1) shl 4) or x] + } else { + neighbours[ChunkNeighbours.SOUTH].light.heightmap[(0 shl 4) or x] + } + ) + } + + private fun doWest(x: Int, topY: Int, bottomY: Int, z: Int) { + if (topY == Int.MIN_VALUE) return // no blocks are set in that column, no need to trace. all levels are MAX + if (bottomY > topY) return // started position is higher than at this, no need to trace + + + // trace section after section + val sectionStart = topY.sectionHeight + val sectionEnd = bottomY.sectionHeight + + for (sectionHeight in sectionStart downTo (sectionEnd + 1)) { + val section = chunk[sectionHeight] ?: continue + section.light.traceSkyLightIncrease(x, 0, z, ProtocolDefinition.MAX_LIGHT_LEVEL_I, Directions.EAST, 0, false) + } + + // trace lowest section just from heightmap start + } + + private fun floodFill(neighbours: Array, x: Int, z: Int) { + val heightmapIndex = (z shl 4) or x + val maxHeight = light.heightmap[heightmapIndex] + + + if (x > 0) { + doWest(x - 1, light.heightmap[heightmapIndex - 1], maxHeight, z) + } else { + val neighbour = neighbours[ChunkNeighbours.WEST].light + neighbour.sky.doWest(ProtocolDefinition.SECTION_MAX_X, neighbour.heightmap[(z shl 4) or ProtocolDefinition.SECTION_MAX_X], maxHeight, z) + } + + + } + + fun startFloodFill(x: Int, z: Int) { + + } + + fun recalculate(sectionHeight: Int) { + val minY = sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y + + // TODO: clear neighbours and let them propagate? + // TODO: Optimize for specific section height (i.e. not trace everything above) + calculate() + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt index 02935b3d3..3f61722ec 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt @@ -242,7 +242,7 @@ class SectionLight( } } blocks.release() - section.chunk.light.recalculateSkylight(section.sectionHeight) + section.chunk.light.sky.recalculate(section.sectionHeight) } @@ -275,11 +275,11 @@ class SectionLight( val totalY = baseY + y neighbours[Directions.O_WEST]?.light?.get(ProtocolDefinition.SECTION_MAX_Z, y, z)?.toInt()?.let { light -> (light and BLOCK_LIGHT_MASK).let { if (it > 1) traceBlockIncrease(0, y, z, it - 1, Directions.EAST) } - (light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkylightIncrease(0, y, z, it - 1, Directions.EAST, totalY) } + (light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkyLightIncrease(0, y, z, it - 1, Directions.EAST, totalY) } } neighbours[Directions.O_EAST]?.light?.get(0, y, z)?.toInt()?.let { light -> (light and BLOCK_LIGHT_MASK).let { if (it > 1) traceBlockIncrease(ProtocolDefinition.SECTION_MAX_X, y, z, it - 1, Directions.WEST) } - (light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkylightIncrease(ProtocolDefinition.SECTION_MAX_X, y, z, it - 1, Directions.WEST, totalY) } + (light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkyLightIncrease(ProtocolDefinition.SECTION_MAX_X, y, z, it - 1, Directions.WEST, totalY) } } } } @@ -290,11 +290,11 @@ class SectionLight( val totalY = baseY + y neighbours[Directions.O_NORTH]?.light?.get(x, y, ProtocolDefinition.SECTION_MAX_Z)?.toInt()?.let { light -> (light and BLOCK_LIGHT_MASK).let { if (it > 1) traceBlockIncrease(x, y, 0, it - 1, Directions.SOUTH) } - (light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkylightIncrease(x, y, 0, it - 1, Directions.SOUTH, totalY) } + (light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkyLightIncrease(x, y, 0, it - 1, Directions.SOUTH, totalY) } } neighbours[Directions.O_SOUTH]?.light?.get(x, y, 0)?.toInt()?.let { light -> (light and BLOCK_LIGHT_MASK).let { if (it > 1) traceBlockIncrease(x, y, ProtocolDefinition.SECTION_MAX_Z, it - 1, Directions.NORTH) } - (light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkylightIncrease(x, y, ProtocolDefinition.SECTION_MAX_Z, it - 1, Directions.NORTH, totalY) } + (light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkyLightIncrease(x, y, ProtocolDefinition.SECTION_MAX_Z, it - 1, Directions.NORTH, totalY) } } } } @@ -304,20 +304,20 @@ class SectionLight( for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) { neighbours[Directions.O_DOWN]?.light?.get(x, ProtocolDefinition.SECTION_MAX_Y, z)?.toInt()?.let { light -> (light and BLOCK_LIGHT_MASK).let { if (it > 1) traceBlockIncrease(x, 0, z, it - 1, Directions.UP) } - (light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkylightIncrease(x, 0, z, it - 1, Directions.UP, baseY + 0) } // ToDo: Is that possible? + (light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkyLightIncrease(x, 0, z, it - 1, Directions.UP, baseY + 0) } // ToDo: Is that possible? } neighbours[Directions.O_UP]?.light?.get(x, 0, z)?.toInt()?.let { light -> (light and BLOCK_LIGHT_MASK).let { if (it > 1) traceBlockIncrease(x, ProtocolDefinition.SECTION_MAX_Y, z, it - 1, Directions.DOWN) } - (light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkylightIncrease(x, ProtocolDefinition.SECTION_MAX_Y, z, it - 1, Directions.DOWN, baseY + ProtocolDefinition.SECTION_MAX_Y) } + (light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkyLightIncrease(x, ProtocolDefinition.SECTION_MAX_Y, z, it - 1, Directions.DOWN, baseY + ProtocolDefinition.SECTION_MAX_Y) } } } } - internal inline fun traceSkylightIncrease(x: Int, y: Int, z: Int, nextLevel: Int, direction: Directions?, totalY: Int) { - return traceSkylightIncrease(x, y, z, nextLevel, direction, totalY, false) + internal inline fun traceSkyLightIncrease(x: Int, y: Int, z: Int, nextLevel: Int, direction: Directions?, totalY: Int) { + return traceSkyLightIncrease(x, y, z, nextLevel, direction, totalY, false) } - fun traceSkylightIncrease(x: Int, y: Int, z: Int, nextLevel: Int, target: Directions?, totalY: Int, force: Boolean) { + fun traceSkyLightIncrease(x: Int, y: Int, z: Int, nextLevel: Int, target: Directions?, totalY: Int, force: Boolean) { val chunk = section.chunk val heightmapIndex = (z shl 4) or x if (!force && totalY >= chunk.light.heightmap[heightmapIndex]) { @@ -362,46 +362,46 @@ class SectionLight( if (target != Directions.UP && (target == null || lightProperties.propagatesLight(Directions.DOWN))) { if (y > 0) { - traceSkylightIncrease(x, y - 1, z, nextNeighbourLevel, Directions.DOWN, totalY - 1) + traceSkyLightIncrease(x, y - 1, z, nextNeighbourLevel, Directions.DOWN, totalY - 1) } else if (section.sectionHeight == chunk.minSection) { chunk.light.bottom.traceSkyIncrease(x, z, nextLevel) } else { - (neighbours[Directions.O_DOWN] ?: chunk.getOrPut(section.sectionHeight - 1, false))?.light?.traceSkylightIncrease(x, ProtocolDefinition.SECTION_MAX_Y, z, nextNeighbourLevel, Directions.DOWN, totalY - 1) + (neighbours[Directions.O_DOWN] ?: chunk.getOrPut(section.sectionHeight - 1, false))?.light?.traceSkyLightIncrease(x, ProtocolDefinition.SECTION_MAX_Y, z, nextNeighbourLevel, Directions.DOWN, totalY - 1) } } if (target != Directions.DOWN && target != null && (lightProperties.propagatesLight(Directions.UP))) { if (y < ProtocolDefinition.SECTION_MAX_Y) { - traceSkylightIncrease(x, y + 1, z, nextNeighbourLevel, Directions.UP, totalY + 1) + traceSkyLightIncrease(x, y + 1, z, nextNeighbourLevel, Directions.UP, totalY + 1) } else if (section.sectionHeight < chunk.maxSection) { - (neighbours[Directions.O_UP] ?: chunk.getOrPut(section.sectionHeight + 1, false))?.light?.traceSkylightIncrease(x, 0, z, nextNeighbourLevel, Directions.UP, totalY + 1) + (neighbours[Directions.O_UP] ?: chunk.getOrPut(section.sectionHeight + 1, false))?.light?.traceSkyLightIncrease(x, 0, z, nextNeighbourLevel, Directions.UP, totalY + 1) } } if (target != Directions.SOUTH && (target == null || lightProperties.propagatesLight(Directions.NORTH))) { if (z > 0) { - traceSkylightIncrease(x, y, z - 1, nextNeighbourLevel, Directions.NORTH, totalY) + traceSkyLightIncrease(x, y, z - 1, nextNeighbourLevel, Directions.NORTH, totalY) } else { - neighbours[Directions.O_NORTH, ChunkNeighbours.NORTH, chunkNeighbours]?.light?.traceSkylightIncrease(x, y, ProtocolDefinition.SECTION_MAX_Z, nextNeighbourLevel, Directions.NORTH, totalY) + neighbours[Directions.O_NORTH, ChunkNeighbours.NORTH, chunkNeighbours]?.light?.traceSkyLightIncrease(x, y, ProtocolDefinition.SECTION_MAX_Z, nextNeighbourLevel, Directions.NORTH, totalY) } } if (target != Directions.NORTH && (target == null || lightProperties.propagatesLight(Directions.SOUTH))) { if (z < ProtocolDefinition.SECTION_MAX_Z) { - traceSkylightIncrease(x, y, z + 1, nextNeighbourLevel, Directions.SOUTH, totalY) + traceSkyLightIncrease(x, y, z + 1, nextNeighbourLevel, Directions.SOUTH, totalY) } else { - neighbours[Directions.O_SOUTH, ChunkNeighbours.SOUTH, chunkNeighbours]?.light?.traceSkylightIncrease(x, y, 0, nextNeighbourLevel, Directions.SOUTH, totalY) + neighbours[Directions.O_SOUTH, ChunkNeighbours.SOUTH, chunkNeighbours]?.light?.traceSkyLightIncrease(x, y, 0, nextNeighbourLevel, Directions.SOUTH, totalY) } } if (target != Directions.EAST && (target == null || lightProperties.propagatesLight(Directions.WEST))) { if (x > 0) { - traceSkylightIncrease(x - 1, y, z, nextNeighbourLevel, Directions.WEST, totalY) + traceSkyLightIncrease(x - 1, y, z, nextNeighbourLevel, Directions.WEST, totalY) } else { - neighbours[Directions.O_WEST, ChunkNeighbours.WEST, chunkNeighbours]?.light?.traceSkylightIncrease(ProtocolDefinition.SECTION_MAX_X, y, z, nextNeighbourLevel, Directions.WEST, totalY) + neighbours[Directions.O_WEST, ChunkNeighbours.WEST, chunkNeighbours]?.light?.traceSkyLightIncrease(ProtocolDefinition.SECTION_MAX_X, y, z, nextNeighbourLevel, Directions.WEST, totalY) } } if (target != Directions.WEST && (target == null || lightProperties.propagatesLight(Directions.EAST))) { if (x < ProtocolDefinition.SECTION_MAX_X) { - traceSkylightIncrease(x + 1, y, z, nextNeighbourLevel, Directions.EAST, totalY) + traceSkyLightIncrease(x + 1, y, z, nextNeighbourLevel, Directions.EAST, totalY) } else { - neighbours[Directions.O_EAST, ChunkNeighbours.EAST, chunkNeighbours]?.light?.traceSkylightIncrease(0, y, z, nextNeighbourLevel, Directions.EAST, totalY) + neighbours[Directions.O_EAST, ChunkNeighbours.EAST, chunkNeighbours]?.light?.traceSkyLightIncrease(0, y, z, nextNeighbourLevel, Directions.EAST, totalY) } } } @@ -413,14 +413,15 @@ class SectionLight( fun propagateFromNeighbours(x: Int, y: Int, z: Int) { val neighbours = section.neighbours ?: return - var skylight = 0 + // TODO: those 2 values are boxed in wrapper classes (slow!) + var skyLight = 0 var blockLight = 0 fun pushLight(light: Byte) { - val nextSkylight = light.toInt() and SKY_LIGHT_MASK shr 4 - if (nextSkylight > skylight) { - skylight = nextSkylight + val nextSkyLight = light.toInt() and SKY_LIGHT_MASK shr 4 + if (nextSkyLight > skyLight) { + skyLight = nextSkyLight } val nextBlockLight = light.toInt() and BLOCK_LIGHT_MASK if (nextBlockLight > blockLight) { @@ -470,12 +471,12 @@ class SectionLight( section.chunk.let { // check if neighbours are above heightmap, if so set light level to max val chunkNeighbours = it.neighbours.get() ?: return@let - val minHeight = it.light.getNeighbourMinHeight(chunkNeighbours, x, z) + val minHeight = it.light.sky.getNeighbourMinHeight(chunkNeighbours, x, z) if (totalY > minHeight) { - skylight = ProtocolDefinition.MAX_LIGHT_LEVEL_I + skyLight = ProtocolDefinition.MAX_LIGHT_LEVEL_I } } - traceSkylightIncrease(x, y, z, skylight - 1, null, totalY) + traceSkyLightIncrease(x, y, z, skyLight - 1, null, totalY) } companion object { From 74302bd390636737a4341f081d71251c623fa774 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Fri, 28 Jul 2023 01:19:58 +0200 Subject: [PATCH 08/17] refactor skylight flood filling Some things are broken, ik. Should be a bit faster than before --- .../world/chunk/heightmap/LightHeightmap.kt | 2 +- .../data/world/chunk/light/ChunkSkyLight.kt | 170 +++++++++++------- 2 files changed, 102 insertions(+), 70 deletions(-) diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt index b412b7c97..1bc575cca 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt @@ -29,7 +29,7 @@ class LightHeightmap(chunk: Chunk) : Heightmap(chunk) { override fun onHeightmapUpdate(x: Int, z: Int, previous: Int, now: Int) { if (previous > now) { // block is lower - return chunk.light.sky.startFloodFill(x, z) + return chunk.light.sky.floodFill(x, z) } // block is now higher // ToDo: Neighbours diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkSkyLight.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkSkyLight.kt index e8dca6eb8..1fa5ca001 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkSkyLight.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkSkyLight.kt @@ -17,17 +17,113 @@ import de.bixilon.minosoft.data.direction.Directions import de.bixilon.minosoft.data.world.chunk.chunk.Chunk import de.bixilon.minosoft.data.world.chunk.light.ChunkLightUtil.hasSkyLight import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours +import de.bixilon.minosoft.gui.rendering.util.VecUtil.inSectionHeight import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition class ChunkSkyLight(val light: ChunkLight) { private val chunk = light.chunk + fun calculate() { if (!chunk.world.dimension.hasSkyLight() || !chunk.neighbours.complete) { // no need to calculate it return } + floodFill() + } + + private fun trace(x: Int, topY: Int, bottomY: Int, z: Int, source: Directions) { + if (topY == Int.MIN_VALUE && bottomY == Int.MIN_VALUE) return // no blocks are set in that column, no need to trace. all levels are MAX + if (bottomY > topY) return // started position is higher than at this, no need to trace + + + // trace section after section + val topSection = topY.sectionHeight + val bottomSection = bottomY.sectionHeight + + + + + for (sectionHeight in topSection downTo maxOf(chunk.minSection, bottomSection + 1)) { + val section = chunk[sectionHeight] ?: continue + val baseY = sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y + + for (y in ProtocolDefinition.SECTION_MAX_Y downTo 0) { + section.light.traceSkyLightIncrease(x, y, z, ProtocolDefinition.MAX_LIGHT_LEVEL_I, source, baseY + y, false) + } + section.light.update = true + } + + // trace lowest section just from heightmap start + + if (bottomSection < chunk.minSection) { + chunk.light.bottom.traceSkyIncrease(x, z, ProtocolDefinition.MAX_LIGHT_LEVEL_I) + } else { + val section = chunk[bottomSection] + if (section != null) { + val baseY = bottomSection * ProtocolDefinition.SECTION_HEIGHT_Y + val start = if (topSection == bottomSection) topY.inSectionHeight else ProtocolDefinition.SECTION_MAX_Y + for (y in start downTo bottomY.inSectionHeight) { + section.light.traceSkyLightIncrease(x, y, z, ProtocolDefinition.MAX_LIGHT_LEVEL_I, source, baseY + y, false) + } + section.light.update = true + } + } + } + + private fun traceDown(x: Int, y: Int, z: Int) { + val sectionHeight = y.sectionHeight + if (sectionHeight == chunk.minSection - 1) { + chunk.light.bottom.traceSkyIncrease(x, z, ProtocolDefinition.MAX_LIGHT_LEVEL_I) + return + } + val section = chunk[y.sectionHeight] ?: return + section.light.traceSkyLightIncrease(x, y.inSectionHeight, z, ProtocolDefinition.MAX_LIGHT_LEVEL_I, null, y, true) + } + + private fun floodFill(neighbours: Array, x: Int, z: Int) { + val heightmapIndex = (z shl 4) or x + val maxHeight = light.heightmap[heightmapIndex] + + + traceDown(x, maxHeight, z) + + if (x > 0) { + trace(x - 1, light.heightmap[heightmapIndex - 1], maxHeight, z, Directions.EAST) + } else { + val neighbour = neighbours[ChunkNeighbours.WEST].light + neighbour.sky.trace(ProtocolDefinition.SECTION_MAX_X, neighbour.heightmap[(z shl 4) or ProtocolDefinition.SECTION_MAX_X], maxHeight, z, Directions.EAST) + } + + if (x < ProtocolDefinition.SECTION_MAX_X) { + trace(x + 1, light.heightmap[heightmapIndex + 1], maxHeight, z, Directions.WEST) + } else { + val neighbour = neighbours[ChunkNeighbours.EAST].light + neighbour.sky.trace(0, neighbour.heightmap[(z shl 4) or 0], maxHeight, z, Directions.WEST) + } + + if (z > 0) { + trace(x, light.heightmap[((z - 1) shl 4) or x], maxHeight, z - 1, Directions.SOUTH) + } else { + val neighbour = neighbours[ChunkNeighbours.NORTH].light + neighbour.sky.trace(x, neighbour.heightmap[(ProtocolDefinition.SECTION_MAX_Z shl 4) or x], maxHeight, ProtocolDefinition.SECTION_MAX_Z, Directions.SOUTH) + } + + if (z < ProtocolDefinition.SECTION_MAX_Z) { + trace(x, light.heightmap[((z + 1) shl 4) or x], maxHeight, z + 1, Directions.NORTH) + } else { + val neighbour = neighbours[ChunkNeighbours.SOUTH].light + neighbour.sky.trace(x, neighbour.heightmap[(0 shl 4) or x], maxHeight, 0, Directions.NORTH) + } + } + + fun floodFill(x: Int, z: Int) { + val neighbours = chunk.neighbours.get() ?: return + floodFill(neighbours, x, z) + } + + private fun floodFill() { val neighbours = this.chunk.neighbours.get() ?: return for (x in 0 until ProtocolDefinition.SECTION_WIDTH_X) { for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) { @@ -36,33 +132,12 @@ class ChunkSkyLight(val light: ChunkLight) { } } - @Deprecated("unused") - private fun getNeighbourMaxHeight(neighbours: Array, x: Int, z: Int, heightmapIndex: Int = (z shl 4) or x): IntArray { - return intArrayOf( - if (x > 0) { - light.heightmap[heightmapIndex - 1] - } else { - neighbours[ChunkNeighbours.WEST].light.heightmap[(z shl 4) or ProtocolDefinition.SECTION_MAX_X] - }, + fun recalculate(sectionHeight: Int) { + val minY = sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y - if (x < ProtocolDefinition.SECTION_MAX_X) { - light.heightmap[heightmapIndex + 1] - } else { - neighbours[ChunkNeighbours.EAST].light.heightmap[(z shl 4) or 0] - }, - - if (z > 0) { - light.heightmap[((z - 1) shl 4) or x] - } else { - neighbours[ChunkNeighbours.NORTH].light.heightmap[(ProtocolDefinition.SECTION_MAX_Z shl 4) or x] - }, - - if (z < ProtocolDefinition.SECTION_MAX_Z) { - light.heightmap[((z + 1) shl 4) or x] - } else { - neighbours[ChunkNeighbours.SOUTH].light.heightmap[(0 shl 4) or x] - } - ) + // TODO: clear neighbours and let them propagate? + // TODO: Optimize for specific section height (i.e. not trace everything above) + calculate() } fun getNeighbourMinHeight(neighbours: Array, x: Int, z: Int, heightmapIndex: Int = (z shl 4) or x): Int { @@ -93,47 +168,4 @@ class ChunkSkyLight(val light: ChunkLight) { ) } - private fun doWest(x: Int, topY: Int, bottomY: Int, z: Int) { - if (topY == Int.MIN_VALUE) return // no blocks are set in that column, no need to trace. all levels are MAX - if (bottomY > topY) return // started position is higher than at this, no need to trace - - - // trace section after section - val sectionStart = topY.sectionHeight - val sectionEnd = bottomY.sectionHeight - - for (sectionHeight in sectionStart downTo (sectionEnd + 1)) { - val section = chunk[sectionHeight] ?: continue - section.light.traceSkyLightIncrease(x, 0, z, ProtocolDefinition.MAX_LIGHT_LEVEL_I, Directions.EAST, 0, false) - } - - // trace lowest section just from heightmap start - } - - private fun floodFill(neighbours: Array, x: Int, z: Int) { - val heightmapIndex = (z shl 4) or x - val maxHeight = light.heightmap[heightmapIndex] - - - if (x > 0) { - doWest(x - 1, light.heightmap[heightmapIndex - 1], maxHeight, z) - } else { - val neighbour = neighbours[ChunkNeighbours.WEST].light - neighbour.sky.doWest(ProtocolDefinition.SECTION_MAX_X, neighbour.heightmap[(z shl 4) or ProtocolDefinition.SECTION_MAX_X], maxHeight, z) - } - - - } - - fun startFloodFill(x: Int, z: Int) { - - } - - fun recalculate(sectionHeight: Int) { - val minY = sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y - - // TODO: clear neighbours and let them propagate? - // TODO: Optimize for specific section height (i.e. not trace everything above) - calculate() - } } From 17d3e66f87097882edc43ca8662a83b65e8375d6 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Fri, 28 Jul 2023 11:14:11 +0200 Subject: [PATCH 09/17] rename Heightmap to ChunkHeightmap --- .../chunk/heightmap/AbstractHeightmap.kt | 26 ---- .../world/chunk/heightmap/ChunkHeightmap.kt | 126 ++++++++++++++++++ .../world/chunk/heightmap/FixedHeightmap.kt | 2 +- .../data/world/chunk/heightmap/Heightmap.kt | 110 +-------------- .../world/chunk/heightmap/LightHeightmap.kt | 2 +- 5 files changed, 133 insertions(+), 133 deletions(-) delete mode 100644 src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/AbstractHeightmap.kt create mode 100644 src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/ChunkHeightmap.kt diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/AbstractHeightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/AbstractHeightmap.kt deleted file mode 100644 index 5dd6004b6..000000000 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/AbstractHeightmap.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 . - * - * This software is not affiliated with Mojang AB, the original developer of Minecraft. - */ - -package de.bixilon.minosoft.data.world.chunk.heightmap - -import de.bixilon.minosoft.data.registries.blocks.state.BlockState - -interface AbstractHeightmap { - - fun recalculate() - - operator fun get(x: Int, z: Int): Int - operator fun get(index: Int): Int - - fun onBlockChange(x: Int, y: Int, z: Int, state: BlockState?) -} diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/ChunkHeightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/ChunkHeightmap.kt new file mode 100644 index 000000000..450c691ac --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/ChunkHeightmap.kt @@ -0,0 +1,126 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.data.world.chunk.heightmap + +import de.bixilon.minosoft.data.registries.blocks.state.BlockState +import de.bixilon.minosoft.data.world.chunk.chunk.Chunk +import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight +import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition + +abstract class ChunkHeightmap(protected val chunk: Chunk) : Heightmap { + protected val heightmap = IntArray(ProtocolDefinition.SECTION_WIDTH_X * ProtocolDefinition.SECTION_WIDTH_Z) { Int.MIN_VALUE } + + override fun get(index: Int) = heightmap[index] + override fun get(x: Int, z: Int) = heightmap[(z shl 4) or x] + + + protected abstract fun passes(state: BlockState): HeightmapPass + protected abstract fun onHeightmapUpdate(x: Int, z: Int, previous: Int, now: Int) + + + override fun recalculate() { + chunk.lock.lock() + val maxY = chunk.maxSection * ProtocolDefinition.SECTION_HEIGHT_Y + + for (x in 0 until ProtocolDefinition.SECTION_WIDTH_X) { + for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) { + trace(x, maxY, z, false) + } + } + chunk.lock.unlock() + } + + private fun trace(x: Int, startY: Int, z: Int, notify: Boolean) { + val sections = chunk.sections + + var y = Int.MIN_VALUE + + sectionLoop@ for (sectionIndex in (startY.sectionHeight - chunk.minSection) downTo 0) { + if (sectionIndex >= sections.size) { + // starting from above world + continue + } + val section = sections[sectionIndex] ?: continue + if (section.blocks.isEmpty) continue + + val min = section.blocks.minPosition + val max = section.blocks.maxPosition + + if (x < min.x || x > max.x || z < min.z || z > max.z) continue // out of section + + section.acquire() + for (sectionY in max.y downTo min.y) { + val state = section.blocks[x, sectionY, z] ?: continue + val pass = passes(state) + if (pass == HeightmapPass.PASSES) continue + + y = (sectionIndex + chunk.minSection) * ProtocolDefinition.SECTION_HEIGHT_Y + sectionY + if (pass == HeightmapPass.ABOVE) y++ + + section.release() + break@sectionLoop + } + section.release() + } + val index = (z shl 4) or x + val previous = heightmap[index] + + if (previous == y) return + + heightmap[index] = y + + if (notify) { + onHeightmapUpdate(x, z, previous, y) + } + } + + + override fun onBlockChange(x: Int, y: Int, z: Int, state: BlockState?) { + chunk.lock.lock() + val index = (z shl 4) or x + + val previous = heightmap[index] + + if (previous > y + 1) { + // our block is/was not the highest, ignore everything + chunk.lock.unlock() + return + } + if (state == null) { + trace(x, y, z, true) + chunk.lock.unlock() + return + } + + val next = when (passes(state)) { + HeightmapPass.ABOVE -> y + 1 + HeightmapPass.IN -> y + HeightmapPass.PASSES -> previous + } + + chunk.lock.unlock() + + if (previous != next) { + heightmap[index] = next + onHeightmapUpdate(x, z, previous, next) + } + } + + protected enum class HeightmapPass { + ABOVE, + IN, + PASSES, + ; + } +} diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/FixedHeightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/FixedHeightmap.kt index 4cf8bfb6c..dc0a1691b 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/FixedHeightmap.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/FixedHeightmap.kt @@ -15,7 +15,7 @@ package de.bixilon.minosoft.data.world.chunk.heightmap import de.bixilon.minosoft.data.registries.blocks.state.BlockState -class FixedHeightmap(val value: Int) : AbstractHeightmap { +class FixedHeightmap(val value: Int) : Heightmap { override fun recalculate() = Unit diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/Heightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/Heightmap.kt index c8a75f2d5..60cc32933 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/Heightmap.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/Heightmap.kt @@ -14,113 +14,13 @@ package de.bixilon.minosoft.data.world.chunk.heightmap import de.bixilon.minosoft.data.registries.blocks.state.BlockState -import de.bixilon.minosoft.data.world.chunk.chunk.Chunk -import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight -import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition -abstract class Heightmap(protected val chunk: Chunk) : AbstractHeightmap { - protected val heightmap = IntArray(ProtocolDefinition.SECTION_WIDTH_X * ProtocolDefinition.SECTION_WIDTH_Z) { Int.MIN_VALUE } +interface Heightmap { - override fun get(index: Int) = heightmap[index] - override fun get(x: Int, z: Int) = heightmap[(z shl 4) or x] + fun recalculate() + operator fun get(x: Int, z: Int): Int + operator fun get(index: Int): Int - protected abstract fun passes(state: BlockState): HeightmapPass - protected abstract fun onHeightmapUpdate(x: Int, z: Int, previous: Int, now: Int) - - - override fun recalculate() { - chunk.lock.lock() - val maxY = chunk.maxSection * ProtocolDefinition.SECTION_HEIGHT_Y - - for (x in 0 until ProtocolDefinition.SECTION_WIDTH_X) { - for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) { - trace(x, maxY, z, false) - } - } - chunk.lock.unlock() - } - - private fun trace(x: Int, startY: Int, z: Int, notify: Boolean) { - val sections = chunk.sections - - var y = Int.MIN_VALUE - - sectionLoop@ for (sectionIndex in (startY.sectionHeight - chunk.minSection) downTo 0) { - if (sectionIndex >= sections.size) { - // starting from above world - continue - } - val section = sections[sectionIndex] ?: continue - if (section.blocks.isEmpty) continue - - val min = section.blocks.minPosition - val max = section.blocks.maxPosition - - if (x < min.x || x > max.x || z < min.z || z > max.z) continue // out of section - - section.acquire() - for (sectionY in max.y downTo min.y) { - val state = section.blocks[x, sectionY, z] ?: continue - val pass = passes(state) - if (pass == HeightmapPass.PASSES) continue - - y = (sectionIndex + chunk.minSection) * ProtocolDefinition.SECTION_HEIGHT_Y + sectionY - if (pass == HeightmapPass.ABOVE) y++ - - section.release() - break@sectionLoop - } - section.release() - } - val index = (z shl 4) or x - val previous = heightmap[index] - - if (previous == y) return - - heightmap[index] = y - - if (notify) { - onHeightmapUpdate(x, z, previous, y) - } - } - - - override fun onBlockChange(x: Int, y: Int, z: Int, state: BlockState?) { - chunk.lock.lock() - val index = (z shl 4) or x - - val previous = heightmap[index] - - if (previous > y + 1) { - // our block is/was not the highest, ignore everything - chunk.lock.unlock() - return - } - if (state == null) { - trace(x, y, z, true) - chunk.lock.unlock() - return - } - - val next = when (passes(state)) { - HeightmapPass.ABOVE -> y + 1 - HeightmapPass.IN -> y - HeightmapPass.PASSES -> previous - } - - chunk.lock.unlock() - - if (previous != next) { - heightmap[index] = next - onHeightmapUpdate(x, z, previous, next) - } - } - - protected enum class HeightmapPass { - ABOVE, - IN, - PASSES, - ; - } + fun onBlockChange(x: Int, y: Int, z: Int, state: BlockState?) } diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt index 1bc575cca..eb25105b7 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/LightHeightmap.kt @@ -18,7 +18,7 @@ import de.bixilon.minosoft.data.registries.blocks.state.BlockState import de.bixilon.minosoft.data.world.chunk.chunk.Chunk import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight -class LightHeightmap(chunk: Chunk) : Heightmap(chunk) { +class LightHeightmap(chunk: Chunk) : ChunkHeightmap(chunk) { override fun recalculate() { From 9f57e8467460a91acf2ce141bffd485252adb2a6 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Fri, 28 Jul 2023 11:31:41 +0200 Subject: [PATCH 10/17] test section block data min and max position, fix initial position Yep, that actually was wrong and decreased the performance --- .../block/BlockSectionDataProviderTest.kt | 79 +++++++++++++++++++ .../world/container/SectionDataProvider.kt | 2 +- 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/integration-test/kotlin/de/bixilon/minosoft/data/world/container/block/BlockSectionDataProviderTest.kt diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/container/block/BlockSectionDataProviderTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/container/block/BlockSectionDataProviderTest.kt new file mode 100644 index 000000000..abe47609c --- /dev/null +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/container/block/BlockSectionDataProviderTest.kt @@ -0,0 +1,79 @@ +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 org.testng.Assert.* +import org.testng.annotations.Test + +@Test(groups = ["chunk"], dependsOnGroups = ["block"]) +class BlockSectionDataProviderTest { + + private fun create(): BlockSectionDataProvider { + return BlockSectionDataProvider(null) + } + + fun `initial empty`() { + val blocks = create() + assertTrue(blocks.isEmpty) + assertEquals(blocks.fluidCount, 0) + assertEquals(blocks.count, 0) + } + + fun `single block set and removed`() { + val blocks = create() + blocks[0] = StoneTest0.state + blocks[0] = null + assertTrue(blocks.isEmpty) + assertEquals(blocks.fluidCount, 0) + assertEquals(blocks.count, 0) + } + + fun `single block set`() { + val blocks = create() + blocks[0] = StoneTest0.state + assertFalse(blocks.isEmpty) + assertEquals(blocks.fluidCount, 0) + assertEquals(blocks.count, 1) + } + + fun `initial min max position`() { + val blocks = create() + assertEquals(blocks.minPosition, Vec3i(16, 16, 16)) + assertEquals(blocks.maxPosition, Vec3i(0, 0, 0)) + } + + fun `set min max position`() { + val blocks = create() + blocks[0] = StoneTest0.state + assertEquals(blocks.minPosition, Vec3i(0, 0, 0)) + assertEquals(blocks.maxPosition, Vec3i(0, 0, 0)) + } + + fun `set min max position but block not on edge`() { + val blocks = create() + blocks[3, 5, 8] = StoneTest0.state + assertEquals(blocks.minPosition, Vec3i(3, 5, 8)) + assertEquals(blocks.maxPosition, Vec3i(3, 5, 8)) + } + + fun `set min max position but multiple blocks set`() { + val blocks = create() + blocks[3, 5, 8] = StoneTest0.state + blocks[1, 2, 12] = StoneTest0.state + assertEquals(blocks.minPosition, Vec3i(1, 2, 8)) + assertEquals(blocks.maxPosition, Vec3i(3, 5, 12)) + } + + fun `remove one min max position but multiple blocks set`() { + val blocks = create() + blocks[3, 5, 8] = StoneTest0.state + blocks[1, 2, 12] = StoneTest0.state + blocks[15, 14, 13] = StoneTest0.state + assertEquals(blocks.minPosition, Vec3i(1, 2, 8)) + assertEquals(blocks.maxPosition, Vec3i(15, 14, 13)) + blocks[15, 14, 13] = null + assertEquals(blocks.maxPosition, Vec3i(3, 5, 12)) + } + + // TODO: test initial block set +} diff --git a/src/main/java/de/bixilon/minosoft/data/world/container/SectionDataProvider.kt b/src/main/java/de/bixilon/minosoft/data/world/container/SectionDataProvider.kt index 0eda72fc5..bfb1b37b6 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/container/SectionDataProvider.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/container/SectionDataProvider.kt @@ -42,7 +42,7 @@ open class SectionDataProvider( if (data != null && calculateInitial) { recalculate() } else { - minPosition = Vec3i.EMPTY + minPosition = Vec3i(ProtocolDefinition.CHUNK_SECTION_SIZE) maxPosition = Vec3i.EMPTY } } From 305e4f30bf76267d150c928887c9198b18a31aa4 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Fri, 28 Jul 2023 11:36:23 +0200 Subject: [PATCH 11/17] block light calculation: only trace from positions with blocks This should optimize it a bit --- .../minosoft/data/world/chunk/light/SectionLight.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt index 3f61722ec..1a22f8705 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt @@ -228,9 +228,12 @@ class SectionLight( val blocks = section.blocks blocks.acquire() - for (x in 0 until ProtocolDefinition.SECTION_WIDTH_X) { - for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) { - for (y in 0 until ProtocolDefinition.SECTION_HEIGHT_Y) { + val min = blocks.minPosition + val max = blocks.maxPosition + + for (x in min.x..max.x) { + for (z in min.z..max.z) { + for (y in min.y..max.y) { val index = getIndex(x, y, z) val luminance = blocks[index]?.luminance ?: continue if (luminance == 0) { From 0101001f5266ffbcd66b9b9e52e33ec44940108e Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Fri, 28 Jul 2023 11:50:29 +0200 Subject: [PATCH 12/17] chunk: update heightmap before checking light This should improve performance and reduce wrong results (especially with the new sky light optimisations) --- .../minosoft/data/world/chunk/ChunkSection.kt | 20 ------------------- .../minosoft/data/world/chunk/chunk/Chunk.kt | 19 +++++++++++------- .../data/world/chunk/light/SectionLight.kt | 6 +++--- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/ChunkSection.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/ChunkSection.kt index 97d2070bd..bec40d3e2 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/ChunkSection.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/ChunkSection.kt @@ -18,7 +18,6 @@ import de.bixilon.kutil.array.ArrayUtil.cast import de.bixilon.kutil.cast.CastUtil.unsafeNull import de.bixilon.minosoft.data.entities.block.BlockEntity import de.bixilon.minosoft.data.registries.biomes.Biome -import de.bixilon.minosoft.data.registries.blocks.state.BlockState import de.bixilon.minosoft.data.world.biome.accessor.NoiseBiomeAccessor import de.bixilon.minosoft.data.world.chunk.chunk.Chunk import de.bixilon.minosoft.data.world.chunk.light.SectionLight @@ -97,25 +96,6 @@ class ChunkSection( } - operator fun set(x: Int, y: Int, z: Int, block: BlockState?): BlockState? { - val previous = blocks.set(x, y, z, block) - if (chunk.world.dimension.light) { - light.onBlockChange(x, y, z, previous, block) - } - - return previous - } - - fun unsafeSet(x: Int, y: Int, z: Int, block: BlockState?): BlockState? { - val previous = blocks.unsafeSet(x, y, z, block) - - if (chunk.world.dimension.light) { - light.onBlockChange(x, y, z, previous, block) - } - - return previous - } - fun clear() { blocks.clear() biomes.clear() diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt index 1841788c9..9976ff494 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt @@ -71,19 +71,24 @@ class Chunk( operator fun set(x: Int, y: Int, z: Int, state: BlockState?) { val section = getOrPut(y.sectionHeight) ?: return - val previous = section.set(x, y and 0x0F, z, state) + val previous = section.blocks.set(x, y and 0x0F, z, state) if (previous == state) return val entity = getOrPutBlockEntity(x, y, z) - light.onBlockChange(x, y, z, section, state) // TODO: heightmap gets updated after the block set? -> optimize/maybe even invalid + + if (world.dimension.light) { + light.onBlockChange(x, y, z, section, state) + section.light.onBlockChange(x, y and 0x0F, z, previous, state) + } SingleBlockUpdate(Vec3i(chunkPosition.x * ProtocolDefinition.SECTION_WIDTH_X + x, y, chunkPosition.y * ProtocolDefinition.SECTION_WIDTH_Z + z), this, state, entity).fire(connection) } - operator fun set(x: Int, y: Int, z: Int, state: BlockState?, entity: BlockEntity?) { - val section = getOrPut(y.sectionHeight) ?: return - section[x, y and 0x0F, z] = state - section.blockEntities[x, y and 0x0F, z] = entity - } +// fun set(x: Int, y: Int, z: Int, state: BlockState?, entity: BlockEntity?) { +// val section = getOrPut(y.sectionHeight) ?: return +// section.blocks[x, y and 0x0F, z] = state +// section.blockEntities[x, y and 0x0F, z] = entity +// // TODO: light update +// } operator fun set(position: Vec3i, blockState: BlockState?) = set(position.x, position.y, position.z, blockState) diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt index 1a22f8705..66f4cef77 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt @@ -27,12 +27,12 @@ class SectionLight( var light: ByteArray = ByteArray(ProtocolDefinition.BLOCKS_PER_SECTION), // packed (skyLight: 0xF0, blockLight: 0x0F) ) : AbstractSectionLight() { - fun onBlockChange(x: Int, y: Int, z: Int, previous: BlockState?, now: BlockState?) { + fun onBlockChange(x: Int, y: Int, z: Int, previous: BlockState?, state: BlockState?) { val previousLuminance = previous?.luminance ?: 0 - val luminance = now?.luminance ?: 0 + val luminance = state?.luminance ?: 0 if (previousLuminance == luminance) { - val nowProperties = now?.block?.getLightProperties(now) + val nowProperties = state?.block?.getLightProperties(state) if (previous?.block?.getLightProperties(previous)?.propagatesLight == nowProperties?.propagatesLight) { // no change for light data return From ba535c2298afca2bd501226d219d36205c0e13ca Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Fri, 28 Jul 2023 12:20:05 +0200 Subject: [PATCH 13/17] more skylight tests --- .../data/world/chunk/light/SkyLightTraceIT.kt | 9 +++++++ .../chunk/light/place/SkyLightPlaceIT.kt | 26 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/SkyLightTraceIT.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/SkyLightTraceIT.kt index 485636830..a5a7f5653 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/SkyLightTraceIT.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/SkyLightTraceIT.kt @@ -36,6 +36,15 @@ class SkyLightTraceIT { world.assertLight(7, 11, 8, 0xE0) } + fun `heightmap optimization west, upper block set 2`() { + val world = createConnection(3, light = true).world + world[Vec3i(8, 255, 8)] = StoneTest0.state + world[Vec3i(7, 1, 8)] = StoneTest0.state + for (y in 0..254) { + world.assertLight(8, y, 8, 0xE0) + } + } + fun `heightmap optimization east, upper block set`() { val world = createConnection(3, light = true).world world[Vec3i(8, 10, 8)] = StoneTest0.state diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/SkyLightPlaceIT.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/SkyLightPlaceIT.kt index 131ff0d67..212e9ef5d 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/SkyLightPlaceIT.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/SkyLightPlaceIT.kt @@ -44,6 +44,32 @@ class SkyLightPlaceIT { world.assertLight(8, 9, 8, 0xE0) } + fun `below block 1`() { + val world = createConnection(3, light = true).world + world[Vec3i(8, 16, 8)] = StoneTest0.state + world.assertLight(8, 15, 8, 0xE0) + } + + fun `below block 2`() { + val world = createConnection(3, light = true).world + world[Vec3i(8, 0, 8)] = StoneTest0.state + world.assertLight(8, -1, 8, 0xE0) + } + + fun `below block 3`() { + val world = createConnection(3, light = true).world + world[Vec3i(8, 15, 8)] = StoneTest0.state + world.assertLight(8, 14, 8, 0xE0) + } + + fun `more blocks below block`() { + val world = createConnection(3, light = true).world + world[Vec3i(8, 37, 8)] = StoneTest0.state + for (y in 0..36) { + world.assertLight(8, y, 8, 0xE0) + } + } + fun belowBlock3() { val world = createConnection(3, light = true).world world.fill(7, 10, 7, 9, 10, 9, StoneTest0.state, false) From 8231d20954dc2edd5155f0a7f9dcc7fac9c544e8 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Fri, 28 Jul 2023 13:06:00 +0200 Subject: [PATCH 14/17] fix some light and chunk bugs --- .../world/chunk/heightmap/ChunkHeightmap.kt | 2 +- .../data/world/chunk/light/ChunkSkyLight.kt | 55 ++++++++++--------- .../world/chunk/neighbours/ChunkNeighbours.kt | 4 +- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/ChunkHeightmap.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/ChunkHeightmap.kt index 450c691ac..d31725508 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/ChunkHeightmap.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/heightmap/ChunkHeightmap.kt @@ -31,7 +31,7 @@ abstract class ChunkHeightmap(protected val chunk: Chunk) : Heightmap { override fun recalculate() { chunk.lock.lock() - val maxY = chunk.maxSection * ProtocolDefinition.SECTION_HEIGHT_Y + val maxY = (chunk.maxSection + 1) * ProtocolDefinition.SECTION_HEIGHT_Y for (x in 0 until ProtocolDefinition.SECTION_WIDTH_X) { for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) { diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkSkyLight.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkSkyLight.kt index 1fa5ca001..8f1b219e5 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkSkyLight.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkSkyLight.kt @@ -17,6 +17,7 @@ import de.bixilon.minosoft.data.direction.Directions import de.bixilon.minosoft.data.world.chunk.chunk.Chunk import de.bixilon.minosoft.data.world.chunk.light.ChunkLightUtil.hasSkyLight import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours +import de.bixilon.minosoft.data.world.positions.SectionHeight import de.bixilon.minosoft.gui.rendering.util.VecUtil.inSectionHeight import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition @@ -33,42 +34,42 @@ class ChunkSkyLight(val light: ChunkLight) { floodFill() } - private fun trace(x: Int, topY: Int, bottomY: Int, z: Int, source: Directions) { - if (topY == Int.MIN_VALUE && bottomY == Int.MIN_VALUE) return // no blocks are set in that column, no need to trace. all levels are MAX - if (bottomY > topY) return // started position is higher than at this, no need to trace + private fun traceSection(sectionHeight: SectionHeight, x: Int, topY: Int, bottomY: Int, z: Int, source: Directions) { + val section = chunk.getOrPut(sectionHeight) ?: return + val baseY = sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y + for (y in topY downTo bottomY) { + section.light.traceSkyLightIncrease(x, y, z, NEIGHBOUR_TRACE_LEVEL, source, baseY + y, false) + } + section.light.update = true + } + + private fun trace(x: Int, topY: Int, bottomY: Int, z: Int, source: Directions) { + if (topY == Int.MIN_VALUE) return // no blocks are set in that column, no need to trace. all levels are MAX + if (bottomY > topY) return // started position is higher than at this, no need to trace // trace section after section val topSection = topY.sectionHeight val bottomSection = bottomY.sectionHeight + val sections = topSection - maxOf(chunk.minSection, bottomSection) + 1 - - for (sectionHeight in topSection downTo maxOf(chunk.minSection, bottomSection + 1)) { - val section = chunk[sectionHeight] ?: continue - val baseY = sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y - - for (y in ProtocolDefinition.SECTION_MAX_Y downTo 0) { - section.light.traceSkyLightIncrease(x, y, z, ProtocolDefinition.MAX_LIGHT_LEVEL_I, source, baseY + y, false) - } - section.light.update = true + // top section + if (sections > 1) { + traceSection(topSection, x, topY.inSectionHeight, 0, z, source) } - // trace lowest section just from heightmap start + // middle sections + for (sectionHeight in (topSection - 1) downTo maxOf(chunk.minSection, bottomSection) + 1) { + traceSection(sectionHeight, x, ProtocolDefinition.SECTION_MAX_Y, 0, z, source) + } - if (bottomSection < chunk.minSection) { - chunk.light.bottom.traceSkyIncrease(x, z, ProtocolDefinition.MAX_LIGHT_LEVEL_I) - } else { - val section = chunk[bottomSection] - if (section != null) { - val baseY = bottomSection * ProtocolDefinition.SECTION_HEIGHT_Y - val start = if (topSection == bottomSection) topY.inSectionHeight else ProtocolDefinition.SECTION_MAX_Y - for (y in start downTo bottomY.inSectionHeight) { - section.light.traceSkyLightIncrease(x, y, z, ProtocolDefinition.MAX_LIGHT_LEVEL_I, source, baseY + y, false) - } - section.light.update = true - } + // lowest section + traceSection(if (bottomY == Int.MIN_VALUE) chunk.minSection else bottomSection, x, if (topSection == bottomSection) topY.inSectionHeight else ProtocolDefinition.SECTION_MAX_Y, if (bottomY == Int.MIN_VALUE) 0 else bottomY.inSectionHeight, z, source) + + if (bottomY == Int.MIN_VALUE) { + chunk.light.bottom.traceSkyIncrease(x, z, NEIGHBOUR_TRACE_LEVEL) } } @@ -168,4 +169,8 @@ class ChunkSkyLight(val light: ChunkLight) { ) } + + private companion object { + const val NEIGHBOUR_TRACE_LEVEL = ProtocolDefinition.MAX_LIGHT_LEVEL_I - 1 + } } diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/neighbours/ChunkNeighbours.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/neighbours/ChunkNeighbours.kt index 0a51514a2..f1acd200c 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/neighbours/ChunkNeighbours.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/neighbours/ChunkNeighbours.kt @@ -133,12 +133,12 @@ class ChunkNeighbours(val chunk: Chunk) : Iterable { this[1] } - offset.y < 0 -> { + offset.y > 0 -> { offset.y-- this[4] } - offset.y > 0 -> { + offset.y < 0 -> { offset.y++ this[3] } From 358b578ba6159da38846a6694ffad0251c9a8628 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Sat, 29 Jul 2023 00:02:29 +0200 Subject: [PATCH 15/17] light: properly fire light changes --- .../chunk/light/place/BlockLightPlaceIT.kt | 25 +++++++++---------- .../minosoft/data/world/chunk/chunk/Chunk.kt | 3 +-- .../data/world/chunk/light/ChunkLight.kt | 9 +++---- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/BlockLightPlaceIT.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/BlockLightPlaceIT.kt index de19aa82e..1e0c613c8 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/BlockLightPlaceIT.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/BlockLightPlaceIT.kt @@ -15,7 +15,7 @@ package de.bixilon.minosoft.data.world.chunk.light.place import de.bixilon.kotlinglm.vec2.Vec2i import de.bixilon.kotlinglm.vec3.Vec3i -import de.bixilon.kutil.collections.CollectionUtil.synchronizedSetOf +import de.bixilon.kutil.collections.CollectionUtil.synchronizedListOf import de.bixilon.minosoft.data.registries.blocks.TorchTest0 import de.bixilon.minosoft.data.registries.blocks.types.stone.StoneTest0 import de.bixilon.minosoft.data.world.WorldTestUtil.fill @@ -166,24 +166,23 @@ class BlockLightPlaceIT { fun lightUpdate() { val world = ConnectionTestUtil.createConnection(3, light = true).world - val events: MutableSet = synchronizedSetOf() + val events: MutableList = synchronizedListOf() world.connection.events.listen { if (it.update !is ChunkLightUpdate) return@listen events += Vec3i(it.update.chunkPosition.x, (it.update as ChunkLightUpdate).sectionHeight, it.update.chunkPosition.y) } world[Vec3i(8, 24, 8)] = TorchTest0.state - assertEquals( - events, setOf( - Vec3i(+0, 1, +0), - Vec3i(+0, 0, +0), - Vec3i(+0, 2, +0), - Vec3i(+0, 1, -1), - Vec3i(+0, 1, +1), - Vec3i(-1, 1, +0), - Vec3i(+1, 1, +0), - ) - ) + assertEquals(events.toSet(), setOf( + Vec3i(+0, 1, +0), + Vec3i(+0, 0, +0), + Vec3i(+0, 2, +0), + Vec3i(+0, 1, -1), + Vec3i(+0, 1, +1), + Vec3i(-1, 1, +0), + Vec3i(+1, 1, +0), + )) + assertEquals(events.size, 7) } diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt index 9976ff494..0843a84bc 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/chunk/Chunk.kt @@ -76,8 +76,7 @@ class Chunk( val entity = getOrPutBlockEntity(x, y, z) if (world.dimension.light) { - light.onBlockChange(x, y, z, section, state) - section.light.onBlockChange(x, y and 0x0F, z, previous, state) + light.onBlockChange(x, y, z, section, previous, state) } SingleBlockUpdate(Vec3i(chunkPosition.x * ProtocolDefinition.SECTION_WIDTH_X + x, y, chunkPosition.y * ProtocolDefinition.SECTION_WIDTH_Z + z), this, state, entity).fire(connection) diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLight.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLight.kt index 3200bd87a..32371d602 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLight.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkLight.kt @@ -37,19 +37,18 @@ class ChunkLight(val chunk: Chunk) { val sky = ChunkSkyLight(this) - fun onBlockChange(x: Int, y: Int, z: Int, section: ChunkSection, next: BlockState?) { - if (!chunk.world.dimension.light) { - return - } + fun onBlockChange(x: Int, y: Int, z: Int, section: ChunkSection, previous: BlockState?, next: BlockState?) { heightmap.onBlockChange(x, y, z, next) + section.light.onBlockChange(x, y and 0x0F, z, previous, next) + val neighbours = chunk.neighbours.get() ?: return fireLightChange(section, y.sectionHeight, neighbours) } - private fun fireLightChange(section: ChunkSection, sectionHeight: Int, neighbours: Array, fireSameChunkEvent: Boolean = true) { + fun fireLightChange(section: ChunkSection, sectionHeight: Int, neighbours: Array, fireSameChunkEvent: Boolean = true) { if (!section.light.update) { return } From 9e08ec70797d428d120403e7c8bdf55dd25c1e87 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Sat, 29 Jul 2023 00:34:05 +0200 Subject: [PATCH 16/17] fix some sky light neighbour tracing bugs --- build.gradle.kts | 2 +- .../chunk/light/place/SkyLightPlaceIT.kt | 2 ++ .../data/world/chunk/light/ChunkSkyLight.kt | 28 +++++++++---------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 97eda7c0b..d7937a3cb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -222,7 +222,7 @@ testing { options { val options = this as TestNGOptions options.preserveOrder = true - options.excludeGroups("input", "font", "command", "registry", "biome", "version", "fluid", "world", "raycasting", "pixlyzer", "item", "physics", "packet", "container", "item_stack", "signature", "private_key", "interaction", "item_digging", "world_renderer", "rendering") + // options.excludeGroups("input", "font", "command", "registry", "biome", "version", "fluid", "world", "raycasting", "pixlyzer", "item", "physics", "packet", "container", "item_stack", "signature", "private_key", "interaction", "item_digging", "world_renderer", "rendering") } } } diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/SkyLightPlaceIT.kt b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/SkyLightPlaceIT.kt index 212e9ef5d..ca11130e7 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/SkyLightPlaceIT.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/data/world/chunk/light/place/SkyLightPlaceIT.kt @@ -73,6 +73,8 @@ class SkyLightPlaceIT { fun belowBlock3() { val world = createConnection(3, light = true).world world.fill(7, 10, 7, 9, 10, 9, StoneTest0.state, false) + // world.chunks[0,0]!!.light.reset() + // world.chunks[0,0]!!.light.sky.calculate() world.assertLight(8, 9, 8, 0xD0) } diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkSkyLight.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkSkyLight.kt index 8f1b219e5..f1c4f506f 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkSkyLight.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/ChunkSkyLight.kt @@ -34,17 +34,17 @@ class ChunkSkyLight(val light: ChunkLight) { floodFill() } - private fun traceSection(sectionHeight: SectionHeight, x: Int, topY: Int, bottomY: Int, z: Int, source: Directions) { + private fun traceSection(sectionHeight: SectionHeight, x: Int, topY: Int, bottomY: Int, z: Int, target: Directions) { val section = chunk.getOrPut(sectionHeight) ?: return val baseY = sectionHeight * ProtocolDefinition.SECTION_HEIGHT_Y for (y in topY downTo bottomY) { - section.light.traceSkyLightIncrease(x, y, z, NEIGHBOUR_TRACE_LEVEL, source, baseY + y, false) + section.light.traceSkyLightIncrease(x, y, z, NEIGHBOUR_TRACE_LEVEL, target, baseY + y, false) } section.light.update = true } - private fun trace(x: Int, topY: Int, bottomY: Int, z: Int, source: Directions) { + private fun trace(x: Int, topY: Int, bottomY: Int, z: Int, target: Directions) { if (topY == Int.MIN_VALUE) return // no blocks are set in that column, no need to trace. all levels are MAX if (bottomY > topY) return // started position is higher than at this, no need to trace @@ -57,16 +57,16 @@ class ChunkSkyLight(val light: ChunkLight) { // top section if (sections > 1) { - traceSection(topSection, x, topY.inSectionHeight, 0, z, source) + traceSection(topSection, x, topY.inSectionHeight, 0, z, target) } // middle sections for (sectionHeight in (topSection - 1) downTo maxOf(chunk.minSection, bottomSection) + 1) { - traceSection(sectionHeight, x, ProtocolDefinition.SECTION_MAX_Y, 0, z, source) + traceSection(sectionHeight, x, ProtocolDefinition.SECTION_MAX_Y, 0, z, target) } // lowest section - traceSection(if (bottomY == Int.MIN_VALUE) chunk.minSection else bottomSection, x, if (topSection == bottomSection) topY.inSectionHeight else ProtocolDefinition.SECTION_MAX_Y, if (bottomY == Int.MIN_VALUE) 0 else bottomY.inSectionHeight, z, source) + traceSection(if (bottomY == Int.MIN_VALUE) chunk.minSection else bottomSection, x, if (topSection == bottomSection) topY.inSectionHeight else ProtocolDefinition.SECTION_MAX_Y, if (bottomY == Int.MIN_VALUE) 0 else bottomY.inSectionHeight, z, target) if (bottomY == Int.MIN_VALUE) { chunk.light.bottom.traceSkyIncrease(x, z, NEIGHBOUR_TRACE_LEVEL) @@ -91,31 +91,31 @@ class ChunkSkyLight(val light: ChunkLight) { traceDown(x, maxHeight, z) if (x > 0) { - trace(x - 1, light.heightmap[heightmapIndex - 1], maxHeight, z, Directions.EAST) + trace(x - 1, light.heightmap[heightmapIndex - 1], maxHeight, z, Directions.WEST) } else { val neighbour = neighbours[ChunkNeighbours.WEST].light - neighbour.sky.trace(ProtocolDefinition.SECTION_MAX_X, neighbour.heightmap[(z shl 4) or ProtocolDefinition.SECTION_MAX_X], maxHeight, z, Directions.EAST) + neighbour.sky.trace(ProtocolDefinition.SECTION_MAX_X, neighbour.heightmap[(z shl 4) or ProtocolDefinition.SECTION_MAX_X], maxHeight, z, Directions.WEST) } if (x < ProtocolDefinition.SECTION_MAX_X) { - trace(x + 1, light.heightmap[heightmapIndex + 1], maxHeight, z, Directions.WEST) + trace(x + 1, light.heightmap[heightmapIndex + 1], maxHeight, z, Directions.EAST) } else { val neighbour = neighbours[ChunkNeighbours.EAST].light - neighbour.sky.trace(0, neighbour.heightmap[(z shl 4) or 0], maxHeight, z, Directions.WEST) + neighbour.sky.trace(0, neighbour.heightmap[(z shl 4) or 0], maxHeight, z, Directions.EAST) } if (z > 0) { - trace(x, light.heightmap[((z - 1) shl 4) or x], maxHeight, z - 1, Directions.SOUTH) + trace(x, light.heightmap[((z - 1) shl 4) or x], maxHeight, z - 1, Directions.NORTH) } else { val neighbour = neighbours[ChunkNeighbours.NORTH].light - neighbour.sky.trace(x, neighbour.heightmap[(ProtocolDefinition.SECTION_MAX_Z shl 4) or x], maxHeight, ProtocolDefinition.SECTION_MAX_Z, Directions.SOUTH) + neighbour.sky.trace(x, neighbour.heightmap[(ProtocolDefinition.SECTION_MAX_Z shl 4) or x], maxHeight, ProtocolDefinition.SECTION_MAX_Z, Directions.NORTH) } if (z < ProtocolDefinition.SECTION_MAX_Z) { - trace(x, light.heightmap[((z + 1) shl 4) or x], maxHeight, z + 1, Directions.NORTH) + trace(x, light.heightmap[((z + 1) shl 4) or x], maxHeight, z + 1, Directions.SOUTH) } else { val neighbour = neighbours[ChunkNeighbours.SOUTH].light - neighbour.sky.trace(x, neighbour.heightmap[(0 shl 4) or x], maxHeight, 0, Directions.NORTH) + neighbour.sky.trace(x, neighbour.heightmap[(0 shl 4) or x], maxHeight, 0, Directions.SOUTH) } } From 732394c5fe61eaac82b174856c01fd74aade4e12 Mon Sep 17 00:00:00 2001 From: Moritz Zwerger Date: Sat, 29 Jul 2023 00:45:11 +0200 Subject: [PATCH 17/17] fix light benchmark --- .../de/bixilon/minosoft/data/world/chunk/ChunkTestingUtil.kt | 4 ++-- .../bixilon/minosoft/data/world/chunk/light/LightBenchmark.kt | 3 ++- .../bixilon/minosoft/data/world/chunk/light/SectionLight.kt | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/benchmark/kotlin/de/bixilon/minosoft/data/world/chunk/ChunkTestingUtil.kt b/src/benchmark/kotlin/de/bixilon/minosoft/data/world/chunk/ChunkTestingUtil.kt index 5e4960081..746cb7255 100644 --- a/src/benchmark/kotlin/de/bixilon/minosoft/data/world/chunk/ChunkTestingUtil.kt +++ b/src/benchmark/kotlin/de/bixilon/minosoft/data/world/chunk/ChunkTestingUtil.kt @@ -22,6 +22,7 @@ import de.bixilon.minosoft.data.registries.blocks.light.OpaqueProperty import de.bixilon.minosoft.data.registries.blocks.settings.BlockSettings import de.bixilon.minosoft.data.registries.blocks.state.AdvancedBlockState import de.bixilon.minosoft.data.registries.blocks.state.BlockState +import de.bixilon.minosoft.data.registries.blocks.state.manager.SimpleStateManager import de.bixilon.minosoft.data.registries.blocks.types.Block import de.bixilon.minosoft.data.registries.blocks.types.TestBlock import de.bixilon.minosoft.data.registries.dimension.DimensionProperties @@ -109,8 +110,7 @@ object ChunkTestingUtil { fun createBlock(name: String, luminance: Int, lightProperties: LightProperties): Block { val block = TestBlock(minosoft(name), BlockSettings()) val state = AdvancedBlockState(block, properties = emptyMap(), collisionShape = AbstractVoxelShape.EMPTY, outlineShape = AbstractVoxelShape.EMPTY, luminance = luminance, lightProperties = lightProperties, solidRenderer = true) - block::states.javaField!!.forceSet(block, setOf(state)) - block.states::default.javaField!!.forceSet(block, state) + block::states.forceSet(SimpleStateManager(state)) return block } diff --git a/src/benchmark/kotlin/de/bixilon/minosoft/data/world/chunk/light/LightBenchmark.kt b/src/benchmark/kotlin/de/bixilon/minosoft/data/world/chunk/light/LightBenchmark.kt index e5742cac1..c42ad6564 100644 --- a/src/benchmark/kotlin/de/bixilon/minosoft/data/world/chunk/light/LightBenchmark.kt +++ b/src/benchmark/kotlin/de/bixilon/minosoft/data/world/chunk/light/LightBenchmark.kt @@ -79,9 +79,10 @@ internal class LightBenchmark { for (index in 0 until 256) { highest.unsafeSet(index or (0x0F shl 8), solid) } + var totalPlace = 0L var totalBreak = 0L - val benchmark = BenchmarkUtil.benchmark(10000) { + val benchmark = BenchmarkUtil.benchmark(100000) { totalBreak += measureNanoTime { chunk[7, 255, 7] = null } totalPlace += measureNanoTime { chunk[7, 255, 7] = solid } } diff --git a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt index 66f4cef77..6902773b0 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/chunk/light/SectionLight.kt @@ -372,7 +372,7 @@ class SectionLight( (neighbours[Directions.O_DOWN] ?: chunk.getOrPut(section.sectionHeight - 1, false))?.light?.traceSkyLightIncrease(x, ProtocolDefinition.SECTION_MAX_Y, z, nextNeighbourLevel, Directions.DOWN, totalY - 1) } } - if (target != Directions.DOWN && target != null && (lightProperties.propagatesLight(Directions.UP))) { + if (target != Directions.DOWN && (target != null || lightProperties.propagatesLight(Directions.UP))) { if (y < ProtocolDefinition.SECTION_MAX_Y) { traceSkyLightIncrease(x, y + 1, z, nextNeighbourLevel, Directions.UP, totalY + 1) } else if (section.sectionHeight < chunk.maxSection) {