some light cleanup, datatype: light level (inlined)

This commit is contained in:
Moritz Zwerger 2025-03-08 14:37:32 +01:00
parent d6223cf873
commit f0d70af460
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
29 changed files with 289 additions and 175 deletions

View File

@ -31,7 +31,7 @@ import de.bixilon.minosoft.data.registries.identified.Namespaces.minosoft
import de.bixilon.minosoft.data.registries.shapes.voxel.AbstractVoxelShape import de.bixilon.minosoft.data.registries.shapes.voxel.AbstractVoxelShape
import de.bixilon.minosoft.data.world.World import de.bixilon.minosoft.data.world.World
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.light.ChunkLight import de.bixilon.minosoft.data.world.chunk.light.section.ChunkLight
import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours
import de.bixilon.minosoft.data.world.positions.ChunkPosition import de.bixilon.minosoft.data.world.positions.ChunkPosition
import de.bixilon.minosoft.data.world.positions.InSectionPosition import de.bixilon.minosoft.data.world.positions.InSectionPosition

View File

@ -22,7 +22,7 @@ import de.bixilon.minosoft.data.registries.dimension.DimensionProperties
import de.bixilon.minosoft.data.world.World import de.bixilon.minosoft.data.world.World
import de.bixilon.minosoft.data.world.biome.WorldBiomes import de.bixilon.minosoft.data.world.biome.WorldBiomes
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.light.ChunkLight import de.bixilon.minosoft.data.world.chunk.light.section.ChunkLight
import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbours
import de.bixilon.minosoft.data.world.positions.ChunkPosition import de.bixilon.minosoft.data.world.positions.ChunkPosition
import de.bixilon.minosoft.data.world.positions.InSectionPosition import de.bixilon.minosoft.data.world.positions.InSectionPosition

View File

@ -31,6 +31,7 @@ object DebugOptions {
const val FORCE_CHECK_UPDATES = false const val FORCE_CHECK_UPDATES = false
const val VERIFY_COORDINATES = true const val VERIFY_COORDINATES = true
const val VERIFY_LIGHT_LEVEL = true
// Add a test to ensure that all options are disabled!!! // Add a test to ensure that all options are disabled!!!
} }

View File

@ -1,6 +1,6 @@
/* /*
* Minosoft * Minosoft
* Copyright (C) 2020-2024 Moritz Zwerger * Copyright (C) 2020-2025 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 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.
* *
@ -22,8 +22,8 @@ import de.bixilon.minosoft.data.registries.identified.ResourceLocation
import de.bixilon.minosoft.data.registries.registries.Registries import de.bixilon.minosoft.data.registries.registries.Registries
import de.bixilon.minosoft.data.text.BaseComponent import de.bixilon.minosoft.data.text.BaseComponent
import de.bixilon.minosoft.data.text.ChatComponent import de.bixilon.minosoft.data.text.ChatComponent
import de.bixilon.minosoft.data.world.chunk.light.SectionLight.Companion.BLOCK_LIGHT_MASK import de.bixilon.minosoft.data.text.TextComponent
import de.bixilon.minosoft.data.world.chunk.light.SectionLight.Companion.SKY_LIGHT_MASK import de.bixilon.minosoft.data.text.formatting.color.ChatColors
import de.bixilon.minosoft.protocol.network.session.play.PlaySession import de.bixilon.minosoft.protocol.network.session.play.PlaySession
open class CropBlock(resourceLocation: ResourceLocation, registries: Registries, data: Map<String, Any>) : PlantBlock(resourceLocation, registries, data), BlockWawlaProvider { open class CropBlock(resourceLocation: ResourceLocation, registries: Registries, data: Map<String, Any>) : PlantBlock(resourceLocation, registries, data), BlockWawlaProvider {
@ -35,14 +35,11 @@ open class CropBlock(resourceLocation: ResourceLocation, registries: Registries,
override fun getWawlaInformation(session: PlaySession, target: BlockTarget): ChatComponent { override fun getWawlaInformation(session: PlaySession, target: BlockTarget): ChatComponent {
val light = session.world.getLight(target.blockPosition) val light = session.world.getLight(target.blockPosition)
val blockLight = light and BLOCK_LIGHT_MASK
val skyLight = (light and SKY_LIGHT_MASK) shr 4
val component = BaseComponent("Light: ") val component = BaseComponent("Light: ")
component += if (blockLight < MIN_LIGHT_LEVEL) "§4$blockLight§r" else "§a$blockLight§r" component += TextComponent(light.block).color(if (light.block < MIN_LIGHT_LEVEL) ChatColors.RED else ChatColors.GREEN)
component += " ($skyLight)" component += " (${light.sky})"
return component return component
} }

View File

@ -1,6 +1,6 @@
/* /*
* Minosoft * Minosoft
* Copyright (C) 2020-2023 Moritz Zwerger * Copyright (C) 2020-2025 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 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.
* *
@ -21,6 +21,7 @@ import de.bixilon.minosoft.data.text.TextComponent
import de.bixilon.minosoft.data.text.formatting.TextFormattable import de.bixilon.minosoft.data.text.formatting.TextFormattable
import org.checkerframework.common.value.qual.IntRange import org.checkerframework.common.value.qual.IntRange
// TODO: JvmInline value class
class RGBColor(val rgba: Int) : TextFormattable { class RGBColor(val rgba: Int) : TextFormattable {
val ansi: String get() = ANSI.rgb(red, green, blue) val ansi: String get() = ANSI.rgb(red, green, blue)

View File

@ -28,8 +28,8 @@ import de.bixilon.minosoft.data.world.audio.WorldAudioPlayer
import de.bixilon.minosoft.data.world.biome.WorldBiomes import de.bixilon.minosoft.data.world.biome.WorldBiomes
import de.bixilon.minosoft.data.world.border.WorldBorder import de.bixilon.minosoft.data.world.border.WorldBorder
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.light.ChunkLightUtil.hasSkyLight import de.bixilon.minosoft.data.world.chunk.light.section.ChunkLightUtil.hasSkyLight
import de.bixilon.minosoft.data.world.chunk.light.SectionLight import de.bixilon.minosoft.data.world.chunk.light.types.LightLevel
import de.bixilon.minosoft.data.world.chunk.manager.ChunkManager import de.bixilon.minosoft.data.world.chunk.manager.ChunkManager
import de.bixilon.minosoft.data.world.difficulty.WorldDifficulty import de.bixilon.minosoft.data.world.difficulty.WorldDifficulty
import de.bixilon.minosoft.data.world.entities.WorldEntities import de.bixilon.minosoft.data.world.entities.WorldEntities
@ -177,17 +177,17 @@ class World(
return !iterator.hasCollisions(entity, aabb, fluids) return !iterator.hasCollisions(entity, aabb, fluids)
} }
fun getLight(position: BlockPosition): Int { fun getLight(position: BlockPosition): LightLevel {
return chunks[position.chunkPosition]?.light?.get(position.inChunkPosition) ?: 0x00 return chunks[position.chunkPosition]?.light?.get(position.inChunkPosition) ?: LightLevel.EMPTY
} }
fun getBrightness(position: BlockPosition): Float { fun getBrightness(position: BlockPosition): Float {
val light = getLight(position) val level = getLight(position)
var level = light and SectionLight.BLOCK_LIGHT_MASK var max = level.block
if (dimension.hasSkyLight()) { if (dimension.hasSkyLight()) {
level = maxOf(level, light and SectionLight.SKY_LIGHT_MASK shr 4) max = maxOf(max, level.sky)
} }
return dimension.ambientLight[level] return dimension.ambientLight[max]
} }
fun recalculateLight(heightmap: Boolean = false) { fun recalculateLight(heightmap: Boolean = false) {

View File

@ -16,7 +16,7 @@ import de.bixilon.minosoft.data.direction.Directions
import de.bixilon.minosoft.data.entities.block.BlockEntity import de.bixilon.minosoft.data.entities.block.BlockEntity
import de.bixilon.minosoft.data.registries.blocks.state.BlockState import de.bixilon.minosoft.data.registries.blocks.state.BlockState
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.light.SectionLight import de.bixilon.minosoft.data.world.chunk.light.section.SectionLight
import de.bixilon.minosoft.data.world.container.SectionDataProvider import de.bixilon.minosoft.data.world.container.SectionDataProvider
import de.bixilon.minosoft.data.world.container.biome.BiomeSectionDataProvider import de.bixilon.minosoft.data.world.container.biome.BiomeSectionDataProvider
import de.bixilon.minosoft.data.world.container.block.BlockSectionDataProvider import de.bixilon.minosoft.data.world.container.block.BlockSectionDataProvider

View File

@ -21,7 +21,7 @@ import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.registries.blocks.types.entity.BlockWithEntity import de.bixilon.minosoft.data.registries.blocks.types.entity.BlockWithEntity
import de.bixilon.minosoft.data.world.biome.source.BiomeSource import de.bixilon.minosoft.data.world.biome.source.BiomeSource
import de.bixilon.minosoft.data.world.chunk.ChunkSection import de.bixilon.minosoft.data.world.chunk.ChunkSection
import de.bixilon.minosoft.data.world.chunk.light.ChunkLight import de.bixilon.minosoft.data.world.chunk.light.section.ChunkLight
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.block.ChunkLocalBlockUpdate 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.chunk.update.block.SingleBlockUpdate

View File

@ -19,7 +19,7 @@ import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.registries.blocks.types.entity.BlockWithEntity import de.bixilon.minosoft.data.registries.blocks.types.entity.BlockWithEntity
import de.bixilon.minosoft.data.world.biome.source.BiomeSource import de.bixilon.minosoft.data.world.biome.source.BiomeSource
import de.bixilon.minosoft.data.world.chunk.ChunkSection import de.bixilon.minosoft.data.world.chunk.ChunkSection
import de.bixilon.minosoft.data.world.chunk.light.LightArray import de.bixilon.minosoft.data.world.chunk.light.types.LightArray
import de.bixilon.minosoft.data.world.positions.ChunkPosition import de.bixilon.minosoft.data.world.positions.ChunkPosition
import de.bixilon.minosoft.data.world.positions.InChunkPosition import de.bixilon.minosoft.data.world.positions.InChunkPosition
import de.bixilon.minosoft.data.world.positions.InSectionPosition import de.bixilon.minosoft.data.world.positions.InSectionPosition

View File

@ -0,0 +1,24 @@
/*
* Minosoft
* Copyright (C) 2020-2025 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.config.DebugOptions
object LightUtil {
inline fun assertLight(condition: Boolean) {
if (!DebugOptions.VERIFY_LIGHT_LEVEL) return
if (!condition) throw AssertionError("Invalid light level!")
}
}

View File

@ -11,13 +11,14 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft. * This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/ */
package de.bixilon.minosoft.data.world.chunk.light package de.bixilon.minosoft.data.world.chunk.light.section
import de.bixilon.minosoft.data.world.chunk.light.types.LightLevel
import de.bixilon.minosoft.data.world.positions.InSectionPosition import de.bixilon.minosoft.data.world.positions.InSectionPosition
abstract class AbstractSectionLight { abstract class AbstractSectionLight {
open var update = false open var update = false
abstract operator fun get(position: InSectionPosition): Byte abstract operator fun get(position: InSectionPosition): LightLevel
} }

View File

@ -11,7 +11,7 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft. * This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/ */
package de.bixilon.minosoft.data.world.chunk.light package de.bixilon.minosoft.data.world.chunk.light.section
import de.bixilon.minosoft.data.direction.Directions import de.bixilon.minosoft.data.direction.Directions
import de.bixilon.minosoft.data.registries.blocks.state.BlockState import de.bixilon.minosoft.data.registries.blocks.state.BlockState
@ -19,7 +19,10 @@ 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.FixedHeightmap
import de.bixilon.minosoft.data.world.chunk.heightmap.LightHeightmap 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.light.section.ChunkLightUtil.hasSkyLight
import de.bixilon.minosoft.data.world.chunk.light.section.border.BottomSectionLight
import de.bixilon.minosoft.data.world.chunk.light.section.border.TopSectionLight
import de.bixilon.minosoft.data.world.chunk.light.types.LightLevel
import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbourArray import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbourArray
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
@ -31,8 +34,8 @@ class ChunkLight(val chunk: Chunk) {
private val session = chunk.session private val session = chunk.session
val heightmap = if (chunk.world.dimension.hasSkyLight()) 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 bottom = BottomSectionLight(chunk)
val top = BorderSectionLight(true, chunk) val top = TopSectionLight(chunk)
val sky = ChunkSkyLight(this) val sky = ChunkSkyLight(this)
@ -101,19 +104,19 @@ class ChunkLight(val chunk: Chunk) {
} }
operator fun get(position: InChunkPosition): Int { operator fun get(position: InChunkPosition): LightLevel {
val sectionHeight = position.sectionHeight val sectionHeight = position.sectionHeight
val inSection = position.inSectionPosition val inSection = position.inSectionPosition
val light = when (sectionHeight) { val light = when (sectionHeight) {
chunk.minSection - 1 -> bottom[inSection].toInt() chunk.minSection - 1 -> bottom[inSection]
chunk.maxSection + 1 -> return top[inSection].toInt() or SectionLight.SKY_LIGHT_MASK // top has always sky=15 chunk.maxSection + 1 -> return top[inSection].with(sky = LightLevel.MAX_LEVEL) // top has always sky=15; TODO: only if dimension has skylight?
else -> chunk[sectionHeight]?.light?.get(inSection)?.toInt() ?: 0x00 else -> chunk[sectionHeight]?.light?.get(inSection) ?: LightLevel.EMPTY
} and 0xFF }
if (position.y >= heightmap[position]) { if (position.y >= heightmap[position]) {
// set sky=15 // set sky=15
return light or SectionLight.SKY_LIGHT_MASK return light.with(sky = LightLevel.MAX_LEVEL)
} }
return light return light
} }

View File

@ -1,6 +1,6 @@
/* /*
* Minosoft * Minosoft
* Copyright (C) 2020-2023 Moritz Zwerger * Copyright (C) 2020-2025 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 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.
* *
@ -11,7 +11,7 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft. * This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/ */
package de.bixilon.minosoft.data.world.chunk.light package de.bixilon.minosoft.data.world.chunk.light.section
import de.bixilon.minosoft.data.registries.dimension.DimensionProperties import de.bixilon.minosoft.data.registries.dimension.DimensionProperties

View File

@ -11,10 +11,10 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft. * This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/ */
package de.bixilon.minosoft.data.world.chunk.light package de.bixilon.minosoft.data.world.chunk.light.section
import de.bixilon.minosoft.data.direction.Directions import de.bixilon.minosoft.data.direction.Directions
import de.bixilon.minosoft.data.world.chunk.light.ChunkLightUtil.hasSkyLight import de.bixilon.minosoft.data.world.chunk.light.section.ChunkLightUtil.hasSkyLight
import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbourArray import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbourArray
import de.bixilon.minosoft.data.world.positions.InSectionPosition import de.bixilon.minosoft.data.world.positions.InSectionPosition
import de.bixilon.minosoft.data.world.positions.SectionHeight import de.bixilon.minosoft.data.world.positions.SectionHeight

View File

@ -11,13 +11,15 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft. * This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/ */
package de.bixilon.minosoft.data.world.chunk.light package de.bixilon.minosoft.data.world.chunk.light.section
import de.bixilon.minosoft.data.direction.Directions import de.bixilon.minosoft.data.direction.Directions
import de.bixilon.minosoft.data.registries.blocks.light.TransparentProperty import de.bixilon.minosoft.data.registries.blocks.light.TransparentProperty
import de.bixilon.minosoft.data.registries.blocks.state.BlockState import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.world.chunk.ChunkSection import de.bixilon.minosoft.data.world.chunk.ChunkSection
import de.bixilon.minosoft.data.world.chunk.light.ChunkSkyLight.Companion.NEIGHBOUR_TRACE_LEVEL import de.bixilon.minosoft.data.world.chunk.light.section.ChunkSkyLight.Companion.NEIGHBOUR_TRACE_LEVEL
import de.bixilon.minosoft.data.world.chunk.light.types.LightArray
import de.bixilon.minosoft.data.world.chunk.light.types.LightLevel
import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbourArray import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbourArray
import de.bixilon.minosoft.data.world.positions.InSectionPosition import de.bixilon.minosoft.data.world.positions.InSectionPosition
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
@ -54,7 +56,7 @@ class SectionLight(
private fun startDecreaseTrace(position: InSectionPosition) { private fun startDecreaseTrace(position: InSectionPosition) {
// that is kind of hacky, but far easier and kind of faster // that is kind of hacky, but far easier and kind of faster
val light = this.light[position].toInt() and BLOCK_LIGHT_MASK val light = this.light[position].block
decreaseLight(position, light, true) // just clear the light decreaseLight(position, light, true) // just clear the light
decreaseLight(position, light, false) // increase the light in all sections decreaseLight(position, light, false) // increase the light in all sections
@ -132,13 +134,13 @@ class SectionLight(
} }
// get block or next luminance level // get block or next luminance level
val blockSkyLight = this.light[position].toInt() val level = this.light[position]
val currentLight = blockSkyLight and BLOCK_LIGHT_MASK // we just care about block light val currentLight = level.block // we just care about block light
if (currentLight >= nextLuminance) { if (currentLight >= nextLuminance) {
// light is already higher, no need to trace // light is already higher, no need to trace
return return
} }
this.light[position] = ((blockSkyLight and SKY_LIGHT_MASK) or nextLuminance) // keep the sky light set this.light[position] = level.with(block = nextLuminance) // keep the sky light set
if (!update) { if (!update) {
update = true update = true
} }
@ -269,13 +271,13 @@ class SectionLight(
for (y in 0 until ProtocolDefinition.SECTION_HEIGHT_Y) { for (y in 0 until ProtocolDefinition.SECTION_HEIGHT_Y) {
for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) { for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) {
val totalY = baseY + y val totalY = baseY + y
neighbours[Directions.O_WEST]?.light?.get(InSectionPosition(ProtocolDefinition.SECTION_MAX_Z, y, z))?.toInt()?.let { light -> neighbours[Directions.O_WEST]?.light?.get(InSectionPosition(ProtocolDefinition.SECTION_MAX_Z, y, z))?.let { light ->
(light and BLOCK_LIGHT_MASK).let { if (it > 1) traceBlockIncrease(InSectionPosition(0, y, z), it - 1, Directions.EAST) } light.block.let { if (it > 1) traceBlockIncrease(InSectionPosition(0, y, z), it - 1, Directions.EAST) }
(light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkyLightIncrease(InSectionPosition(0, y, z), it - 1, Directions.EAST, totalY) } light.sky.let { if (it > 1) traceSkyLightIncrease(InSectionPosition(0, y, z), it - 1, Directions.EAST, totalY) }
} }
neighbours[Directions.O_EAST]?.light?.get(InSectionPosition(0, y, z))?.toInt()?.let { light -> neighbours[Directions.O_EAST]?.light?.get(InSectionPosition(0, y, z))?.let { light ->
(light and BLOCK_LIGHT_MASK).let { if (it > 1) traceBlockIncrease(InSectionPosition(ProtocolDefinition.SECTION_MAX_X, y, z), it - 1, Directions.WEST) } light.block.let { if (it > 1) traceBlockIncrease(InSectionPosition(ProtocolDefinition.SECTION_MAX_X, y, z), it - 1, Directions.WEST) }
(light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkyLightIncrease(InSectionPosition(ProtocolDefinition.SECTION_MAX_X, y, z), it - 1, Directions.WEST, totalY) } light.sky.let { if (it > 1) traceSkyLightIncrease(InSectionPosition(ProtocolDefinition.SECTION_MAX_X, y, z), it - 1, Directions.WEST, totalY) }
} }
} }
} }
@ -284,13 +286,13 @@ class SectionLight(
private fun propagateZ(baseY: Int, neighbours: Array<ChunkSection?>, x: Int) { private fun propagateZ(baseY: Int, neighbours: Array<ChunkSection?>, x: Int) {
for (y in 0 until ProtocolDefinition.SECTION_HEIGHT_Y) { for (y in 0 until ProtocolDefinition.SECTION_HEIGHT_Y) {
val totalY = baseY + y val totalY = baseY + y
neighbours[Directions.O_NORTH]?.light?.get(InSectionPosition(x, y, ProtocolDefinition.SECTION_MAX_Z))?.toInt()?.let { light -> neighbours[Directions.O_NORTH]?.light?.get(InSectionPosition(x, y, ProtocolDefinition.SECTION_MAX_Z))?.let { light ->
(light and BLOCK_LIGHT_MASK).let { if (it > 1) traceBlockIncrease(InSectionPosition(x, y, 0), it - 1, Directions.SOUTH) } light.block.let { if (it > 1) traceBlockIncrease(InSectionPosition(x, y, 0), it - 1, Directions.SOUTH) }
(light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkyLightIncrease(InSectionPosition(x, y, 0), it - 1, Directions.SOUTH, totalY) } light.sky.let { if (it > 1) traceSkyLightIncrease(InSectionPosition(x, y, 0), it - 1, Directions.SOUTH, totalY) }
} }
neighbours[Directions.O_SOUTH]?.light?.get(InSectionPosition(x, y, 0))?.toInt()?.let { light -> neighbours[Directions.O_SOUTH]?.light?.get(InSectionPosition(x, y, 0))?.let { light ->
(light and BLOCK_LIGHT_MASK).let { if (it > 1) traceBlockIncrease(InSectionPosition(x, y, ProtocolDefinition.SECTION_MAX_Z), it - 1, Directions.NORTH) } light.block.let { if (it > 1) traceBlockIncrease(InSectionPosition(x, y, ProtocolDefinition.SECTION_MAX_Z), it - 1, Directions.NORTH) }
(light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkyLightIncrease(InSectionPosition(x, y, ProtocolDefinition.SECTION_MAX_Z), it - 1, Directions.NORTH, totalY) } light.sky.let { if (it > 1) traceSkyLightIncrease(InSectionPosition(x, y, ProtocolDefinition.SECTION_MAX_Z), it - 1, Directions.NORTH, totalY) }
} }
} }
} }
@ -298,13 +300,13 @@ class SectionLight(
private fun propagateY(neighbours: Array<ChunkSection?>, x: Int, baseY: Int) { private fun propagateY(neighbours: Array<ChunkSection?>, x: Int, baseY: Int) {
// ToDo: Border light // ToDo: Border light
for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) { for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) {
neighbours[Directions.O_DOWN]?.light?.get(InSectionPosition(x, ProtocolDefinition.SECTION_MAX_Y, z))?.toInt()?.let { light -> neighbours[Directions.O_DOWN]?.light?.get(InSectionPosition(x, ProtocolDefinition.SECTION_MAX_Y, z))?.let { light ->
(light and BLOCK_LIGHT_MASK).let { if (it > 1) traceBlockIncrease(InSectionPosition(x, 0, z), it - 1, Directions.UP) } light.block.let { if (it > 1) traceBlockIncrease(InSectionPosition(x, 0, z), it - 1, Directions.UP) }
(light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkyLightIncrease(InSectionPosition(x, 0, z), it - 1, Directions.UP, baseY + 0) } // ToDo: Is that possible? light.sky.let { if (it > 1) traceSkyLightIncrease(InSectionPosition(x, 0, z), it - 1, Directions.UP, baseY + 0) } // ToDo: Is that possible?
} }
neighbours[Directions.O_UP]?.light?.get(InSectionPosition(x, 0, z))?.toInt()?.let { light -> neighbours[Directions.O_UP]?.light?.get(InSectionPosition(x, 0, z))?.let { light ->
(light and BLOCK_LIGHT_MASK).let { if (it > 1) traceBlockIncrease(InSectionPosition(x, ProtocolDefinition.SECTION_MAX_Y, z), it - 1, Directions.DOWN) } light.block.let { if (it > 1) traceBlockIncrease(InSectionPosition(x, ProtocolDefinition.SECTION_MAX_Y, z), it - 1, Directions.DOWN) }
(light and SKY_LIGHT_MASK shr 4).let { if (it > 1) traceSkyLightIncrease(InSectionPosition(x, ProtocolDefinition.SECTION_MAX_Y, z), it - 1, Directions.DOWN, baseY + ProtocolDefinition.SECTION_MAX_Y) } light.sky.let { if (it > 1) traceSkyLightIncrease(InSectionPosition(x, ProtocolDefinition.SECTION_MAX_Y, z), it - 1, Directions.DOWN, baseY + ProtocolDefinition.SECTION_MAX_Y) }
} }
} }
} }
@ -317,8 +319,8 @@ class SectionLight(
return return
} }
val chunkNeighbours = chunk.neighbours.neighbours val chunkNeighbours = chunk.neighbours.neighbours
val currentLight = this[position].toInt() val light = this[position]
if (((currentLight and SKY_LIGHT_MASK) shr 4) >= nextLevel) { if (light.sky >= nextLevel) {
return return
} }
@ -333,7 +335,7 @@ class SectionLight(
val neighbours = this.section.neighbours ?: return val neighbours = this.section.neighbours ?: return
this.light[position] = ((currentLight and BLOCK_LIGHT_MASK) or (nextLevel shl 4)).toByte() this.light[position] = light.with(sky = nextLevel)
if (!update) { if (!update) {
update = true update = true
@ -411,7 +413,7 @@ class SectionLight(
val neighbours = this.section.neighbours ?: return val neighbours = this.section.neighbours ?: return
this.light[position] = ((this[position].toInt() and BLOCK_LIGHT_MASK) or (ProtocolDefinition.MAX_LIGHT_LEVEL_I shl 4)).toByte() this.light[position] = this.light[position].with(sky = ProtocolDefinition.MAX_LIGHT_LEVEL_I)
if (!update) { if (!update) {
update = true update = true
@ -434,59 +436,46 @@ class SectionLight(
fun propagateFromNeighbours(position: InSectionPosition) { fun propagateFromNeighbours(position: InSectionPosition) {
val neighbours = section.neighbours ?: return val neighbours = section.neighbours ?: return
// TODO: those 2 values are boxed in wrapper classes (slow!) var level = LightLevel(0, 0)
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 nextBlockLight = light.toInt() and BLOCK_LIGHT_MASK
if (nextBlockLight > blockLight) {
blockLight = nextBlockLight
}
}
// ToDo: check if light can exit block at side or can enter block at neighbour // ToDo: check if light can exit block at side or can enter block at neighbour
if (position.x > 0) { if (position.x > 0) {
pushLight(this[position.minusX()]) level = level.max(this[position.minusX()])
} else { } else {
neighbours[Directions.O_WEST]?.light?.get(position.with(x = ProtocolDefinition.SECTION_MAX_X))?.let { pushLight(it) } neighbours[Directions.O_WEST]?.light?.get(position.with(x = ProtocolDefinition.SECTION_MAX_X))?.let { level = level.max(it) }
} }
if (position.x < ProtocolDefinition.SECTION_MAX_X) { if (position.x < ProtocolDefinition.SECTION_MAX_X) {
pushLight(this[position.plusX()]) level = level.max(this[position.plusX()])
} else { } else {
neighbours[Directions.O_EAST]?.light?.get(position.with(x = 0))?.let { pushLight(it) } neighbours[Directions.O_EAST]?.light?.get(position.with(x = 0))?.let { level = level.max(it) }
} }
if (position.y > 0) { if (position.y > 0) {
pushLight(this[position.minusY()]) level = level.max(this[position.minusY()])
} else { } else {
neighbours[Directions.O_DOWN]?.light?.get(position.with(y = ProtocolDefinition.SECTION_MAX_Y))?.let { pushLight(it) } neighbours[Directions.O_DOWN]?.light?.get(position.with(y = ProtocolDefinition.SECTION_MAX_Y))?.let { level = level.max(it) }
} }
if (position.y < ProtocolDefinition.SECTION_MAX_Y) { if (position.y < ProtocolDefinition.SECTION_MAX_Y) {
pushLight(this[position.plusY()]) level = level.max(this[position.plusY()])
} else { } else {
neighbours[Directions.O_UP]?.light?.get(position.with(y = 0))?.let { pushLight(it) } neighbours[Directions.O_UP]?.light?.get(position.with(y = 0))?.let { level = level.max(it) }
} }
if (position.z > 0) { if (position.z > 0) {
pushLight(this[position.minusZ()]) level = level.max(this[position.minusZ()])
} else { } else {
neighbours[Directions.O_NORTH]?.light?.get(position.with(z = ProtocolDefinition.SECTION_MAX_Z))?.let { pushLight(it) } neighbours[Directions.O_NORTH]?.light?.get(position.with(z = ProtocolDefinition.SECTION_MAX_Z))?.let { level = level.max(it) }
} }
if (position.z < ProtocolDefinition.SECTION_MAX_Z) { if (position.z < ProtocolDefinition.SECTION_MAX_Z) {
pushLight(this[position.plusZ()]) level = level.max(this[position.plusZ()])
} else { } else {
neighbours[Directions.O_SOUTH]?.light?.get(position.with(z = 0))?.let { pushLight(it) } neighbours[Directions.O_SOUTH]?.light?.get(position.with(z = 0))?.let { level = level.max(it) }
} }
traceBlockIncrease(position, blockLight - 1, null) traceBlockIncrease(position, level.block - 1, null)
val totalY = section.height * ProtocolDefinition.SECTION_HEIGHT_Y + position.y val totalY = section.height * ProtocolDefinition.SECTION_HEIGHT_Y + position.y
section.chunk.let { section.chunk.let {
@ -495,16 +484,11 @@ class SectionLight(
if (!it.neighbours.complete) return@let if (!it.neighbours.complete) return@let
val minHeight = it.light.sky.getNeighbourMinHeight(chunkNeighbours, position.x, position.z) val minHeight = it.light.sky.getNeighbourMinHeight(chunkNeighbours, position.x, position.z)
if (totalY > minHeight) { if (totalY > minHeight) {
skyLight = ProtocolDefinition.MAX_LIGHT_LEVEL_I level = level.with(sky = ProtocolDefinition.MAX_LIGHT_LEVEL_I)
} }
} }
traceSkyLightIncrease(position, skyLight - 1, null, totalY) traceSkyLightIncrease(position, level.sky - 1, null, totalY)
} }
override fun get(position: InSectionPosition) = light[position] override fun get(position: InSectionPosition) = light[position]
companion object {
const val BLOCK_LIGHT_MASK = 0x0F
const val SKY_LIGHT_MASK = 0xF0
}
} }

View File

@ -11,45 +11,47 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft. * This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/ */
package de.bixilon.minosoft.data.world.chunk.light package de.bixilon.minosoft.data.world.chunk.light.section.border
import de.bixilon.kutil.array.ArrayUtil.getFirst import de.bixilon.kutil.array.ArrayUtil.getFirst
import de.bixilon.kutil.array.ArrayUtil.getLast import de.bixilon.kutil.array.ArrayUtil.getLast
import de.bixilon.minosoft.data.direction.Directions import de.bixilon.minosoft.data.direction.Directions
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.light.section.AbstractSectionLight
import de.bixilon.minosoft.data.world.chunk.light.types.LightArray
import de.bixilon.minosoft.data.world.chunk.light.types.LightLevel
import de.bixilon.minosoft.data.world.positions.InSectionPosition import de.bixilon.minosoft.data.world.positions.InSectionPosition
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import java.util.*
class BorderSectionLight( abstract class BorderSectionLight(
val top: Boolean,
val chunk: Chunk, val chunk: Chunk,
) : AbstractSectionLight() { ) : AbstractSectionLight() {
val light = ByteArray(ProtocolDefinition.SECTION_WIDTH_X * ProtocolDefinition.SECTION_WIDTH_Z) val light = ByteArray(ProtocolDefinition.SECTION_WIDTH_X * ProtocolDefinition.SECTION_WIDTH_Z)
override fun get(position: InSectionPosition): Byte { protected abstract fun getNearestSection(): ChunkSection?
if ((top && position.y == 0) || (!top && position.y == ProtocolDefinition.SECTION_MAX_Y)) { protected abstract fun Chunk.getBorderLight(): BorderSectionLight
return light[position.xz]
} protected inline operator fun get(index: Int) = LightLevel(this.light[index])
return 0x00 protected inline operator fun set(index: Int, value: LightLevel) {
this.light[index] = value.index
} }
private fun updateY() { private fun updateY() {
// we can not further increase the light // we can not further increase the light
if (top) { val section = getNearestSection()
chunk.sections.getLast()?.light?.apply { if (!update) update = true } section?.light?.apply { if (!update) update = true }
} else {
chunk.sections.getFirst()?.light?.apply { if (!update) update = true }
}
} }
fun traceBlockIncrease(x: Int, z: Int, nextLuminance: Int) { fun traceBlockIncrease(x: Int, z: Int, nextLuminance: Int) {
val index = z shl 4 or x val index = z shl 4 or x
val currentLight = light[index].toInt() and SectionLight.BLOCK_LIGHT_MASK val currentLight = this[index]
if (currentLight >= nextLuminance) { if (currentLight.block >= nextLuminance) {
// light is already higher, no need to trace // light is already higher, no need to trace
return return
} }
this.light[index] = ((this.light[index].toInt() and SectionLight.SKY_LIGHT_MASK) or nextLuminance).toByte() this[index] = currentLight.with(block = nextLuminance)
if (!update) { if (!update) {
update = true update = true
@ -61,7 +63,7 @@ class BorderSectionLight(
} }
val neighbourLuminance = nextLuminance - 1 val neighbourLuminance = nextLuminance - 1
if (top) { if (this is TopSectionLight) { // TODO: slow check
chunk.sections.getLast()?.light?.traceBlockIncrease(InSectionPosition(x, ProtocolDefinition.SECTION_MAX_Y, z), neighbourLuminance, Directions.DOWN) chunk.sections.getLast()?.light?.traceBlockIncrease(InSectionPosition(x, ProtocolDefinition.SECTION_MAX_Y, z), neighbourLuminance, Directions.DOWN)
} else { } else {
chunk.sections.getFirst()?.light?.traceBlockIncrease(InSectionPosition(x, 0, z), neighbourLuminance, Directions.UP) chunk.sections.getFirst()?.light?.traceBlockIncrease(InSectionPosition(x, 0, z), neighbourLuminance, Directions.UP)
@ -90,19 +92,15 @@ class BorderSectionLight(
} }
} }
private fun Chunk.getBorderLight(): BorderSectionLight {
return if (top) light.top else light.bottom
}
fun traceSkyIncrease(x: Int, z: Int, nextLevel: Int) { fun traceSkyIncrease(x: Int, z: Int, nextLevel: Int) {
// TODO: check heightmap // TODO: check heightmap
val index = z shl 4 or x val index = z shl 4 or x
val light = light[index].toInt() val light = this[index]
if ((light and SectionLight.SKY_LIGHT_MASK shr 4) >= nextLevel) { if (light.sky >= nextLevel) {
// light is already higher, no need to trace // light is already higher, no need to trace
return return
} }
this.light[index] = ((light and SectionLight.BLOCK_LIGHT_MASK) or (nextLevel shl 4)).toByte() this[index] = light.with(sky = nextLevel)
if (!update) { if (!update) {
update = true update = true
@ -114,7 +112,7 @@ class BorderSectionLight(
} }
val neighbourLevel = nextLevel - 1 val neighbourLevel = nextLevel - 1
if (top) { if (this is TopSectionLight) { // TOOD: slow check
chunk.sections.getLast()?.light?.traceSkyLightIncrease(InSectionPosition(x, ProtocolDefinition.SECTION_MAX_Y, z), neighbourLevel, Directions.DOWN, chunk.maxSection * ProtocolDefinition.SECTION_HEIGHT_Y + ProtocolDefinition.SECTION_MAX_Y) chunk.sections.getLast()?.light?.traceSkyLightIncrease(InSectionPosition(x, ProtocolDefinition.SECTION_MAX_Y, z), neighbourLevel, Directions.DOWN, chunk.maxSection * ProtocolDefinition.SECTION_HEIGHT_Y + ProtocolDefinition.SECTION_MAX_Y)
} else { } else {
chunk.sections.getFirst()?.light?.traceSkyLightIncrease(InSectionPosition(x, 0, z), neighbourLevel, Directions.UP, chunk.minSection * ProtocolDefinition.SECTION_HEIGHT_Y) chunk.sections.getFirst()?.light?.traceSkyLightIncrease(InSectionPosition(x, 0, z), neighbourLevel, Directions.UP, chunk.minSection * ProtocolDefinition.SECTION_HEIGHT_Y)
@ -170,9 +168,7 @@ class BorderSectionLight(
} }
fun reset() { fun reset() {
for (i in light.indices) { Arrays.fill(this.light, 0x00)
light[i] = 0x00
}
} }
fun update(array: LightArray) { fun update(array: LightArray) {

View File

@ -0,0 +1,33 @@
/*
* Minosoft
* Copyright (C) 2020-2025 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.section.border
import de.bixilon.kutil.array.ArrayUtil.getFirst
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
import de.bixilon.minosoft.data.world.chunk.light.types.LightLevel
import de.bixilon.minosoft.data.world.positions.InSectionPosition
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
class BottomSectionLight(
chunk: Chunk,
) : BorderSectionLight(chunk) {
override fun get(position: InSectionPosition): LightLevel {
if (position.y != ProtocolDefinition.SECTION_MAX_Y) return LightLevel.EMPTY
return LightLevel(this.light[position.xz])
}
override fun getNearestSection() = chunk.sections.getFirst()
override fun Chunk.getBorderLight() = this.light.bottom
}

View File

@ -0,0 +1,32 @@
/*
* Minosoft
* Copyright (C) 2020-2025 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.section.border
import de.bixilon.kutil.array.ArrayUtil.getLast
import de.bixilon.minosoft.data.world.chunk.chunk.Chunk
import de.bixilon.minosoft.data.world.chunk.light.types.LightLevel
import de.bixilon.minosoft.data.world.positions.InSectionPosition
class TopSectionLight(
chunk: Chunk,
) : BorderSectionLight(chunk) {
override fun get(position: InSectionPosition): LightLevel {
if (position.y != 0) return LightLevel.EMPTY
return LightLevel(this.light[position.xz])
}
override fun getNearestSection() = chunk.sections.getLast()
override fun Chunk.getBorderLight() = this.light.top
}

View File

@ -11,7 +11,7 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft. * This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/ */
package de.bixilon.minosoft.data.world.chunk.light package de.bixilon.minosoft.data.world.chunk.light.types
import de.bixilon.minosoft.data.world.positions.InSectionPosition import de.bixilon.minosoft.data.world.positions.InSectionPosition
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
@ -20,13 +20,9 @@ import java.util.*
@JvmInline @JvmInline
value class LightArray(inline val array: ByteArray = ByteArray(ProtocolDefinition.BLOCKS_PER_SECTION)) { value class LightArray(inline val array: ByteArray = ByteArray(ProtocolDefinition.BLOCKS_PER_SECTION)) {
inline operator fun get(position: InSectionPosition) = array[position.index] inline operator fun get(position: InSectionPosition) = LightLevel(array[position.index])
inline operator fun set(position: InSectionPosition, value: Byte) { inline operator fun set(position: InSectionPosition, value: LightLevel) {
array[position.index] = value array[position.index] = value.index
}
inline operator fun set(position: InSectionPosition, value: Int) {
array[position.index] = value.toByte()
} }
inline fun clear() = Arrays.fill(array, 0.toByte()) inline fun clear() = Arrays.fill(array, 0.toByte())

View File

@ -0,0 +1,51 @@
/*
* Minosoft
* Copyright (C) 2020-2025 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.types
import de.bixilon.minosoft.data.world.chunk.light.LightUtil.assertLight
@JvmInline
value class LightLevel(val index: Byte) {
constructor(block: Int, sky: Int) : this(((block shl BLOCK_SHIFT) or (sky shl SKY_SHIFT)).toByte()) {
assertLight(block >= MIN_LEVEL)
assertLight(block <= MAX_LEVEL)
assertLight(sky >= MIN_LEVEL)
assertLight(sky <= MAX_LEVEL)
}
inline val block: Int get() = (index.toInt() ushr BLOCK_SHIFT) and BLOCK_MASK
inline val sky: Int get() = (index.toInt() ushr SKY_SHIFT) and SKY_MASK
inline fun with(block: Int = this.block, sky: Int = this.sky) = LightLevel(block, sky)
inline fun max(other: LightLevel) = LightLevel(maxOf(block, other.block), maxOf(sky, other.sky))
companion object {
const val BLOCK_SHIFT = 0
const val BLOCK_MASK = 0x0F
const val SKY_SHIFT = 4
const val SKY_MASK = 0x0F
const val MIN_LEVEL = 0
const val MAX_LEVEL = 15
val EMPTY = LightLevel(0, 0)
val MAX = LightLevel(0, 0)
}
}

View File

@ -162,7 +162,7 @@ class FluidSectionMesher(
val light = chunk.light[InChunkPosition(x, position.y, z)] val light = chunk.light[InChunkPosition(x, position.y, z)]
addFluidVertices(meshToUse, positions, texturePositions, texture, tint, light) addFluidVertices(meshToUse, positions, texturePositions, texture, tint, light.index.toInt())
rendered = true rendered = true
} }
// ToDo: Sides: Minecraft uses (for water) an overlay texture (with cullface) that is used, when the face fits to a non opaque block // ToDo: Sides: Minecraft uses (for water) an overlay texture (with cullface) that is used, when the face fits to a non opaque block
@ -226,7 +226,7 @@ class FluidSectionMesher(
val meshToUse = mesh[model.flowing.transparency] val meshToUse = mesh[model.flowing.transparency]
val fluidLight = chunk.light[InChunkPosition(x, offsetY + y, z)] val fluidLight = chunk.light[InChunkPosition(x, offsetY + y, z)]
addFluidVertices(meshToUse, positions, texturePositions, model.flowing, tint, fluidLight) addFluidVertices(meshToUse, positions, texturePositions, model.flowing, tint, fluidLight.index.toInt())
rendered = true rendered = true
} }

View File

@ -28,7 +28,7 @@ import de.bixilon.minosoft.data.registries.blocks.types.fluid.FluidBlock
import de.bixilon.minosoft.data.registries.blocks.types.properties.offset.OffsetBlock import de.bixilon.minosoft.data.registries.blocks.types.properties.offset.OffsetBlock
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.light.SectionLight.Companion.SKY_LIGHT_MASK import de.bixilon.minosoft.data.world.chunk.light.types.LightLevel
import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbourArray import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbourArray
import de.bixilon.minosoft.data.world.positions.BlockPosition import de.bixilon.minosoft.data.world.positions.BlockPosition
import de.bixilon.minosoft.data.world.positions.InChunkPosition import de.bixilon.minosoft.data.world.positions.InChunkPosition
@ -109,15 +109,15 @@ class SolidSectionMesher(
setX(neighbourBlocks, inChunk, neighbours, light, neighbourChunks, section, chunk) setX(neighbourBlocks, inChunk, neighbours, light, neighbourChunks, section, chunk)
val maxHeight = chunk.light.heightmap[inSection.xz] val maxHeight = chunk.light.heightmap[inSection.xz]
light[SELF_LIGHT_INDEX] = section.light[inSection] light[SELF_LIGHT_INDEX] = section.light[inSection].index
if (position.y > maxHeight) { if (position.y > maxHeight) {
light[O_UP] = (light[O_UP].toInt() or SKY_LIGHT_MASK).toByte() light[O_UP] = (light[O_UP].toInt() or MAX_SKY_LIGHT).toByte()
} }
if (position.y >= maxHeight) { if (position.y >= maxHeight) {
light[SELF_LIGHT_INDEX] = (light[SELF_LIGHT_INDEX].toInt() or SKY_LIGHT_MASK).toByte() light[SELF_LIGHT_INDEX] = (light[SELF_LIGHT_INDEX].toInt() or MAX_SKY_LIGHT).toByte()
} }
if (position.y - 1 >= maxHeight) { if (position.y - 1 >= maxHeight) {
light[O_DOWN] = (light[O_DOWN].toInt() or SKY_LIGHT_MASK).toByte() light[O_DOWN] = (light[O_DOWN].toInt() or MAX_SKY_LIGHT).toByte()
} }
// TODO: cull neighbours // TODO: cull neighbours
@ -154,21 +154,21 @@ class SolidSectionMesher(
neighbourBlocks[O_DOWN] = bedrock neighbourBlocks[O_DOWN] = bedrock
} else { } else {
neighbourBlocks[O_DOWN] = neighbours[O_DOWN]?.blocks?.let { it[position.with(y = ProtocolDefinition.SECTION_MAX_Y)] } neighbourBlocks[O_DOWN] = neighbours[O_DOWN]?.blocks?.let { it[position.with(y = ProtocolDefinition.SECTION_MAX_Y)] }
light[O_DOWN] = (if (lowest) chunk.light.bottom else neighbours[O_DOWN]?.light)?.get(position.with(y = ProtocolDefinition.SECTION_MAX_Y)) ?: 0x00 light[O_DOWN] = (if (lowest) chunk.light.bottom else neighbours[O_DOWN]?.light)?.get(position.with(y = ProtocolDefinition.SECTION_MAX_Y))?.index ?: 0x00
} }
} else { } else {
neighbourBlocks[O_DOWN] = section.blocks[position.minusY()] neighbourBlocks[O_DOWN] = section.blocks[position.minusY()]
light[O_DOWN] = section.light[position.minusY()] light[O_DOWN] = section.light[position.minusY()].index
} }
} }
fun checkUp(highest: Boolean, position: InSectionPosition, neighbourBlocks: Array<BlockState?>, neighbours: Array<ChunkSection?>, light: ByteArray, section: ChunkSection, chunk: Chunk) { fun checkUp(highest: Boolean, position: InSectionPosition, neighbourBlocks: Array<BlockState?>, neighbours: Array<ChunkSection?>, light: ByteArray, section: ChunkSection, chunk: Chunk) {
if (position.y == ProtocolDefinition.SECTION_MAX_Y) { if (position.y == ProtocolDefinition.SECTION_MAX_Y) {
neighbourBlocks[O_UP] = neighbours[O_UP]?.blocks?.let { it[position.with(y = 0)] } neighbourBlocks[O_UP] = neighbours[O_UP]?.blocks?.let { it[position.with(y = 0)] }
light[O_UP] = (if (highest) chunk.light.top else neighbours[O_UP]?.light)?.get(position.with(y = 0)) ?: 0x00 light[O_UP] = (if (highest) chunk.light.top else neighbours[O_UP]?.light)?.get(position.with(y = 0))?.index ?: 0x00
} else { } else {
neighbourBlocks[O_UP] = section.blocks[position.plusY()] neighbourBlocks[O_UP] = section.blocks[position.plusY()]
light[O_UP] = section.light[position.plusY()] light[O_UP] = section.light[position.plusY()].index
} }
} }
@ -202,14 +202,15 @@ class SolidSectionMesher(
private inline fun setNeighbour(blocks: Array<BlockState?>, position: InChunkPosition, light: ByteArray, section: ChunkSection?, chunk: Chunk?, direction: Int) { private inline fun setNeighbour(blocks: Array<BlockState?>, position: InChunkPosition, light: ByteArray, section: ChunkSection?, chunk: Chunk?, direction: Int) {
val inSection = position.inSectionPosition val inSection = position.inSectionPosition
blocks[direction] = section?.blocks?.let { it[inSection] } blocks[direction] = section?.blocks?.let { it[inSection] }
var level = section?.light?.get(inSection) ?: 0x00 var level = section?.light?.get(inSection)?.index ?: 0x00
if (chunk != null && position.y >= chunk.light.heightmap[position.xz]) { if (chunk != null && position.y >= chunk.light.heightmap[position.xz]) {
level = (level.toInt() or SKY_LIGHT_MASK).toByte() // set sky light to 0x0F level = (level.toInt() or MAX_SKY_LIGHT).toByte() // set sky light to 0x0F
} }
light[direction] = level light[direction] = level
} }
companion object { companion object {
const val SELF_LIGHT_INDEX = 6 // after all directions const val SELF_LIGHT_INDEX = 6 // after all directions
const val MAX_SKY_LIGHT = LightLevel.SKY_MASK shl LightLevel.SKY_SHIFT
} }
} }

View File

@ -19,6 +19,7 @@ import de.bixilon.kutil.math.interpolation.Interpolator
import de.bixilon.minosoft.data.entities.entities.Entity import de.bixilon.minosoft.data.entities.entities.Entity
import de.bixilon.minosoft.data.text.formatting.color.ChatColors import de.bixilon.minosoft.data.text.formatting.color.ChatColors
import de.bixilon.minosoft.data.text.formatting.color.ColorUtil import de.bixilon.minosoft.data.text.formatting.color.ColorUtil
import de.bixilon.minosoft.data.world.chunk.light.types.LightLevel
import de.bixilon.minosoft.gui.rendering.entities.EntitiesRenderer import de.bixilon.minosoft.gui.rendering.entities.EntitiesRenderer
import de.bixilon.minosoft.gui.rendering.entities.easteregg.EntityEasterEggs.FLIP_ROTATION import de.bixilon.minosoft.gui.rendering.entities.easteregg.EntityEasterEggs.FLIP_ROTATION
import de.bixilon.minosoft.gui.rendering.entities.easteregg.EntityEasterEggs.isFlipped import de.bixilon.minosoft.gui.rendering.entities.easteregg.EntityEasterEggs.isFlipped
@ -89,17 +90,17 @@ abstract class EntityRenderer<E : Entity>(
this.distance = (entity.renderInfo.eyePosition - renderer.session.camera.entity.renderInfo.eyePosition).length2() this.distance = (entity.renderInfo.eyePosition - renderer.session.camera.entity.renderInfo.eyePosition).length2()
} }
private fun getCurrentLight(): Int { private fun getCurrentLight(): LightLevel {
var light = entity.physics.positionInfo.chunk?.light?.get(entity.physics.positionInfo.position.inChunkPosition) ?: return 0xFF var light = with(entity.physics.positionInfo) { chunk?.light?.get(position.inChunkPosition) } ?: return LightLevel.MAX
if (entity.isOnFire) { if (entity.isOnFire) {
light = light or 0xF0 light = light.with(block = LightLevel.MAX_LEVEL)
} }
return light return light
} }
protected open fun updateLight(delta: Float) { protected open fun updateLight(delta: Float) {
if (this.light.delta >= 1.0f) { if (this.light.delta >= 1.0f) {
val rgb = renderer.context.light.map.buffer[getCurrentLight()] val rgb = renderer.context.light.map.buffer[getCurrentLight().index.toInt()]
this.light.push(rgb) this.light.push(rgb)
} }
this.light.add(delta, 0.1f) this.light.add(delta, 0.1f)

View File

@ -32,7 +32,6 @@ import de.bixilon.minosoft.data.text.BaseComponent
import de.bixilon.minosoft.data.text.TextComponent import de.bixilon.minosoft.data.text.TextComponent
import de.bixilon.minosoft.data.text.formatting.color.ChatColors import de.bixilon.minosoft.data.text.formatting.color.ChatColors
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.light.SectionLight
import de.bixilon.minosoft.gui.rendering.chunk.ChunkRenderer import de.bixilon.minosoft.gui.rendering.chunk.ChunkRenderer
import de.bixilon.minosoft.gui.rendering.entities.EntitiesRenderer import de.bixilon.minosoft.gui.rendering.entities.EntitiesRenderer
import de.bixilon.minosoft.gui.rendering.events.ResizeWindowEvent import de.bixilon.minosoft.gui.rendering.events.ResizeWindowEvent
@ -299,7 +298,7 @@ class DebugHUDElement(guiRenderer: GUIRenderer) : Element(guiRenderer), Layouted
this@DebugWorldInfo += AutoTextElement(guiRenderer, 1) { BaseComponent("Sky properties ", entity.session.world.dimension.effects) } this@DebugWorldInfo += AutoTextElement(guiRenderer, 1) { BaseComponent("Sky properties ", entity.session.world.dimension.effects) }
this@DebugWorldInfo += AutoTextElement(guiRenderer, 1) { BaseComponent("Biome ", biome) } this@DebugWorldInfo += AutoTextElement(guiRenderer, 1) { BaseComponent("Biome ", biome) }
this@DebugWorldInfo += AutoTextElement(guiRenderer, 1) { with(entity.session.world.getLight(entity.renderInfo.eyePosition.blockPosition)) { BaseComponent("Light block=", (this and SectionLight.BLOCK_LIGHT_MASK), ", sky=", ((this and SectionLight.SKY_LIGHT_MASK) shr 4)) } } this@DebugWorldInfo += AutoTextElement(guiRenderer, 1) { with(entity.session.world.getLight(entity.renderInfo.eyePosition.blockPosition)) { BaseComponent("Light block=", this.block, ", sky=", this.sky) } }
this@DebugWorldInfo += AutoTextElement(guiRenderer, 1) { BaseComponent("Fully loaded: ", chunk.neighbours.complete) } this@DebugWorldInfo += AutoTextElement(guiRenderer, 1) { BaseComponent("Fully loaded: ", chunk.neighbours.complete) }
lastChunk.value = chunk lastChunk.value = chunk

View File

@ -17,7 +17,7 @@ import de.bixilon.kotlinglm.vec3.Vec3d
import de.bixilon.minosoft.data.registries.particle.data.ParticleData import de.bixilon.minosoft.data.registries.particle.data.ParticleData
import de.bixilon.minosoft.data.text.formatting.color.ChatColors import de.bixilon.minosoft.data.text.formatting.color.ChatColors
import de.bixilon.minosoft.data.text.formatting.color.RGBColor import de.bixilon.minosoft.data.text.formatting.color.RGBColor
import de.bixilon.minosoft.data.world.chunk.light.SectionLight import de.bixilon.minosoft.data.world.chunk.light.types.LightLevel
import de.bixilon.minosoft.gui.rendering.particle.types.Particle import de.bixilon.minosoft.gui.rendering.particle.types.Particle
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.blockPosition import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.blockPosition
import de.bixilon.minosoft.protocol.network.session.play.PlaySession import de.bixilon.minosoft.protocol.network.session.play.PlaySession
@ -34,26 +34,20 @@ abstract class RenderParticle(session: PlaySession, position: Vec3d, velocity: V
this.light = retrieveLight() this.light = retrieveLight()
} }
private fun retrieveLight(): Int { private fun retrieveLight(): LightLevel {
val aabb = aabb + position val aabb = aabb + position
var maxBlockLight = emittingLight var maxLevel = LightLevel.EMPTY.with(emittingLight)
var maxSkyLight = 0
val chunkPosition = position.blockPosition.chunkPosition val chunkPosition = position.blockPosition.chunkPosition
val chunk = getChunk() ?: return maxBlockLight val chunk = getChunk() ?: return maxLevel
for (position in aabb.positions()) { for (position in aabb.positions()) {
val next = chunk.neighbours.traceChunk(position.chunkPosition - chunkPosition) val next = chunk.neighbours.traceChunk(position.chunkPosition - chunkPosition)
val light = next?.light?.get(position.inChunkPosition) ?: SectionLight.SKY_LIGHT_MASK val light = next?.light?.get(position.inChunkPosition) ?: LightLevel(0, LightLevel.MAX_LEVEL) // No chunk is given, assume there is light (otherwise particle might looks badly dark)
if (light and SectionLight.BLOCK_LIGHT_MASK > maxBlockLight) { maxLevel = maxLevel.max(light)
maxBlockLight = light and SectionLight.BLOCK_LIGHT_MASK
}
if (light and SectionLight.SKY_LIGHT_MASK > maxSkyLight) {
maxSkyLight = light and SectionLight.SKY_LIGHT_MASK
}
} }
return maxBlockLight or maxSkyLight return maxLevel
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
* Minosoft * Minosoft
* Copyright (C) 2020-2024 Moritz Zwerger * Copyright (C) 2020-2025 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 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.
* *
@ -32,6 +32,6 @@ abstract class TextureParticle(session: PlaySession, position: Vec3d, velocity:
when { when {
texture.transparency == TextureTransparencies.TRANSLUCENT || color.alpha != 255 -> translucentMesh texture.transparency == TextureTransparencies.TRANSLUCENT || color.alpha != 255 -> translucentMesh
else -> mesh else -> mesh
}.addVertex(getCameraPosition(time), scale, texture, color, light = light) }.addVertex(getCameraPosition(time), scale, texture, color, light = light.index.toInt())
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
* Minosoft * Minosoft
* Copyright (C) 2020-2024 Moritz Zwerger * Copyright (C) 2020-2025 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 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.
* *
@ -32,6 +32,6 @@ abstract class AdvancedTextureParticle(session: PlaySession, position: Vec3d, ve
when { when {
texture.transparency == TextureTransparencies.TRANSLUCENT || color.alpha != 255 -> translucentMesh texture.transparency == TextureTransparencies.TRANSLUCENT || color.alpha != 255 -> translucentMesh
else -> mesh else -> mesh
}.addVertex(getCameraPosition(time), scale, texture, color, minUV.array, maxUV.array, light) }.addVertex(getCameraPosition(time), scale, texture, color, minUV.array, maxUV.array, light.index.toInt())
} }
} }

View File

@ -24,7 +24,7 @@ import de.bixilon.minosoft.data.world.biome.source.XZBiomeArray
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.chunk.ChunkPrototype import de.bixilon.minosoft.data.world.chunk.chunk.ChunkPrototype
import de.bixilon.minosoft.data.world.chunk.light.LightArray import de.bixilon.minosoft.data.world.chunk.light.types.LightArray
import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbourArray import de.bixilon.minosoft.data.world.chunk.neighbours.ChunkNeighbourArray
import de.bixilon.minosoft.data.world.container.palette.PalettedContainerReader import de.bixilon.minosoft.data.world.container.palette.PalettedContainerReader
import de.bixilon.minosoft.data.world.container.palette.palettes.BiomePaletteFactory import de.bixilon.minosoft.data.world.container.palette.palettes.BiomePaletteFactory

View File

@ -15,7 +15,7 @@ package de.bixilon.minosoft.protocol.packets.s2c.play.block.chunk.light
import de.bixilon.minosoft.data.registries.dimension.DimensionProperties import de.bixilon.minosoft.data.registries.dimension.DimensionProperties
import de.bixilon.minosoft.data.world.chunk.chunk.ChunkPrototype import de.bixilon.minosoft.data.world.chunk.chunk.ChunkPrototype
import de.bixilon.minosoft.data.world.chunk.light.LightArray import de.bixilon.minosoft.data.world.chunk.light.types.LightArray
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions import de.bixilon.minosoft.protocol.protocol.ProtocolVersions
import de.bixilon.minosoft.protocol.protocol.ProtocolVersions.V_1_16 import de.bixilon.minosoft.protocol.protocol.ProtocolVersions.V_1_16