Merge branch 'improve-light'

This commit is contained in:
Moritz Zwerger 2023-07-29 00:51:09 +02:00
commit cfd1207cdd
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
35 changed files with 816 additions and 451 deletions

View File

@ -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")
}
}
}

View File

@ -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
}

View File

@ -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 }
}

View File

@ -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
}

View File

@ -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.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
@ -25,32 +27,13 @@ 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
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)
}
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)
assertEquals(chunk.light.sky.getNeighbourMinHeight(chunk.neighbours.get()!!, 3, 3), 11)
}
fun testMinHeightNeighbourEast() {
@ -60,8 +43,26 @@ 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
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)] = StairsTest0.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)
}
// TODO: Test other directions
}

View File

@ -0,0 +1,68 @@
/*
* 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, 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, 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
world[Vec3i(9, 12, 8)] = StoneTest0.state
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, 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, 0xE0)
}
}

View File

@ -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<Vec3i> = synchronizedSetOf()
val events: MutableList<Vec3i> = synchronizedListOf()
world.connection.events.listen<WorldUpdateEvent> {
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)
}

View File

@ -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() {
@ -44,9 +44,37 @@ 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)
// world.chunks[0,0]!!.light.reset()
// world.chunks[0,0]!!.light.sky.calculate()
world.assertLight(8, 9, 8, 0xD0)
}

View File

@ -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
}

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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
@ -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
@ -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]
@ -228,7 +228,7 @@ class World(
reset += { chunk.light.reset() }
calculate += {
if (heightmap) {
chunk.light.recalculateHeightmap()
chunk.light.heightmap.recalculate()
}
chunk.light.calculate()
}

View File

@ -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()

View File

@ -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.*
/**
@ -62,13 +58,9 @@ class Chunk(
init {
light.recalculateHeightmap()
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? {
@ -79,19 +71,23 @@ 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, 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)
@ -162,7 +158,7 @@ class Chunk(
if (executed.isEmpty()) {
return lock.unlock()
}
light.recalculateHeightmap()
light.heightmap.recalculate()
light.recalculate()
for (section in sections) {
@ -203,7 +199,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 +231,6 @@ class Chunk(
}
return biomeSource.getBiome(x and 0x0F, y, z and 0x0F)
}
@Deprecated("")
private fun updateNeighbours(neighbours: Array<Chunk>, 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)
}
}

View File

@ -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 <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 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 + 1) * 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,
;
}
}

View File

@ -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) : Heightmap {
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, state: BlockState?) = Unit
companion object {
val MAX_VALUE = FixedHeightmap(Int.MAX_VALUE)
}
}

View File

@ -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 Heightmap {
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?)
}

View File

@ -0,0 +1,62 @@
/*
* 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) : ChunkHeightmap(chunk) {
override fun recalculate() {
super.recalculate()
chunk.light.sky.calculate()
}
override fun onHeightmapUpdate(x: Int, z: Int, previous: Int, now: Int) {
if (previous > now) {
// block is lower
return chunk.light.sky.floodFill(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.sky.calculate()
}
override fun passes(state: BlockState): HeightmapPass {
val light = state.block.getLightProperties(state)
if (!light.skylightEnters) return HeightmapPass.ABOVE
if (light.filtersSkylight) return HeightmapPass.IN
if (!light.propagatesLight(Directions.DOWN)) return HeightmapPass.IN
return HeightmapPass.PASSES
}
}

View File

@ -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) {

View File

@ -17,32 +17,30 @@ 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.neighbours.ChunkNeighbours
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.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 = 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.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) {
return
}
val heightmapIndex = (z shl 4) or x
val previous = heightmap[heightmapIndex]
recalculateHeightmap(x, y, z, next)
onHeightmapUpdate(x, y, z, previous, heightmap[heightmapIndex])
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
@ -50,7 +48,7 @@ class ChunkLight(private val chunk: Chunk) {
}
private fun fireLightChange(section: ChunkSection, sectionHeight: Int, neighbours: Array<Chunk>, fireSameChunkEvent: Boolean = true) {
fun fireLightChange(section: ChunkSection, sectionHeight: Int, neighbours: Array<Chunk>, fireSameChunkEvent: Boolean = true) {
if (!section.light.update) {
return
}
@ -135,7 +133,7 @@ class ChunkLight(private val chunk: Chunk) {
}
section.light.recalculate()
}
calculateSkylight()
sky.calculate()
if (fireEvent) {
fireLightChange(sections, fireSameChunkEvent)
}
@ -149,7 +147,7 @@ class ChunkLight(private val chunk: Chunk) {
}
section.light.calculate()
}
calculateSkylight()
sky.calculate()
if (fireEvent) {
fireLightChange(sections, fireSameChunkEvent)
}
@ -180,245 +178,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() {
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<Chunk>, 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<Chunk>, 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]
}
)
}
private 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", 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]
}
}

View File

@ -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 <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.minosoft.data.registries.dimension.DimensionProperties
object ChunkLightUtil {
fun DimensionProperties.hasSkyLight(): Boolean {
return this.skyLight || this.effects.skyLight
}
}

View File

@ -0,0 +1,176 @@
/*
* 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.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
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 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, target, baseY + y, false)
}
section.light.update = true
}
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
// trace section after section
val topSection = topY.sectionHeight
val bottomSection = bottomY.sectionHeight
val sections = topSection - maxOf(chunk.minSection, bottomSection) + 1
// top section
if (sections > 1) {
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, 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, target)
if (bottomY == Int.MIN_VALUE) {
chunk.light.bottom.traceSkyIncrease(x, z, NEIGHBOUR_TRACE_LEVEL)
}
}
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<Chunk>, 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.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.WEST)
}
if (x < ProtocolDefinition.SECTION_MAX_X) {
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.EAST)
}
if (z > 0) {
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.NORTH)
}
if (z < ProtocolDefinition.SECTION_MAX_Z) {
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.SOUTH)
}
}
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) {
floodFill(neighbours, x, z)
}
}
}
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()
}
fun getNeighbourMinHeight(neighbours: Array<Chunk>, 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 companion object {
const val NEIGHBOUR_TRACE_LEVEL = ProtocolDefinition.MAX_LIGHT_LEVEL_I - 1
}
}

View File

@ -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
@ -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) {
@ -242,7 +245,7 @@ class SectionLight(
}
}
blocks.release()
section.chunk.light.recalculateSkylight(section.sectionHeight)
section.chunk.light.sky.recalculate(section.sectionHeight)
}
@ -275,11 +278,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 +293,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 +307,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 +365,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 (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 +416,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 +474,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 {

View File

@ -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<Chunk?> {
@ -100,6 +104,68 @@ class ChunkNeighbours(val chunk: Chunk) : Iterable<Chunk?> {
return neighbours.iterator()
}
fun update(neighbours: Array<Chunk>, 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

View File

@ -42,7 +42,7 @@ open class SectionDataProvider<T>(
if (data != null && calculateInitial) {
recalculate()
} else {
minPosition = Vec3i.EMPTY
minPosition = Vec3i(ProtocolDefinition.CHUNK_SECTION_SIZE)
maxPosition = Vec3i.EMPTY
}
}

View File

@ -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

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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++

View File

@ -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]

View File

@ -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