diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/SolidSectionMesher.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/SolidSectionMesher.kt index f505d50d8..1da1eef57 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/SolidSectionMesher.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/mesher/SolidSectionMesher.kt @@ -34,11 +34,12 @@ 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.neighbours.ChunkNeighbours import de.bixilon.minosoft.data.world.positions.BlockPosition +import de.bixilon.minosoft.data.world.positions.InChunkSectionPosition import de.bixilon.minosoft.gui.rendering.RenderContext import de.bixilon.minosoft.gui.rendering.chunk.entities.BlockEntityRenderer import de.bixilon.minosoft.gui.rendering.chunk.entities.renderer.RenderedBlockEntity import de.bixilon.minosoft.gui.rendering.chunk.mesh.ChunkMeshes -import de.bixilon.minosoft.gui.rendering.light.AmbientOcclusionUtil +import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusion import de.bixilon.minosoft.gui.rendering.models.block.state.render.WorldRenderProps import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import java.util.* @@ -68,6 +69,7 @@ class SolidSectionMesher( val entities: ArrayList> = ArrayList(section.blockEntities.count) val position = BlockPosition() + val inSectionPosition = InChunkSectionPosition() val neighbourBlocks: Array = arrayOfNulls(Directions.SIZE) val light = ByteArray(Directions.SIZE + 1) // last index (6) for the current block @@ -79,15 +81,17 @@ class SolidSectionMesher( val floatOffset = FloatArray(3) - val ao = Array(Directions.SIZE) { IntArray(4) } + val ao = if (ambientOcclusion) AmbientOcclusion(section) else null - val props = WorldRenderProps(position, floatOffset, mesh, random, neighbourBlocks, light, ao) + val props = WorldRenderProps(position, inSectionPosition, floatOffset, mesh, random, neighbourBlocks, light, ao) for (y in blocks.minPosition.y..blocks.maxPosition.y) { + inSectionPosition.y = y position.y = offsetY + y floatOffset[1] = (position.y - cameraOffset.y).toFloat() val fastBedrock = y == 0 && isLowestSection && fastBedrock for (x in blocks.minPosition.x..blocks.maxPosition.x) { + inSectionPosition.x = x position.x = offsetX + x floatOffset[0] = (position.x - cameraOffset.x).toFloat() for (z in blocks.minPosition.z..blocks.maxPosition.z) { @@ -104,6 +108,7 @@ class SolidSectionMesher( light[SELF_LIGHT_INDEX] = section.light[index] position.z = offsetZ + z + inSectionPosition.z = z floatOffset[2] = (position.z - cameraOffset.z).toFloat() val maxHeight = chunk.light.heightmap[baseIndex] @@ -136,9 +141,7 @@ class SolidSectionMesher( floatOffset[2] += offset.z } - if (ambientOcclusion) { - AmbientOcclusionUtil.apply(section, x, y, z, ao) - } + ao?.clear() val tints = tints.getBlockTint(state, chunk, x, position.y, z) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/light/ao/AmbientOcclusion.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/light/ao/AmbientOcclusion.kt new file mode 100644 index 000000000..5902132c9 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/light/ao/AmbientOcclusion.kt @@ -0,0 +1,61 @@ +/* + * 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 . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.light.ao + +import de.bixilon.minosoft.data.direction.Directions +import de.bixilon.minosoft.data.world.chunk.ChunkSection +import de.bixilon.minosoft.data.world.positions.BlockPosition +import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusionUtil.applyBottom +import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusionUtil.applyEast +import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusionUtil.applyNorth +import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusionUtil.applySouth +import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusionUtil.applyTop +import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusionUtil.applyWest + +class AmbientOcclusion( + val section: ChunkSection, +) { + private var input = Array(Directions.SIZE) { IntArray(AmbientOcclusionUtil.LEVELS) } + private var output = Array(Directions.SIZE) { AmbientOcclusionUtil.EMPTY } + private var dirty = 0x00 + + + fun apply(direction: Directions, position: BlockPosition) = apply(direction, position.x, position.y, position.z) + + fun apply(direction: Directions, x: Int, y: Int, z: Int): IntArray { + val mask = 1 shl direction.ordinal + if (dirty and mask != 0) return output[direction.ordinal] // already calculated + + val input = input[direction.ordinal] + + dirty = dirty or mask + val output = when (direction) { + Directions.DOWN -> applyBottom(section, x, y, z, input) + Directions.UP -> applyTop(section, x, y, z, input) + + Directions.NORTH -> applyNorth(section, x, y, z, input) + Directions.SOUTH -> applySouth(section, x, y, z, input) + + Directions.WEST -> applyWest(section, x, y, z, input) + Directions.EAST -> applyEast(section, x, y, z, input) + } + this.output[direction.ordinal] = output + + return output + } + + fun clear() { + dirty = 0x00 + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/light/AmbientOcclusionUtil.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/light/ao/AmbientOcclusionUtil.kt similarity index 83% rename from src/main/java/de/bixilon/minosoft/gui/rendering/light/AmbientOcclusionUtil.kt rename to src/main/java/de/bixilon/minosoft/gui/rendering/light/ao/AmbientOcclusionUtil.kt index f1772e24f..55e37ae31 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/light/AmbientOcclusionUtil.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/light/ao/AmbientOcclusionUtil.kt @@ -11,7 +11,7 @@ * This software is not affiliated with Mojang AB, the original developer of Minecraft. */ -package de.bixilon.minosoft.gui.rendering.light +package de.bixilon.minosoft.gui.rendering.light.ao import de.bixilon.minosoft.data.direction.Directions.Companion.O_DOWN import de.bixilon.minosoft.data.direction.Directions.Companion.O_EAST @@ -24,11 +24,14 @@ import de.bixilon.minosoft.data.world.chunk.ChunkSection import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition object AmbientOcclusionUtil { + const val LEVELS = 4 const val LEVEL_NONE = 0 const val LEVEL_1 = 1 const val LEVEL_2 = 2 const val LEVEL_3 = 3 + val EMPTY = IntArray(LEVELS) { LEVEL_NONE } + fun ChunkSection?.trace(x: Int, y: Int, z: Int): Int { if (this == null) return 0 var x = x @@ -37,6 +40,8 @@ object AmbientOcclusionUtil { var section: ChunkSection? = this + if (x < -1 || x > 16 || y < -1 || y > 16 || z < -1 || z > 16) throw IllegalArgumentException("x=$x, y=$y, z=$z") + if (x < 0) { section = section?.neighbours?.get(O_WEST) x = ProtocolDefinition.SECTION_MAX_X @@ -74,16 +79,9 @@ object AmbientOcclusionUtil { return side1 + side2 + corner } - private fun IntArray.clear() { - this[0] = LEVEL_NONE - this[1] = LEVEL_NONE - this[2] = LEVEL_NONE - this[3] = LEVEL_NONE - } - fun setY(section: ChunkSection?, x: Int, y: Int, z: Int, flip: Boolean, ao: IntArray) { - ao.clear() - if (section == null || section.blocks.isEmpty) return + fun setY(section: ChunkSection?, x: Int, y: Int, z: Int, flip: Boolean, ao: IntArray): IntArray { + if (section == null || section.blocks.isEmpty) return EMPTY val west = section.trace(x - 1, y, z + 0) val north = section.trace(x + 0, y, z - 1) @@ -94,9 +92,11 @@ object AmbientOcclusionUtil { ao[2] = calculateLevel(east, south, section.trace(x + 1, y, z + 1)) ao[if (flip) 3 else 1] = calculateLevel(north, east, section.trace(x + 1, y, z - 1)) ao[if (flip) 1 else 3] = calculateLevel(south, west, section.trace(x - 1, y, z + 1)) + + return ao } - fun applyBottom(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray) { + fun applyBottom(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray): IntArray { var section: ChunkSection? = section var y = y - 1 if (y < 0) { @@ -104,10 +104,10 @@ object AmbientOcclusionUtil { y = ProtocolDefinition.SECTION_MAX_Y } - setY(section, x, y, z, true, ao) + return setY(section, x, y, z, true, ao) } - fun applyTop(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray) { + fun applyTop(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray): IntArray { var section: ChunkSection? = section var y = y + 1 if (y > ProtocolDefinition.SECTION_MAX_Y) { @@ -115,12 +115,11 @@ object AmbientOcclusionUtil { y = 0 } - setY(section, x, y, z, false, ao) + return setY(section, x, y, z, false, ao) } - fun setZ(section: ChunkSection?, x: Int, y: Int, z: Int, flip: Boolean, ao: IntArray) { - ao.clear() - if (section == null || section.blocks.isEmpty) return + fun setZ(section: ChunkSection?, x: Int, y: Int, z: Int, flip: Boolean, ao: IntArray): IntArray { + if (section == null || section.blocks.isEmpty) return EMPTY val down = section.trace(x + 0, y - 1, z) val west = section.trace(x - 1, y + 0, z) @@ -131,10 +130,12 @@ object AmbientOcclusionUtil { ao[if (flip) 3 else 1] = calculateLevel(west, up, section.trace(x - 1, y + 1, z)) ao[2] = calculateLevel(up, east, section.trace(x + 1, y + 1, z)) ao[if (flip) 1 else 3] = calculateLevel(east, down, section.trace(x + 1, y - 1, z)) + + return ao } - fun applyNorth(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray) { + fun applyNorth(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray): IntArray { var section: ChunkSection? = section var z = z - 1 if (z < 0) { @@ -142,10 +143,10 @@ object AmbientOcclusionUtil { z = ProtocolDefinition.SECTION_MAX_Z } - setZ(section, x, y, z, true, ao) + return setZ(section, x, y, z, true, ao) } - fun applySouth(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray) { + fun applySouth(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray): IntArray { var section: ChunkSection? = section var z = z + 1 if (z > ProtocolDefinition.SECTION_MAX_Z) { @@ -153,12 +154,11 @@ object AmbientOcclusionUtil { z = 0 } - setZ(section, x, y, z, false, ao) + return setZ(section, x, y, z, false, ao) } - fun setX(section: ChunkSection?, x: Int, y: Int, z: Int, flip: Boolean, ao: IntArray) { - ao.clear() - if (section == null || section.blocks.isEmpty) return + fun setX(section: ChunkSection?, x: Int, y: Int, z: Int, flip: Boolean, ao: IntArray): IntArray { + if (section == null || section.blocks.isEmpty) return EMPTY val down = section.trace(x + 0, y - 1, z) val north = section.trace(x, y + 0, z - 1) @@ -169,9 +169,11 @@ object AmbientOcclusionUtil { ao[if (flip) 3 else 1] = calculateLevel(north, up, section.trace(x, y + 1, z - 1)) ao[2] = calculateLevel(up, south, section.trace(x, y + 1, z + 1)) ao[if (flip) 1 else 3] = calculateLevel(south, down, section.trace(x, y - 1, z + 1)) + + return ao } - fun applyWest(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray) { + fun applyWest(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray): IntArray { var section: ChunkSection? = section var x = x - 1 if (x < 0) { @@ -179,10 +181,10 @@ object AmbientOcclusionUtil { x = ProtocolDefinition.SECTION_MAX_X } - setX(section, x, y, z, false, ao) + return setX(section, x, y, z, false, ao) } - fun applyEast(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray) { + fun applyEast(section: ChunkSection, x: Int, y: Int, z: Int, ao: IntArray): IntArray { var section: ChunkSection? = section var x = x + 1 if (x > ProtocolDefinition.SECTION_MAX_X) { @@ -190,17 +192,6 @@ object AmbientOcclusionUtil { x = 0 } - setX(section, x, y, z, true, ao) - } - - fun apply(section: ChunkSection, x: Int, y: Int, z: Int, ao: Array) { - applyBottom(section, x, y, z, ao[O_DOWN]) - applyTop(section, x, y, z, ao[O_UP]) - - applyNorth(section, x, y, z, ao[O_NORTH]) - applySouth(section, x, y, z, ao[O_SOUTH]) - - applyWest(section, x, y, z, ao[O_WEST]) - applyEast(section, x, y, z, ao[O_EAST]) + return setX(section, x, y, z, true, ao) } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/models/block/state/baked/BakedModel.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/models/block/state/baked/BakedModel.kt index eb0540219..650c19e33 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/models/block/state/baked/BakedModel.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/models/block/state/baked/BakedModel.kt @@ -19,6 +19,7 @@ import de.bixilon.minosoft.data.direction.Directions import de.bixilon.minosoft.data.entities.block.BlockEntity import de.bixilon.minosoft.data.registries.blocks.state.BlockState import de.bixilon.minosoft.gui.rendering.chunk.mesh.BlockVertexConsumer +import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusionUtil import de.bixilon.minosoft.gui.rendering.models.block.state.baked.cull.FaceCulling import de.bixilon.minosoft.gui.rendering.models.block.state.baked.cull.side.SideProperties import de.bixilon.minosoft.gui.rendering.models.block.state.render.BlockRender @@ -56,16 +57,22 @@ class BakedModel( for ((directionIndex, faces) in faces.withIndex()) { val neighbour = neighbours[directionIndex] - val direction = Directions.VALUES[directionIndex].inverted + val direction = Directions.VALUES[directionIndex] + val inverted = direction.inverted - val ao = ao[directionIndex] for (face in faces) { - if (FaceCulling.canCull(state, face.properties, direction, neighbour)) { + if (FaceCulling.canCull(state, face.properties, inverted, neighbour)) { continue } - face.render(offset, mesh, light, tints, ao) + var aoRaw = AmbientOcclusionUtil.EMPTY + + if (ao != null && face.properties != null) { + aoRaw = ao.apply(direction, props.inSectionPosition) + } + + face.render(offset, mesh, light, tints, aoRaw) rendered = true } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/models/block/state/render/WorldRenderProps.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/models/block/state/render/WorldRenderProps.kt index 4ef4caede..3f891ad0a 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/models/block/state/render/WorldRenderProps.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/models/block/state/render/WorldRenderProps.kt @@ -15,13 +15,17 @@ package de.bixilon.minosoft.gui.rendering.models.block.state.render import de.bixilon.minosoft.data.registries.blocks.state.BlockState import de.bixilon.minosoft.data.world.positions.BlockPosition +import de.bixilon.minosoft.data.world.positions.InChunkSectionPosition import de.bixilon.minosoft.gui.rendering.chunk.mesh.BlockVertexConsumer +import de.bixilon.minosoft.gui.rendering.light.ao.AmbientOcclusion import java.util.* class WorldRenderProps( @JvmField val position: BlockPosition, @JvmField + val inSectionPosition: InChunkSectionPosition, + @JvmField val offset: FloatArray, @JvmField val mesh: BlockVertexConsumer, @@ -32,5 +36,5 @@ class WorldRenderProps( @JvmField val light: ByteArray, @JvmField - var ao: Array, + val ao: AmbientOcclusion?, )