mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-13 09:26:11 -04:00
wip: greedy meshing
This commit is contained in:
parent
ec35307a22
commit
2455359747
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user