more culling tests, improved culling

This commit is contained in:
Bixilon 2023-06-26 18:37:36 +02:00
parent 334cddde50
commit a649edbdaa
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
12 changed files with 119 additions and 60 deletions

View File

@ -23,7 +23,8 @@ import de.bixilon.minosoft.data.registries.blocks.types.Block
import de.bixilon.minosoft.data.registries.identified.Namespaces.minosoft
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.BakedFace
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.BakedModel
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.SideSize
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.cull.side.FaceProperties
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.cull.side.SideProperties
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureTransparencies
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.MemoryTexture
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY
@ -34,22 +35,22 @@ import org.testng.annotations.Test
@Test(groups = ["models", "culling"])
class FaceCullingTest {
private fun createFace(size: SideSize.FaceSize? = SideSize.FaceSize(Vec2(0), Vec2(1)), transparency: TextureTransparencies = TextureTransparencies.OPAQUE): BakedFace {
return BakedFace(floatArrayOf(), floatArrayOf(), 1.0f, -1, null, MemoryTexture(minosoft("test"), Vec2i.EMPTY), size)
private fun createFace(transparency: TextureTransparencies = TextureTransparencies.OPAQUE, properties: FaceProperties? = FaceProperties(Vec2(0), Vec2(1), transparency)): BakedFace {
return BakedFace(floatArrayOf(), floatArrayOf(), 1.0f, -1, null, MemoryTexture(minosoft("test"), Vec2i.EMPTY), properties)
}
private fun createNeighbour(size: SideSize? = SideSize(arrayOf(SideSize.FaceSize(Vec2(0), Vec2(1)))), transparency: TextureTransparencies = TextureTransparencies.OPAQUE, type: Int = 0): BlockState {
private fun createNeighbour(transparency: TextureTransparencies = TextureTransparencies.OPAQUE, properties: SideProperties? = SideProperties(arrayOf(FaceProperties(Vec2(0), Vec2(1), transparency)), transparency), type: Int = 0): BlockState {
val block = object : Block(minosoft("dummy$type"), BlockSettings()) {
override val hardness: Float get() = Broken()
}
return createNeighbour(block, size, transparency, type)
return createNeighbour(block, transparency, properties)
}
private fun createNeighbour(block: Block, size: SideSize? = SideSize(arrayOf(SideSize.FaceSize(Vec2(0), Vec2(1)))), transparency: TextureTransparencies = TextureTransparencies.OPAQUE, type: Int = 0): BlockState {
private fun createNeighbour(block: Block, transparency: TextureTransparencies = TextureTransparencies.OPAQUE, properties: SideProperties? = SideProperties(arrayOf(FaceProperties(Vec2(0), Vec2(1), transparency)), transparency)): BlockState {
val state = BlockState(block, 0)
state.model = BakedModel(emptyArray(), arrayOf(size, size, size, size, size, size), null)
state.model = BakedModel(emptyArray(), arrayOf(properties, properties, properties, properties, properties, properties), null)
return state
}
@ -63,7 +64,7 @@ class FaceCullingTest {
@Test
fun selfNotTouching() {
val face = createFace(null)
val face = createFace(properties = null)
val neighbour = createNeighbour()
assertFalse(FaceCulling.canCull(createState(), face, Directions.DOWN, neighbour))
}
@ -71,7 +72,7 @@ class FaceCullingTest {
@Test
fun neighbourNotTouching() {
val face = createFace()
val neighbour = createNeighbour(null)
val neighbour = createNeighbour(properties = null)
assertFalse(FaceCulling.canCull(createState(), face, Directions.DOWN, neighbour))
}
@ -84,50 +85,50 @@ class FaceCullingTest {
@Test
fun sizeMatch() {
val face = createFace(SideSize.FaceSize(Vec2(0.0f, 0.5f), Vec2(1.0f, 0.5f)))
val neighbour = createNeighbour(side(SideSize.FaceSize(Vec2(0.0f, 0.5f), Vec2(1.0f, 0.5f))))
val face = createFace(properties = FaceProperties(Vec2(0.0f, 0.5f), Vec2(1.0f, 0.5f), TextureTransparencies.OPAQUE))
val neighbour = createNeighbour(properties = side(FaceProperties(Vec2(0.0f, 0.5f), Vec2(1.0f, 0.5f), TextureTransparencies.OPAQUE)))
assertTrue(FaceCulling.canCull(createState(), face, Directions.EAST, neighbour))
}
@Test
fun greaterNeighbour() {
val face = createFace(SideSize.FaceSize(Vec2(0.0f, 0.5f), Vec2(1.0f, 0.5f)))
val neighbour = createNeighbour(side(SideSize.FaceSize(Vec2(0.0f, 0.5f), Vec2(1.0f, 0.6f))))
val face = createFace(properties = FaceProperties(Vec2(0.0f, 0.5f), Vec2(1.0f, 0.5f), TextureTransparencies.OPAQUE))
val neighbour = createNeighbour(properties = side(FaceProperties(Vec2(0.0f, 0.5f), Vec2(1.0f, 0.6f), TextureTransparencies.OPAQUE)))
assertTrue(FaceCulling.canCull(createState(), face, Directions.EAST, neighbour))
}
@Test
fun smallerNeighbour() {
val face = createFace(SideSize.FaceSize(Vec2(0.0f, 0.5f), Vec2(1.0f, 0.5f)))
val neighbour = createNeighbour(side(SideSize.FaceSize(Vec2(0.0f, 0.5f), Vec2(1.0f, 0.4f))))
val face = createFace(properties = FaceProperties(Vec2(0.0f, 0.5f), Vec2(1.0f, 0.5f), TextureTransparencies.OPAQUE))
val neighbour = createNeighbour(properties = side(FaceProperties(Vec2(0.0f, 0.5f), Vec2(1.0f, 0.4f), TextureTransparencies.OPAQUE)))
assertFalse(FaceCulling.canCull(createState(), face, Directions.EAST, neighbour))
}
@Test
fun shiftedNeighbour1() {
val face = createFace(SideSize.FaceSize(Vec2(0.0f, 0.5f), Vec2(1.0f, 0.5f)))
val neighbour = createNeighbour(side(SideSize.FaceSize(Vec2(0.1f, 0.5f), Vec2(1.0f, 0.5f))))
val face = createFace(properties = FaceProperties(Vec2(0.0f, 0.5f), Vec2(1.0f, 0.5f), TextureTransparencies.OPAQUE))
val neighbour = createNeighbour(properties = side(FaceProperties(Vec2(0.1f, 0.5f), Vec2(1.0f, 0.5f), TextureTransparencies.OPAQUE)))
assertFalse(FaceCulling.canCull(createState(), face, Directions.EAST, neighbour))
}
@Test
fun shiftedNeighbour2() {
val face = createFace(SideSize.FaceSize(Vec2(0.0f, 0.5f), Vec2(1.0f, 0.5f)))
val neighbour = createNeighbour(side(SideSize.FaceSize(Vec2(0.1f, 0.5f), Vec2(1.0f, 0.6f))))
val face = createFace(properties = FaceProperties(Vec2(0.0f, 0.5f), Vec2(1.0f, 0.5f), TextureTransparencies.OPAQUE))
val neighbour = createNeighbour(properties = side(FaceProperties(Vec2(0.1f, 0.5f), Vec2(1.0f, 0.6f), TextureTransparencies.OPAQUE)))
assertFalse(FaceCulling.canCull(createState(), face, Directions.EAST, neighbour))
}
@Test
fun shiftedNeighbour3() {
val face = createFace(SideSize.FaceSize(Vec2(0.1f, 0.8f), Vec2(0.9f, 0.9f)))
val neighbour = createNeighbour(side(SideSize.FaceSize(Vec2(0.1f, 0.5f), Vec2(0.95f, 0.95f))))
val face = createFace(properties = FaceProperties(Vec2(0.1f, 0.8f), Vec2(0.9f, 0.9f), TextureTransparencies.OPAQUE))
val neighbour = createNeighbour(properties = side(FaceProperties(Vec2(0.1f, 0.5f), Vec2(0.95f, 0.95f), TextureTransparencies.OPAQUE)))
assertTrue(FaceCulling.canCull(createState(), face, Directions.EAST, neighbour))
}
@Test
fun multipleNeighbourFaces() {
val face = createFace(SideSize.FaceSize(Vec2(0.1f, 0.3f), Vec2(0.9f, 0.9f)))
val neighbour = createNeighbour(side(SideSize.FaceSize(Vec2(0.1f, 0.2f), Vec2(0.95f, 0.4f)), SideSize.FaceSize(Vec2(0.1f, 0.4f), Vec2(0.95f, 0.6f)), SideSize.FaceSize(Vec2(0.1f, 0.6f), Vec2(0.95f, 0.95f))))
val face = createFace(properties = FaceProperties(Vec2(0.1f, 0.3f), Vec2(0.9f, 0.9f), TextureTransparencies.OPAQUE))
val neighbour = createNeighbour(properties = side(FaceProperties(Vec2(0.1f, 0.2f), Vec2(0.95f, 0.4f), TextureTransparencies.OPAQUE), FaceProperties(Vec2(0.1f, 0.4f), Vec2(0.95f, 0.6f), TextureTransparencies.OPAQUE), FaceProperties(Vec2(0.1f, 0.6f), Vec2(0.95f, 0.95f), TextureTransparencies.OPAQUE)))
assertTrue(FaceCulling.canCull(createState(), face, Directions.EAST, neighbour))
}
@ -206,7 +207,7 @@ class FaceCullingTest {
val block = object : Block(minosoft("dummy"), BlockSettings()), CustomBlockCulling {
override val hardness get() = Broken()
override fun shouldCull(state: BlockState, face: BakedFace, neighbour: BlockState): Boolean {
override fun shouldCull(state: BlockState, face: BakedFace, directions: Directions, neighbour: BlockState): Boolean {
throw AssertionError("shouldCall invoked!")
}
}
@ -215,14 +216,14 @@ class FaceCullingTest {
assertTrue(FaceCulling.canCull(createNeighbour(block), face, Directions.EAST, neighbour))
}
private fun side(vararg size: SideSize.FaceSize): SideSize {
return SideSize(arrayOf(*size))
private fun side(vararg properties: FaceProperties): SideProperties {
return SideProperties(arrayOf(*properties), properties.first().transparency)
}
private fun forceNoCull() = object : Block(minosoft("dummy"), BlockSettings()), CustomBlockCulling {
override val hardness get() = Broken()
override fun shouldCull(state: BlockState, face: BakedFace, neighbour: BlockState): Boolean {
override fun shouldCull(state: BlockState, face: BakedFace, directions: Directions, neighbour: BlockState): Boolean {
return false
}
}

View File

@ -27,7 +27,7 @@ import de.bixilon.minosoft.gui.rendering.models.block.state.baked.BakingUtil.com
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.BakingUtil.compactSize
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.BakingUtil.positions
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.BakingUtil.pushRight
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.SideSize
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.cull.side.FaceProperties
import de.bixilon.minosoft.gui.rendering.models.loader.BlockLoader
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureManager
import de.bixilon.minosoft.util.KUtil.toResourceLocation
@ -112,7 +112,7 @@ data class SingleBlockStateApply(
if (model.elements == null) return null
val bakedFaces: Array<MutableList<BakedFace>> = Array(Directions.SIZE) { mutableListOf() }
val sizes: Array<MutableList<SideSize.FaceSize>> = Array(Directions.SIZE) { mutableListOf() } // TODO
val sizes: Array<MutableList<FaceProperties>> = Array(Directions.SIZE) { mutableListOf() } // TODO
for (element in model.elements) {
for ((direction, face) in element.faces) {

View File

@ -16,6 +16,7 @@ package de.bixilon.minosoft.gui.rendering.models.block.state.baked
import de.bixilon.kotlinglm.vec3.Vec3
import de.bixilon.minosoft.data.direction.Directions
import de.bixilon.minosoft.data.text.formatting.color.RGBColor
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.cull.side.FaceProperties
import de.bixilon.minosoft.gui.rendering.system.base.MeshUtil.buffer
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureTransparencies
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.AbstractTexture
@ -31,7 +32,7 @@ class BakedFace(
val tintIndex: Int,
val cull: Directions?,
val texture: AbstractTexture,
val size: SideSize.FaceSize? = null,
val properties: FaceProperties? = null,
) {
private var cullIndex = cull?.ordinal ?: SELF_LIGHT_INDEX

View File

@ -18,6 +18,7 @@ import de.bixilon.minosoft.data.direction.Directions
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.world.positions.BlockPosition
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
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.AbstractTexture
import de.bixilon.minosoft.gui.rendering.world.mesh.WorldMesh
@ -25,11 +26,11 @@ import java.util.*
class BakedModel(
val faces: Array<Array<BakedFace>>,
val sizes: Array<SideSize?>,
val sizes: Array<SideProperties?>,
val particle: AbstractTexture?,
) : BlockRender {
override fun getSize(direction: Directions) = sizes[direction.ordinal]
override fun getProperties(direction: Directions) = sizes[direction.ordinal]
override fun getParticleTexture(random: Random?, position: Vec3i) = particle

View File

@ -16,6 +16,9 @@ package de.bixilon.minosoft.gui.rendering.models.block.state.baked
import de.bixilon.kotlinglm.vec3.Vec3
import de.bixilon.kutil.array.ArrayUtil.cast
import de.bixilon.minosoft.data.direction.Directions
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.cull.side.FaceProperties
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.cull.side.SideProperties
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureTransparencies
object BakingUtil {
@ -42,13 +45,30 @@ object BakingUtil {
return array.cast()
}
fun Array<MutableList<SideSize.FaceSize>>.compactSize(): Array<SideSize?> {
val array: Array<SideSize?> = arrayOfNulls(size)
fun Array<MutableList<FaceProperties>>.compactSize(): Array<SideProperties?> {
val array: Array<SideProperties?> = arrayOfNulls(size)
for ((index, entries) in this.withIndex()) {
val size = entries.toTypedArray()
if (size.isEmpty()) continue
array[index] = SideSize(size)
var transparency: TextureTransparencies? = null
var set = false
for (entry in size) {
if (!set) {
transparency = entry.transparency
set = true
continue
}
if (transparency != entry.transparency) {
transparency = null
break
}
}
array[index] = SideProperties(size, transparency)
}
return array

View File

@ -13,10 +13,11 @@
package de.bixilon.minosoft.gui.rendering.models.block.state.baked.cull
import de.bixilon.minosoft.data.direction.Directions
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.BakedFace
interface CustomBlockCulling {
fun shouldCull(state: BlockState, face: BakedFace, neighbour: BlockState): Boolean
fun shouldCull(state: BlockState, face: BakedFace, directions: Directions, neighbour: BlockState): Boolean
}

View File

@ -16,20 +16,38 @@ package de.bixilon.minosoft.gui.rendering.models.block.state.baked.cull
import de.bixilon.minosoft.data.direction.Directions
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.BakedFace
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureTransparencies
object FaceCulling {
fun canCull(block: BlockState, face: BakedFace, direction: Directions, neighbour: BlockState?): Boolean {
fun canCull(state: BlockState, face: BakedFace, direction: Directions, neighbour: BlockState?): Boolean {
if (neighbour == null) return false
if (!face.touchingSide) return false
val model = neighbour.model ?: return false
val size = model.getSize(direction) ?: return false
val neighbourProperties = model.getProperties(direction) ?: return false // not touching side
val properties = face.properties ?: return false
return true // TODO
// if not covered return false
if (neighbourProperties.transparency == TextureTransparencies.OPAQUE) {
// impossible to see that face
return true
}
if (state.block is CustomBlockCulling) {
return state.block.shouldCull(state, face, direction, neighbour)
}
if (neighbourProperties.transparency == null) return false // can not determinate it
if (state.block != neighbour.block) return false
if (neighbourProperties.transparency == properties.transparency) return true
return false
}
private inline val BakedFace.touchingSide: Boolean get() = size != null
private inline val BakedFace.touchingSide: Boolean get() = properties != null
}

View File

@ -0,0 +1,23 @@
/*
* Minosoft
* Copyright (C) 2020-2023 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.gui.rendering.models.block.state.baked.cull.side
import de.bixilon.kotlinglm.vec2.Vec2
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureTransparencies
class FaceProperties(
val start: Vec2,
val end: Vec2,
val transparency: TextureTransparencies,
)

View File

@ -11,21 +11,15 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.rendering.models.block.state.baked
package de.bixilon.minosoft.gui.rendering.models.block.state.baked.cull.side
import de.bixilon.kotlinglm.vec2.Vec2
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureTransparencies
class SideSize(
val sizes: Array<FaceSize>,
class SideProperties(
val sizes: Array<FaceProperties>,
val transparency: TextureTransparencies?,
) {
init {
if (sizes.isEmpty()) throw IllegalCallerException("sizes is empty!")
}
class FaceSize(
val start: Vec2,
val end: Vec2,
)
}

View File

@ -18,7 +18,7 @@ import de.bixilon.minosoft.gui.rendering.models.block.state.apply.BlockStateAppl
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.BakedFace
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.BakedModel
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.BakingUtil.compact
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.SideSize
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.cull.side.FaceProperties
import de.bixilon.minosoft.gui.rendering.models.block.state.render.BlockRender
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureManager
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.AbstractTexture
@ -53,7 +53,7 @@ class BuilderApply(
private fun List<BakedModel>.combine(): BakedModel {
val faces: Array<MutableList<BakedFace>> = Array(Directions.SIZE) { mutableListOf() }
val sizes: Array<MutableList<SideSize.FaceSize>> = Array(Directions.SIZE) { mutableListOf() }
val sizes: Array<MutableList<FaceProperties>> = Array(Directions.SIZE) { mutableListOf() }
var particle: AbstractTexture? = null
for (model in this) {

View File

@ -17,7 +17,7 @@ import de.bixilon.kotlinglm.vec3.Vec3i
import de.bixilon.minosoft.data.direction.Directions
import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.world.positions.BlockPosition
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.SideSize
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.cull.side.SideProperties
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.AbstractTexture
import de.bixilon.minosoft.gui.rendering.world.mesh.WorldMesh
import java.util.*
@ -27,5 +27,5 @@ interface BlockRender {
fun render(position: BlockPosition, offset: FloatArray, mesh: WorldMesh, random: Random?, state: BlockState, neighbours: Array<BlockState?>, light: ByteArray, tints: IntArray?): Boolean
fun getSize(direction: Directions): SideSize? = null
fun getProperties(direction: Directions): SideProperties? = null
}

View File

@ -20,7 +20,7 @@ import de.bixilon.minosoft.data.registries.blocks.state.BlockState
import de.bixilon.minosoft.data.world.positions.BlockPosition
import de.bixilon.minosoft.data.world.positions.BlockPositionUtil.positionHash
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.BakedModel
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.SideSize
import de.bixilon.minosoft.gui.rendering.models.block.state.baked.cull.side.SideProperties
import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.AbstractTexture
import de.bixilon.minosoft.gui.rendering.world.mesh.WorldMesh
import java.util.*
@ -32,7 +32,7 @@ class WeightedBlockRender(
) : BlockRender {
private val size = models.getSize()
override fun getSize(direction: Directions): SideSize? {
override fun getProperties(direction: Directions): SideProperties? {
return size[direction.ordinal] // TODO: get random block model
}
@ -66,8 +66,8 @@ class WeightedBlockRender(
val model: BakedModel,
)
private fun Array<WeightedEntry>.getSize(): Array<SideSize?> {
val sizes: Array<SideSize?> = arrayOfNulls(Directions.SIZE)
private fun Array<WeightedEntry>.getSize(): Array<SideProperties?> {
val sizes: Array<SideProperties?> = arrayOfNulls(Directions.SIZE)
val skip = BooleanArray(Directions.SIZE)
for ((_, model) in this) {
@ -75,7 +75,7 @@ class WeightedBlockRender(
if (skip[direction.ordinal]) continue
val current = sizes[direction.ordinal]
val size = model.getSize(direction)
val size = model.getProperties(direction)
if (current == null) {
sizes[direction.ordinal] = size
continue