diff --git a/src/main/java/de/bixilon/minosoft/data/direction/Directions.kt b/src/main/java/de/bixilon/minosoft/data/direction/Directions.kt index 84a28b2f0..5ebf63d9f 100644 --- a/src/main/java/de/bixilon/minosoft/data/direction/Directions.kt +++ b/src/main/java/de/bixilon/minosoft/data/direction/Directions.kt @@ -14,16 +14,16 @@ package de.bixilon.minosoft.data.direction import de.bixilon.minosoft.data.Axes import de.bixilon.minosoft.data.registries.blocks.properties.serializer.BlockPropertiesSerializer +import de.bixilon.minosoft.data.text.ChatColors import de.bixilon.minosoft.gui.rendering.models.FaceSize import de.bixilon.minosoft.gui.rendering.util.VecUtil.get import de.bixilon.minosoft.util.KUtil import de.bixilon.minosoft.util.enum.ValuesEnum +import glm_.vec2.Vec2 import glm_.vec3.Vec3 import glm_.vec3.Vec3d import glm_.vec3.Vec3i -import glm_.vec3.swizzle.xy -import glm_.vec3.swizzle.xz -import glm_.vec3.swizzle.yz +import glm_.vec3.swizzle.* import kotlin.math.abs enum class Directions( @@ -41,6 +41,7 @@ enum class Directions( override val vectord = Vec3d(vector) val axis: Axes get() = Axes[this] // ToDo + val debugColor = ChatColors[ordinal] lateinit var inverted: Directions private set @@ -87,6 +88,17 @@ enum class Directions( } } + fun getUVMultiplier(from: Vec3, to: Vec3): Vec2 { + return when (this) { + DOWN -> from.zx - to.zx + UP -> from.xz - to.xz + NORTH -> from.xy - to.xy + SOUTH -> from.yx - to.yx + EAST -> from.zy - to.zy + WEST -> from.yz - to.yz + } + } + companion object : BlockPropertiesSerializer, ValuesEnum { override val VALUES = values() diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/block/SectionPreparer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/block/SectionPreparer.kt index cbd302310..2e8894832 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/block/SectionPreparer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/block/SectionPreparer.kt @@ -14,69 +14,176 @@ package de.bixilon.minosoft.gui.rendering.block import de.bixilon.minosoft.data.direction.Directions -import de.bixilon.minosoft.data.registries.blocks.BlockState import de.bixilon.minosoft.data.world.ChunkSection import de.bixilon.minosoft.gui.rendering.RenderWindow import de.bixilon.minosoft.gui.rendering.block.mesh.ChunkSectionMesh -import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition +import de.bixilon.minosoft.gui.rendering.models.baked.block.GreedyBakedBlockModel +import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition.SECTION_SIZE +import de.bixilon.minosoft.util.KUtil.decide import de.bixilon.minosoft.util.logging.Log import de.bixilon.minosoft.util.logging.LogLevels import de.bixilon.minosoft.util.logging.LogMessageType import glm_.vec3.Vec3i import java.util.* + class SectionPreparer( val renderWindow: RenderWindow, ) { + private fun renderNormal(position: Vec3i, section: ChunkSection, mesh: ChunkSectionMesh, random: Random) { + // ToDo + } + fun prepare(section: ChunkSection): ChunkSectionMesh { val startTime = System.nanoTime() val mesh = ChunkSectionMesh(renderWindow) val random = Random(0L) - for (x in 0 until ProtocolDefinition.SECTION_WIDTH_X) { - for (y in 0 until ProtocolDefinition.SECTION_HEIGHT_Y) { - for (z in 0 until ProtocolDefinition.SECTION_WIDTH_Z) { - val block = section.blocks[ChunkSection.getIndex(x, y, z)] ?: continue - val neighbours: Array = arrayOfNulls(Directions.VALUES.size) - // ToDo: Chunk borders - neighbours[Directions.DOWN.ordinal] = if (y == 0) { - null - } else { - section.blocks[ChunkSection.getIndex(x, y - 1, z)] - } - neighbours[Directions.UP.ordinal] = if (y == ProtocolDefinition.SECTION_MAX_Y) { - null - } else { - section.blocks[ChunkSection.getIndex(x, y + 1, z)] - } - neighbours[Directions.NORTH.ordinal] = if (z == 0) { - null - } else { - section.blocks[ChunkSection.getIndex(x, y, z - 1)] - } - neighbours[Directions.SOUTH.ordinal] = if (z == ProtocolDefinition.SECTION_MAX_Z) { - null - } else { - section.blocks[ChunkSection.getIndex(x, y, z + 1)] - } - neighbours[Directions.WEST.ordinal] = if (x == 0) { - null - } else { - section.blocks[ChunkSection.getIndex(x - 1, y, z)] - } - neighbours[Directions.EAST.ordinal] = if (x == ProtocolDefinition.SECTION_MAX_X) { - null - } else { - section.blocks[ChunkSection.getIndex(x + 1, y, z)] - } - val model = block.model + for (direction in Directions.VALUES) { + // Sweep over each axis (X, Y and Z) + val backFace = direction.ordinal % 2 == 0 + val axis = direction.axis.ordinal + var i: Int + var j: Int + var k: Int + var l: Int + var w: Int + var h: Int + val nextAxis = (axis + 1) % 3 + val nextNextAxis = (axis + 2) % 3 + val position = IntArray(3) + val checkOffset = IntArray(3) + val mask = BooleanArray(SECTION_SIZE * SECTION_SIZE) + checkOffset[axis] = 1 - random.setSeed(0L) - model?.singleRender(Vec3i(x, y, z), mesh, random, neighbours, 0xFF, intArrayOf(0xF, 0xF, 0xF, 0xF)) + val offsetCheck = backFace.decide(-1, 1) + + // Check each slice of the chunk one at a time + + position[axis] = -1 + while (position[axis] < SECTION_SIZE) { + + // Compute the mask + var n = 0 + position[nextNextAxis] = 0 + while (position[nextNextAxis] < SECTION_SIZE) { + position[nextAxis] = 0 + while (position[nextAxis] < SECTION_SIZE) { + // checkOffset determines the direction (X, Y or Z) that we are searching + // m.IsBlockAt(x,y,z) takes global map positions and returns true if a block exists there + if ((offsetCheck == 1 && position[axis] < 0) || (offsetCheck == -1 && position[axis] > SECTION_SIZE)) { + ++position[nextAxis] + n++ + continue + } + val currentBlock = if (position[axis] >= 0) section.blocks[ChunkSection.getIndex(position[0], position[1], position[2])] else null + val compareBlock = if (position[axis] < SECTION_SIZE - 1) section.blocks[ChunkSection.getIndex(position[0] + checkOffset[0], position[1] + checkOffset[1], position[2] + checkOffset[2])] else null + + // The mask is set to true if there is a visible face between two blocks, + // i.e. both aren't empty and both aren't blocks + val primaryBlock = if (backFace) { + compareBlock + } else { + currentBlock + } + + mask[n++] = primaryBlock != null && currentBlock != compareBlock + ++position[nextAxis] + } + ++position[nextNextAxis] + } + ++position[axis] + n = 0 + + // Generate a mesh from the mask using lexicographic ordering, + // by looping over each block in this slice of the chunk + j = 0 + while (j < SECTION_SIZE) { + i = 0 + while (i < SECTION_SIZE) { + if (mask[n]) { + // Compute the width of this quad and store it in w + // This is done by searching along the current axis until mask[n + w] is false + w = 1 + while (i + w < SECTION_SIZE && mask[n + w]) { + w++ + } + + + // Compute the height of this quad and store it in h + // This is done by checking if every block next to this row (range 0 to w) is also part of the mask. + // For example, if w is 5 we currently have a quad of dimensions 1 x 5. To reduce triangle count, + // greedy meshing will attempt to expand this quad out to CHUNK_SIZE x 5, but will stop if it reaches a hole in the mask + var done = false + + h = 1 + while (j + h < SECTION_SIZE) { + k = 0 + while (k < w) { + if (!mask[n + k + h * SECTION_SIZE]) { + done = true + break + } + k++ + } + if (done) { + break + } + h++ + } + + position[nextAxis] = i + position[nextNextAxis] = j + + // du and dv determine the size and orientation of this face + val du = IntArray(3) + du[nextAxis] = w + val dv = IntArray(3) + dv[nextNextAxis] = h + + + if (!backFace) { + position[axis] -= offsetCheck + } + + val start = Vec3i(position[0], position[1], position[2]) + + + val end = Vec3i(position[0] + du[0] + dv[0], position[1] + du[1] + dv[1], position[2] + du[2] + dv[2]) + + + val block = section.blocks[ChunkSection.getIndex(position[0], position[1], position[2])]!! + (block.model as GreedyBakedBlockModel).greedyRender(start, end, direction, mesh, 0xFF) + + + if (!backFace) { + position[axis] += offsetCheck + } + + // Clear this part of the mask, so we don't add duplicate faces + l = 0 + while (l < h) { + k = 0 + while (k < w) { + mask[n + k + l * SECTION_SIZE] = false + ++k + } + ++l + } + + // Increment counters and continue + i += w + n += w + } else { + i++ + n++ + } + } + ++j } } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/block/WorldRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/block/WorldRenderer.kt index f61a22e01..d9fee4ba8 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/block/WorldRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/block/WorldRenderer.kt @@ -34,6 +34,7 @@ import glm_.vec2.Vec2i import java.io.FileInputStream import java.util.zip.GZIPInputStream import java.util.zip.ZipInputStream +import kotlin.random.Random class WorldRenderer( private val connection: PlayConnection, @@ -63,10 +64,11 @@ class WorldRenderer( lightMap.use(shader) + val random = Random(0L) val blockState = connection.registries.blockRegistry["diamond_block"]?.defaultState - val chunk = ChunkSection(Array(4096) { if (it < 4096) blockState else null }) - // for(i in 0 until 100000) - mesh = sectionPreparer.prepare(chunk) + val chunk = ChunkSection(Array(4096) { if (random.nextBoolean()) blockState else null }) + for (i in 0 until 1000) + mesh = sectionPreparer.prepare(chunk) mesh.load() } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/block/mesh/ChunkSectionMesh.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/block/mesh/ChunkSectionMesh.kt index a5b239db4..983385e21 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/block/mesh/ChunkSectionMesh.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/block/mesh/ChunkSectionMesh.kt @@ -25,6 +25,7 @@ import glm_.vec3.Vec3 class ChunkSectionMesh(renderWindow: RenderWindow) : Mesh(renderWindow, SectionArrayMeshStruct, initialCacheSize = 100000) { fun addVertex(position: Vec3, uv: Vec2, texture: AbstractTexture, tintColor: RGBColor?, light: Int) { + //val texture = renderWindow.WHITE_TEXTURE.texture val textureLayer = if (RenderConstants.FORCE_DEBUG_TEXTURE) { RenderConstants.DEBUG_TEXTURE_ID } else { diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/models/baked/block/BakedBlockStateModel.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/models/baked/block/BakedBlockStateModel.kt index b5559e33d..dccbc67ea 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/models/baked/block/BakedBlockStateModel.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/models/baked/block/BakedBlockStateModel.kt @@ -24,7 +24,8 @@ import java.util.* class BakedBlockStateModel( val faces: Array>, -) : BakedBlockModel { // ToDo: Greedy meshable +) : BakedBlockModel, GreedyBakedBlockModel { // ToDo: Greedy meshable + override val canGreedyMesh: Boolean = true override fun getFaceSize(direction: Directions, random: Random): Array { return arrayOf() // ToDo @@ -43,6 +44,14 @@ class BakedBlockStateModel( } } + override fun greedyRender(start: Vec3i, end: Vec3i, side: Directions, mesh: ChunkSectionMesh, light: Int) { + val floatStart = start.toVec3() + val floatEnd = end.toVec3() + for (face in faces[side.ordinal]) { + face.greedyRender(floatStart, floatEnd, side, mesh, light) + } + } + override fun getLight(position: Vec3i, random: Random, side: Directions, lightAccessor: LightAccessor): Int { TODO("Not yet implemented") } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/models/baked/block/BakedFace.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/models/baked/block/BakedFace.kt index b2315bc36..f3febdba6 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/models/baked/block/BakedFace.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/models/baked/block/BakedFace.kt @@ -13,11 +13,13 @@ package de.bixilon.minosoft.gui.rendering.models.baked.block +import de.bixilon.minosoft.data.Axes import de.bixilon.minosoft.data.direction.Directions import de.bixilon.minosoft.data.registries.blocks.BlockState import de.bixilon.minosoft.gui.rendering.block.mesh.ChunkSectionMesh import de.bixilon.minosoft.gui.rendering.models.FaceSize import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.AbstractTexture +import de.bixilon.minosoft.gui.rendering.util.VecUtil.get import de.bixilon.minosoft.gui.rendering.util.mesh.Mesh import glm_.vec2.Vec2 import glm_.vec3.Vec3 @@ -37,4 +39,33 @@ class BakedFace( mesh.addVertex(positions[index] + position, uv[textureIndex], texture, null, light) } } + + fun greedyRender(start: Vec3, end: Vec3, side: Directions, mesh: ChunkSectionMesh, light: Int) { + val multiplier = end - start + val positions = arrayOf( + (positions[0] * multiplier) + start, + (positions[1] * multiplier) + start, + (positions[2] * multiplier) + start, + (positions[3] * multiplier) + start, + ) + val fixPosition = this.positions[0][side.axis] + for (position in positions) { + when (side.axis) { + Axes.X -> position.x = start.x + fixPosition + Axes.Y -> position.y = start.y + fixPosition + Axes.Z -> position.z = start.z + fixPosition + } + } + + val uvMultiplier = side.getUVMultiplier(start, end) + val uv = arrayOf( + uv[0] * uvMultiplier, + uv[1] * uvMultiplier, + uv[2] * uvMultiplier, + uv[3] * uvMultiplier, + ) + for ((index, textureIndex) in Mesh.QUAD_DRAW_ODER) { + mesh.addVertex(positions[index], uv[textureIndex], texture, null, light) + } + } } diff --git a/src/main/java/de/bixilon/minosoft/protocol/protocol/ProtocolDefinition.java b/src/main/java/de/bixilon/minosoft/protocol/protocol/ProtocolDefinition.java index 48f8cae70..d5ae66aa0 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/protocol/ProtocolDefinition.java +++ b/src/main/java/de/bixilon/minosoft/protocol/protocol/ProtocolDefinition.java @@ -54,6 +54,7 @@ public final class ProtocolDefinition { public static final Pattern RESOURCE_LOCATION_PATTERN = Pattern.compile("([a-z_0-9]+:)?[a-zA-Z_0-9.]+"); public static final Pattern SCOREBOARD_OBJECTIVE_PATTERN = Pattern.compile("[a-zA-z-.+]{1,16}"); + public static final int SECTION_SIZE = 16; public static final int SECTION_WIDTH_X = 16; public static final int SECTION_MAX_X = SECTION_WIDTH_X - 1; public static final int SECTION_WIDTH_Z = 16;