mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-14 18:05:51 -04:00
refactor heightmap
This commit is contained in:
parent
ebb5b8a04e
commit
4d02696bc5
@ -14,6 +14,8 @@
|
|||||||
package de.bixilon.minosoft.data.world.chunk.light
|
package de.bixilon.minosoft.data.world.chunk.light
|
||||||
|
|
||||||
import de.bixilon.kotlinglm.vec3.Vec3i
|
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.registries.blocks.types.stone.StoneTest0
|
||||||
import de.bixilon.minosoft.data.world.chunk.ChunkTestingUtil.createChunkWithNeighbours
|
import de.bixilon.minosoft.data.world.chunk.ChunkTestingUtil.createChunkWithNeighbours
|
||||||
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
|
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
|
||||||
@ -25,15 +27,6 @@ import org.testng.annotations.Test
|
|||||||
@Test(groups = ["light"], dependsOnGroups = ["block"])
|
@Test(groups = ["light"], dependsOnGroups = ["block"])
|
||||||
class GeneralHeightmapTest {
|
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() {
|
fun testMinHeightEast() {
|
||||||
val chunk: Chunk = createChunkWithNeighbours()
|
val chunk: Chunk = createChunkWithNeighbours()
|
||||||
chunk[Vec3i(2, 10, 3)] = StoneTest0.state
|
chunk[Vec3i(2, 10, 3)] = StoneTest0.state
|
||||||
@ -43,16 +36,6 @@ class GeneralHeightmapTest {
|
|||||||
assertEquals(chunk.light.getNeighbourMinHeight(chunk.neighbours.get()!!, 3, 3), 11)
|
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() {
|
fun testMinHeightNeighbourEast() {
|
||||||
val chunk: Chunk = createChunkWithNeighbours()
|
val chunk: Chunk = createChunkWithNeighbours()
|
||||||
val neighbours = chunk.neighbours.get()!!
|
val neighbours = chunk.neighbours.get()!!
|
||||||
@ -62,6 +45,24 @@ class GeneralHeightmapTest {
|
|||||||
neighbours[ChunkNeighbours.EAST][Vec3i(0, 10, 3)] = StoneTest0.state
|
neighbours[ChunkNeighbours.EAST][Vec3i(0, 10, 3)] = StoneTest0.state
|
||||||
assertEquals(chunk.light.getNeighbourMinHeight(neighbours, 15, 3), 11)
|
assertEquals(chunk.light.getNeighbourMinHeight(neighbours, 15, 3), 11)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Test other directions
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,7 @@ import de.bixilon.minosoft.protocol.network.connection.play.ConnectionTestUtil.c
|
|||||||
import org.testng.annotations.Test
|
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 {
|
class SkyLightPlaceIT {
|
||||||
|
|
||||||
fun aboveBlock() {
|
fun aboveBlock() {
|
||||||
|
@ -228,7 +228,7 @@ class World(
|
|||||||
reset += { chunk.light.reset() }
|
reset += { chunk.light.reset() }
|
||||||
calculate += {
|
calculate += {
|
||||||
if (heightmap) {
|
if (heightmap) {
|
||||||
chunk.light.recalculateHeightmap()
|
chunk.light.heightmap.recalculate()
|
||||||
}
|
}
|
||||||
chunk.light.calculate()
|
chunk.light.calculate()
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ class Chunk(
|
|||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
light.recalculateHeightmap()
|
light.heightmap.recalculate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("neighbours.complete", ReplaceWith("neighbours.complete"))
|
@Deprecated("neighbours.complete", ReplaceWith("neighbours.complete"))
|
||||||
@ -162,7 +162,7 @@ class Chunk(
|
|||||||
if (executed.isEmpty()) {
|
if (executed.isEmpty()) {
|
||||||
return lock.unlock()
|
return lock.unlock()
|
||||||
}
|
}
|
||||||
light.recalculateHeightmap()
|
light.heightmap.recalculate()
|
||||||
light.recalculate()
|
light.recalculate()
|
||||||
|
|
||||||
for (section in sections) {
|
for (section in sections) {
|
||||||
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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?)
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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,
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
}
|
@ -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.registries.dimension.DimensionProperties
|
||||||
import de.bixilon.minosoft.data.world.chunk.ChunkSection
|
import de.bixilon.minosoft.data.world.chunk.ChunkSection
|
||||||
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
|
import de.bixilon.minosoft.data.world.chunk.chunk.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.neighbours.ChunkNeighbours
|
||||||
import de.bixilon.minosoft.data.world.chunk.update.AbstractWorldUpdate
|
import de.bixilon.minosoft.data.world.chunk.update.AbstractWorldUpdate
|
||||||
import de.bixilon.minosoft.data.world.chunk.update.chunk.ChunkLightUpdate
|
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) {
|
class ChunkLight(private val chunk: Chunk) {
|
||||||
private val connection = chunk.connection
|
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 bottom = BorderSectionLight(false, chunk)
|
||||||
val top = BorderSectionLight(true, chunk)
|
val top = BorderSectionLight(true, chunk)
|
||||||
@ -39,10 +41,7 @@ class ChunkLight(private val chunk: Chunk) {
|
|||||||
if (!chunk.world.dimension.light) {
|
if (!chunk.world.dimension.light) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val heightmapIndex = (z shl 4) or x
|
heightmap.onBlockChange(x, y, z, next)
|
||||||
val previous = heightmap[heightmapIndex]
|
|
||||||
recalculateHeightmap(x, y, z, next)
|
|
||||||
onHeightmapUpdate(x, y, z, previous, heightmap[heightmapIndex])
|
|
||||||
|
|
||||||
val neighbours = chunk.neighbours.get() ?: return
|
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) {
|
fun calculateSkylight() {
|
||||||
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() {
|
|
||||||
if (!chunk.world.dimension.canSkylight() || !chunk.neighbours.complete) {
|
if (!chunk.world.dimension.canSkylight() || !chunk.neighbours.complete) {
|
||||||
// no need to calculate it
|
// no need to calculate it
|
||||||
return
|
return
|
||||||
@ -400,6 +289,7 @@ class ChunkLight(private val chunk: Chunk) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("heightmap")
|
||||||
inline fun getMaxHeight(x: Int, z: Int): Int {
|
inline fun getMaxHeight(x: Int, z: Int): Int {
|
||||||
return heightmap[(z shl 4) or x]
|
return heightmap[(z shl 4) or x]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user