wip: greedy meshing

This commit is contained in:
Bixilon 2021-11-07 23:50:42 +01:00
parent ec35307a22
commit 2455359747
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
7 changed files with 211 additions and 48 deletions

View File

@ -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<Directions> {
override val VALUES = values()

View File

@ -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<BlockState?> = 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
}
}
}

View File

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

View File

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

View File

@ -24,7 +24,8 @@ import java.util.*
class BakedBlockStateModel(
val faces: Array<Array<BakedFace>>,
) : BakedBlockModel { // ToDo: Greedy meshable
) : BakedBlockModel, GreedyBakedBlockModel { // ToDo: Greedy meshable
override val canGreedyMesh: Boolean = true
override fun getFaceSize(direction: Directions, random: Random): Array<FaceSize> {
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")
}

View File

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

View File

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