improve frustum culling performance, update neighbour chunks, improvements

This commit is contained in:
Bixilon 2021-11-17 14:02:20 +01:00
parent 4a8dc14dd7
commit 870d2e3cca
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
12 changed files with 162 additions and 89 deletions

View File

@ -43,7 +43,6 @@
- ToDo: - ToDo:
- Thread safety
- Build biome cache only in render distance - Build biome cache only in render distance
- Update neighbour chunks if needed - Update neighbour chunks if needed
- Reduce memory usage - Reduce memory usage

View File

@ -105,6 +105,7 @@ class ErosCrashReport : JavaFXWindowController() {
"Written while driving in a FlixBus", "Written while driving in a FlixBus",
"Coded while traveling in the ICE 272 towards Hamburg-Altona", "Coded while traveling in the ICE 272 towards Hamburg-Altona",
"Sorry, the ICE 693 drive towards Munich was really long", "Sorry, the ICE 693 drive towards Munich was really long",
"Coded while playing bedwars"
) )

View File

@ -15,6 +15,7 @@ package de.bixilon.minosoft.gui.rendering.block
import de.bixilon.minosoft.data.assets.AssetsUtil import de.bixilon.minosoft.data.assets.AssetsUtil
import de.bixilon.minosoft.data.assets.Resources import de.bixilon.minosoft.data.assets.Resources
import de.bixilon.minosoft.data.direction.Directions
import de.bixilon.minosoft.data.registries.ResourceLocation import de.bixilon.minosoft.data.registries.ResourceLocation
import de.bixilon.minosoft.data.world.Chunk import de.bixilon.minosoft.data.world.Chunk
import de.bixilon.minosoft.data.world.ChunkSection import de.bixilon.minosoft.data.world.ChunkSection
@ -35,17 +36,15 @@ import de.bixilon.minosoft.gui.rendering.system.base.phases.TransparentDrawable
import de.bixilon.minosoft.gui.rendering.util.VecUtil.chunkPosition import de.bixilon.minosoft.gui.rendering.util.VecUtil.chunkPosition
import de.bixilon.minosoft.gui.rendering.util.VecUtil.empty import de.bixilon.minosoft.gui.rendering.util.VecUtil.empty
import de.bixilon.minosoft.gui.rendering.util.VecUtil.inChunkSectionPosition import de.bixilon.minosoft.gui.rendering.util.VecUtil.inChunkSectionPosition
import de.bixilon.minosoft.gui.rendering.util.VecUtil.inSectionHeight
import de.bixilon.minosoft.gui.rendering.util.VecUtil.of import de.bixilon.minosoft.gui.rendering.util.VecUtil.of
import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight import de.bixilon.minosoft.gui.rendering.util.VecUtil.sectionHeight
import de.bixilon.minosoft.gui.rendering.util.VecUtil.toVec3
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3Util.EMPTY import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3Util.EMPTY
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3dUtil.toVec3
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.EMPTY import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.EMPTY
import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.toVec3 import de.bixilon.minosoft.gui.rendering.util.vec.vec3.Vec3iUtil.toVec3
import de.bixilon.minosoft.modding.event.events.BlockSetEvent import de.bixilon.minosoft.modding.event.events.*
import de.bixilon.minosoft.modding.event.events.ChunkDataChangeEvent
import de.bixilon.minosoft.modding.event.events.ChunkUnloadEvent
import de.bixilon.minosoft.modding.event.events.RespawnEvent
import de.bixilon.minosoft.modding.event.invoker.CallbackEventInvoker import de.bixilon.minosoft.modding.event.invoker.CallbackEventInvoker
import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
@ -169,28 +168,56 @@ class WorldRenderer(
queueSection(Vec2i(chunkPosition.x + 1, chunkPosition.y), sectionHeight) queueSection(Vec2i(chunkPosition.x + 1, chunkPosition.y), sectionHeight)
} }
}) })
/*
connection.registerEvent(CallbackEventInvoker.of<MassBlockSetEvent> { connection.registerEvent(CallbackEventInvoker.of<MassBlockSetEvent> {
val chunk = world[it.chunkPosition] ?: return@of val chunk = world[it.chunkPosition] ?: return@of // should not happen
if (!chunk.isFullyLoaded || it.chunkPosition in incomplete) { if (!chunk.isFullyLoaded) {
return@of return@of
} }
val neighbourChunks = world.getChunkNeighbours(it.chunkPosition) val sectionHeights: MutableMap<Int, BooleanArray> = mutableMapOf()
if (!neighbourChunks.fullyLoaded) {
return@of
}
val sectionHeights: MutableSet<Int> = mutableSetOf()
for (blockPosition in it.blocks.keys) { for (blockPosition in it.blocks.keys) {
sectionHeights += blockPosition.sectionHeight val neighbours = sectionHeights.getOrPut(blockPosition.sectionHeight) { BooleanArray(Directions.SIZE) }
val inSectionHeight = blockPosition.y.inSectionHeight
if (inSectionHeight == 0) {
neighbours[0] = true
} else if (inSectionHeight == ProtocolDefinition.SECTION_MAX_Y) {
neighbours[1] = true
}
if (blockPosition.z == 0) {
neighbours[2] = true
} else if (blockPosition.z == ProtocolDefinition.SECTION_MAX_Z) {
neighbours[3] = true
}
if (blockPosition.x == 0) {
neighbours[4] = true
} else if (blockPosition.x == ProtocolDefinition.SECTION_MAX_X) {
neighbours[5] = true
}
} }
renderWindow.queue += { for ((sectionHeight, neighbourUpdates) in sectionHeights) {
val meshes = loadedMeshes.getOrPut(it.chunkPosition) { mutableMapOf() } queueSection(it.chunkPosition, sectionHeight, chunk)
for (sectionHeight in sectionHeights) {
// updateSection(it.chunkPosition, sectionHeight, chunk, neighbourChunks.unsafeCast(), meshes) if (neighbourUpdates[0]) {
queueSection(it.chunkPosition, sectionHeight - 1, chunk)
}
if (neighbourUpdates[1]) {
queueSection(it.chunkPosition, sectionHeight + 1, chunk)
}
if (neighbourUpdates[2]) {
queueSection(Vec2i(it.chunkPosition.x, it.chunkPosition.y - 1), sectionHeight)
}
if (neighbourUpdates[3]) {
queueSection(Vec2i(it.chunkPosition.x, it.chunkPosition.y + 1), sectionHeight)
}
if (neighbourUpdates[4]) {
queueSection(Vec2i(it.chunkPosition.x - 1, it.chunkPosition.y), sectionHeight)
}
if (neighbourUpdates[5]) {
queueSection(Vec2i(it.chunkPosition.x + 1, it.chunkPosition.y), sectionHeight)
} }
} }
}) })
*/
connection.registerEvent(CallbackEventInvoker.of<ChunkUnloadEvent> { unloadChunk(it.chunkPosition) }) connection.registerEvent(CallbackEventInvoker.of<ChunkUnloadEvent> { unloadChunk(it.chunkPosition) })
} }
@ -253,6 +280,28 @@ class WorldRenderer(
meshesToUnloadLock.unlock() meshesToUnloadLock.unlock()
} }
private fun addMesh(mesh: ChunkSectionMeshes, visibleOpaque: MutableList<ChunkSectionMesh> = this.visibleOpaque, visibleTranslucent: MutableList<ChunkSectionMesh> = this.visibleTranslucent, visibleTransparent: MutableList<ChunkSectionMesh> = this.visibleTransparent) {
val distance = (cameraPosition - mesh.center).length2()
mesh.opaqueMesh?.let {
it.distance = distance
visibleOpaque += it
}
mesh.translucentMesh?.let {
it.distance = distance
visibleTranslucent += it
}
mesh.transparentMesh?.let {
it.distance = distance
visibleTransparent += it
}
}
private fun sortVisible(visibleOpaque: MutableList<ChunkSectionMesh> = this.visibleOpaque, visibleTranslucent: MutableList<ChunkSectionMesh> = this.visibleTranslucent, visibleTransparent: MutableList<ChunkSectionMesh> = this.visibleTransparent) {
visibleOpaque.sortBy { it.distance }
visibleTranslucent.sortBy { -it.distance }
visibleTransparent.sortBy { it.distance }
}
private fun removeMesh(mesh: ChunkSectionMeshes) { private fun removeMesh(mesh: ChunkSectionMeshes) {
mesh.opaqueMesh?.let { visibleOpaque -= it } mesh.opaqueMesh?.let { visibleOpaque -= it }
mesh.translucentMesh?.let { visibleTranslucent -= it } mesh.translucentMesh?.let { visibleTranslucent -= it }
@ -324,13 +373,13 @@ class WorldRenderer(
} }
private fun internalQueueSection(chunkPosition: Vec2i, sectionHeight: Int, chunk: Chunk, section: ChunkSection, ignoreFrustum: Boolean): Boolean { private fun internalQueueSection(chunkPosition: Vec2i, sectionHeight: Int, chunk: Chunk, section: ChunkSection, ignoreFrustum: Boolean): Boolean {
if (!chunk.isFullyLoaded || section.blocks.isEmpty) { if (!chunk.isFullyLoaded || section.blocks.isEmpty) { // ToDo: Unload if empty
return false return false
} }
val visible = ignoreFrustum || isChunkVisible(chunkPosition, sectionHeight, section.blocks.minPosition, section.blocks.maxPosition) val visible = ignoreFrustum || isSectionVisible(chunkPosition, sectionHeight, section.blocks.minPosition, section.blocks.maxPosition, true)
if (visible) { if (visible) {
val item = WorldQueueItem(chunkPosition, sectionHeight, chunk, section, Vec3i.of(chunkPosition, sectionHeight, Vec3i(8, 8, 8)).toVec3(), null) val item = WorldQueueItem(chunkPosition, sectionHeight, chunk, section, Vec3i.of(chunkPosition, sectionHeight).toVec3() + CHUNK_CENTER, null)
queueLock.lock() queueLock.lock()
queue.removeAll { it == item } // Prevent duplicated entries (to not prepare the same chunk twice (if it changed and was not prepared yet or ...) queue.removeAll { it == item } // Prevent duplicated entries (to not prepare the same chunk twice (if it changed and was not prepared yet or ...)
if (chunkPosition == cameraChunkPosition) { if (chunkPosition == cameraChunkPosition) {
@ -365,6 +414,7 @@ class WorldRenderer(
return return
} }
// ToDo: Check if chunk is visible (not section, chunk)
var queueChanges = 0 var queueChanges = 0
for (sectionHeight in chunk.lowestSection until chunk.highestSection) { for (sectionHeight in chunk.lowestSection until chunk.highestSection) {
val section = chunk[sectionHeight] ?: continue val section = chunk[sectionHeight] ?: continue
@ -386,6 +436,7 @@ class WorldRenderer(
return return
} }
var addedMeshes = 0
val time = System.currentTimeMillis() val time = System.currentTimeMillis()
val maxTime = if (connection.player.velocity.empty) 50L else 20L // If the player is still, then we can load more chunks (to not cause lags) val maxTime = if (connection.player.velocity.empty) 50L else 20L // If the player is still, then we can load more chunks (to not cause lags)
@ -393,12 +444,12 @@ class WorldRenderer(
val item = meshesToLoad.removeAt(0) val item = meshesToLoad.removeAt(0)
val mesh = item.mesh ?: throw IllegalStateException("Mesh of queued item is null!") val mesh = item.mesh ?: throw IllegalStateException("Mesh of queued item is null!")
mesh.load() mesh.load()
val visible = isChunkVisible(item.chunkPosition, item.sectionHeight, mesh.minPosition, mesh.maxPosition) val visible = isSectionVisible(item.chunkPosition, item.sectionHeight, mesh.minPosition, mesh.maxPosition, true)
if (visible) { if (visible) {
mesh.opaqueMesh?.let { visibleOpaque += it } addMesh(mesh)
mesh.translucentMesh?.let { visibleTranslucent += it } addedMeshes++
mesh.transparentMesh?.let { visibleTransparent += it }
} }
loadedMeshesLock.lock() loadedMeshesLock.lock()
val meshes = loadedMeshes.getOrPut(item.chunkPosition) { mutableMapOf() } val meshes = loadedMeshes.getOrPut(item.chunkPosition) { mutableMapOf() }
@ -409,6 +460,10 @@ class WorldRenderer(
loadedMeshesLock.unlock() loadedMeshesLock.unlock()
} }
meshesToLoadLock.release() meshesToLoadLock.release()
if (addedMeshes > 0) {
sortVisible()
}
} }
private fun unloadMeshes() { private fun unloadMeshes() {
@ -473,8 +528,12 @@ class WorldRenderer(
} }
} }
private fun isChunkVisible(chunkPosition: Vec2i, sectionHeight: Int, minPosition: Vec3i, maxPosition: Vec3i): Boolean { private fun isChunkVisible(chunkPosition: Vec2i): Boolean {
if (!chunkPosition.isInRenderDistance(cameraChunkPosition)) { return chunkPosition.isInRenderDistance(cameraChunkPosition)
}
private fun isSectionVisible(chunkPosition: Vec2i, sectionHeight: Int, minPosition: Vec3i, maxPosition: Vec3i, checkChunk: Boolean): Boolean {
if (checkChunk && !isChunkVisible(chunkPosition)) {
return false return false
} }
// ToDo: Cave culling, frustum clipping, improve performance // ToDo: Cave culling, frustum clipping, improve performance
@ -482,19 +541,28 @@ class WorldRenderer(
} }
private fun onFrustumChange() { private fun onFrustumChange() {
var sortQueue = false
val cameraPosition = connection.player.cameraPosition.toVec3()
if (this.cameraPosition != cameraPosition) {
this.cameraPosition = cameraPosition
this.cameraChunkPosition = connection.player.positionInfo.chunkPosition
sortQueue = true
}
val visibleOpaque: MutableList<ChunkSectionMesh> = mutableListOf() val visibleOpaque: MutableList<ChunkSectionMesh> = mutableListOf()
val visibleTranslucent: MutableList<ChunkSectionMesh> = mutableListOf() val visibleTranslucent: MutableList<ChunkSectionMesh> = mutableListOf()
val visibleTransparent: MutableList<ChunkSectionMesh> = mutableListOf() val visibleTransparent: MutableList<ChunkSectionMesh> = mutableListOf()
loadedMeshesLock.acquire() loadedMeshesLock.acquire()
for ((chunkPosition, meshes) in this.loadedMeshes) { for ((chunkPosition, meshes) in this.loadedMeshes) {
if (!isChunkVisible(chunkPosition)) {
continue
}
for ((sectionHeight, mesh) in meshes) { for ((sectionHeight, mesh) in meshes) {
if (!isChunkVisible(chunkPosition, sectionHeight, mesh.minPosition, mesh.maxPosition)) { if (!isSectionVisible(chunkPosition, sectionHeight, mesh.minPosition, mesh.maxPosition, false)) {
continue continue
} }
mesh.opaqueMesh?.let { visibleOpaque += it } addMesh(mesh, visibleOpaque, visibleTranslucent, visibleTransparent)
mesh.translucentMesh?.let { visibleTranslucent += it }
mesh.transparentMesh?.let { visibleTransparent += it }
} }
} }
loadedMeshesLock.release() loadedMeshesLock.release()
@ -502,9 +570,12 @@ class WorldRenderer(
culledQueueLock.acquire() culledQueueLock.acquire()
val queue: MutableMap<Vec2i, MutableSet<Int>> = mutableMapOf() // The queue method needs the full lock of the culledQueue val queue: MutableMap<Vec2i, MutableSet<Int>> = mutableMapOf() // The queue method needs the full lock of the culledQueue
for ((chunkPosition, sectionHeights) in this.culledQueue) { for ((chunkPosition, sectionHeights) in this.culledQueue) {
if (!isChunkVisible(chunkPosition)) {
continue
}
var chunkQueue: MutableSet<Int>? = null var chunkQueue: MutableSet<Int>? = null
for (sectionHeight in sectionHeights) { for (sectionHeight in sectionHeights) {
if (!isChunkVisible(chunkPosition, sectionHeight, Vec3i.EMPTY, Vec3i(16))) { if (!isSectionVisible(chunkPosition, sectionHeight, Vec3i.EMPTY, Vec3i(16), false)) {
continue continue
} }
if (chunkQueue == null) { if (chunkQueue == null) {
@ -513,6 +584,7 @@ class WorldRenderer(
chunkQueue += sectionHeight chunkQueue += sectionHeight
} }
} }
culledQueueLock.release() culledQueueLock.release()
@ -537,20 +609,13 @@ class WorldRenderer(
culledQueueLock.release() culledQueueLock.release()
val cameraPosition = connection.player.cameraPosition.toVec3 sortVisible(visibleOpaque, visibleTranslucent, visibleTransparent)
visibleOpaque.sortBy { (it.center - cameraPosition).length2() }
this.visibleOpaque = visibleOpaque this.visibleOpaque = visibleOpaque
visibleTranslucent.sortBy { -(it.center - cameraPosition).length2() }
this.visibleTranslucent = visibleTranslucent this.visibleTranslucent = visibleTranslucent
visibleTransparent.sortBy { (it.center - cameraPosition).length2() }
this.visibleTransparent = visibleTransparent this.visibleTransparent = visibleTransparent
if (this.cameraPosition != cameraPosition) { if (sortQueue) {
this.cameraPosition = cameraPosition
this.cameraChunkPosition = connection.player.positionInfo.chunkPosition
sortQueue() sortQueue()
} }
} }
@ -558,6 +623,7 @@ class WorldRenderer(
companion object : RendererBuilder<WorldRenderer> { companion object : RendererBuilder<WorldRenderer> {
override val RESOURCE_LOCATION = ResourceLocation("minosoft:world_renderer") override val RESOURCE_LOCATION = ResourceLocation("minosoft:world_renderer")
private val CHUNK_CENTER = Vec3(8.0f)
override fun build(connection: PlayConnection, renderWindow: RenderWindow): WorldRenderer { override fun build(connection: PlayConnection, renderWindow: RenderWindow): WorldRenderer {
return WorldRenderer(connection, renderWindow) return WorldRenderer(connection, renderWindow)

View File

@ -21,7 +21,8 @@ import de.bixilon.minosoft.gui.rendering.util.mesh.MeshStruct
import glm_.vec2.Vec2 import glm_.vec2.Vec2
import glm_.vec3.Vec3 import glm_.vec3.Vec3
class ChunkSectionMesh(renderWindow: RenderWindow, initialCacheSize: Int, val center: Vec3) : Mesh(renderWindow, SectionArrayMeshStruct, initialCacheSize = initialCacheSize) { class ChunkSectionMesh(renderWindow: RenderWindow, initialCacheSize: Int) : Mesh(renderWindow, SectionArrayMeshStruct, initialCacheSize = initialCacheSize) {
var distance: Float = 0.0f // Used for sorting
fun addVertex(position: FloatArray, uv: Vec2, texture: AbstractTexture, tintColor: Int, light: Int) { fun addVertex(position: FloatArray, uv: Vec2, texture: AbstractTexture, tintColor: Int, light: Int) {
val transformedUV = texture.renderData?.transformUV(uv) ?: uv val transformedUV = texture.renderData?.transformUV(uv) ?: uv

View File

@ -24,11 +24,12 @@ class ChunkSectionMeshes(
val chunkPosition: Vec2i, val chunkPosition: Vec2i,
val sectionHeight: Int, val sectionHeight: Int,
) { ) {
var opaqueMesh: ChunkSectionMesh? = ChunkSectionMesh(renderWindow, 100000, Vec3(Vec3i.of(chunkPosition, sectionHeight, Vec3i(8, 8, 8)))) val center: Vec3 = Vec3(Vec3i.of(chunkPosition, sectionHeight, Vec3i(8, 8, 8)))
var opaqueMesh: ChunkSectionMesh? = ChunkSectionMesh(renderWindow, 100000)
private set private set
var translucentMesh: ChunkSectionMesh? = ChunkSectionMesh(renderWindow, 10000, Vec3(Vec3i.of(chunkPosition, sectionHeight, Vec3i(8, 8, 8)))) var translucentMesh: ChunkSectionMesh? = ChunkSectionMesh(renderWindow, 10000)
private set private set
var transparentMesh: ChunkSectionMesh? = ChunkSectionMesh(renderWindow, 20000, Vec3(Vec3i.of(chunkPosition, sectionHeight, Vec3i(8, 8, 8)))) var transparentMesh: ChunkSectionMesh? = ChunkSectionMesh(renderWindow, 20000)
private set private set
// used for frustum culling // used for frustum culling

View File

@ -23,7 +23,6 @@ import de.bixilon.minosoft.gui.rendering.models.baked.block.GreedyBakedBlockMode
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition.SECTION_SIZE import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition.SECTION_SIZE
import de.bixilon.minosoft.util.KUtil.decide import de.bixilon.minosoft.util.KUtil.decide
import glm_.vec2.Vec2i import glm_.vec2.Vec2i
import glm_.vec3.Vec3
import glm_.vec3.Vec3i import glm_.vec3.Vec3i
import java.util.* import java.util.*
@ -41,7 +40,7 @@ class GreedySectionPreparer(
// base taken from https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/ // base taken from https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/
@Deprecated("TODO") @Deprecated("TODO")
override fun prepare(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection, neighbours: Array<ChunkSection?>): ChunkSectionMeshes { override fun prepare(chunkPosition: Vec2i, sectionHeight: Int, section: ChunkSection, neighbours: Array<ChunkSection?>): ChunkSectionMeshes {
val mesh = ChunkSectionMesh(renderWindow, 20000, Vec3()) val mesh = ChunkSectionMesh(renderWindow, 20000)
val random = Random(0L) val random = Random(0L)

View File

@ -25,6 +25,7 @@ import de.bixilon.minosoft.data.registries.fluid.DefaultFluids
import de.bixilon.minosoft.data.text.ChatColors import de.bixilon.minosoft.data.text.ChatColors
import de.bixilon.minosoft.gui.rendering.RenderConstants import de.bixilon.minosoft.gui.rendering.RenderConstants
import de.bixilon.minosoft.gui.rendering.RenderWindow import de.bixilon.minosoft.gui.rendering.RenderWindow
import de.bixilon.minosoft.gui.rendering.input.camera.frustum.Frustum
import de.bixilon.minosoft.gui.rendering.input.camera.hit.BlockRaycastHit import de.bixilon.minosoft.gui.rendering.input.camera.hit.BlockRaycastHit
import de.bixilon.minosoft.gui.rendering.input.camera.hit.EntityRaycastHit import de.bixilon.minosoft.gui.rendering.input.camera.hit.EntityRaycastHit
import de.bixilon.minosoft.gui.rendering.input.camera.hit.FluidRaycastHit import de.bixilon.minosoft.gui.rendering.input.camera.hit.FluidRaycastHit

View File

@ -11,33 +11,35 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft. * This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/ */
package de.bixilon.minosoft.gui.rendering.input.camera package de.bixilon.minosoft.gui.rendering.input.camera.frustum
import de.bixilon.minosoft.data.registries.AABB import de.bixilon.minosoft.data.registries.AABB
import de.bixilon.minosoft.gui.rendering.RenderConstants import de.bixilon.minosoft.gui.rendering.RenderConstants
import de.bixilon.minosoft.gui.rendering.input.camera.Camera
import de.bixilon.minosoft.gui.rendering.util.VecUtil.of import de.bixilon.minosoft.gui.rendering.util.VecUtil.of
import de.bixilon.minosoft.gui.rendering.util.vec.vec4.Vec4Util.dot
import de.bixilon.minosoft.util.KUtil import de.bixilon.minosoft.util.KUtil
import de.bixilon.minosoft.util.KUtil.get import de.bixilon.minosoft.util.KUtil.get
import de.bixilon.minosoft.util.enum.ValuesEnum import de.bixilon.minosoft.util.enum.ValuesEnum
import glm_.mat3x3.Mat3 import glm_.mat3x3.Mat3
import glm_.mat4x4.Mat4
import glm_.vec2.Vec2i import glm_.vec2.Vec2i
import glm_.vec3.Vec3 import glm_.vec3.Vec3
import glm_.vec3.Vec3i import glm_.vec3.Vec3i
import glm_.vec4.Vec4d import glm_.vec4.Vec4
// Bit thanks to: https://gist.github.com/podgorskiy/e698d18879588ada9014768e3e82a644 // Big thanks to: https://gist.github.com/podgorskiy/e698d18879588ada9014768e3e82a644
class Frustum(private val camera: Camera) { class Frustum(private val camera: Camera) {
private var normals: List<Vec3> = listOf() private lateinit var data: FrustumData
private var planes: List<Vec4d> = listOf()
init { init {
recalculate() recalculate()
} }
fun recalculate() { fun recalculate() {
val matrix = camera.viewProjectionMatrix.transpose() val matrix = Mat4(camera.viewProjectionMatrix).transpose()
val planes = listOf( val planes = arrayOf(
matrix[3] + matrix[0], matrix[3] + matrix[0],
matrix[3] - matrix[0], matrix[3] - matrix[0],
@ -48,7 +50,7 @@ class Frustum(private val camera: Camera) {
matrix[3] - matrix[2], matrix[3] - matrix[2],
) )
val crosses = listOf( val crosses = arrayOf(
Vec3(planes[Planes.LEFT]) cross Vec3(planes[Planes.RIGHT]), Vec3(planes[Planes.LEFT]) cross Vec3(planes[Planes.RIGHT]),
Vec3(planes[Planes.LEFT]) cross Vec3(planes[Planes.BOTTOM]), Vec3(planes[Planes.LEFT]) cross Vec3(planes[Planes.BOTTOM]),
Vec3(planes[Planes.LEFT]) cross Vec3(planes[Planes.TOP]), Vec3(planes[Planes.LEFT]) cross Vec3(planes[Planes.TOP]),
@ -80,7 +82,7 @@ class Frustum(private val camera: Camera) {
return res * (-1.0f / d) return res * (-1.0f / d)
} }
val normals: List<Vec3> = listOf( val normals = arrayOf(
intersections(Planes.LEFT, Planes.BOTTOM, Planes.NEAR), intersections(Planes.LEFT, Planes.BOTTOM, Planes.NEAR),
intersections(Planes.LEFT, Planes.TOP, Planes.NEAR), intersections(Planes.LEFT, Planes.TOP, Planes.NEAR),
intersections(Planes.RIGHT, Planes.BOTTOM, Planes.NEAR), intersections(Planes.RIGHT, Planes.BOTTOM, Planes.NEAR),
@ -92,13 +94,7 @@ class Frustum(private val camera: Camera) {
intersections(Planes.RIGHT, Planes.TOP, Planes.FAR), intersections(Planes.RIGHT, Planes.TOP, Planes.FAR),
) )
synchronized(this.normals) { this.data = FrustumData(normals, planes)
this.normals = normals
}
synchronized(this.planes) {
this.planes = planes
}
} }
@ -106,42 +102,36 @@ class Frustum(private val camera: Camera) {
if (!RenderConstants.FRUSTUM_CULLING_ENABLED) { if (!RenderConstants.FRUSTUM_CULLING_ENABLED) {
return true return true
} }
val (normals, planes) = this.data
val minArray = min.array
val maxArray = max.array
val normals: List<Vec3> for (i in 0 until Planes.SIZE) {
synchronized(this.normals) { val plane = planes[i].array
normals = this.normals if (plane.dot(minArray[0], minArray[1], minArray[2]) < 0.0f
} && plane.dot(maxArray[0], minArray[1], minArray[2]) < 0.0f
val planes: List<Vec4d> && plane.dot(minArray[0], maxArray[1], minArray[2]) < 0.0f
synchronized(this.planes) { && plane.dot(maxArray[0], maxArray[1], minArray[2]) < 0.0f
planes = this.planes && plane.dot(minArray[0], minArray[1], maxArray[2]) < 0.0f
} && plane.dot(maxArray[0], minArray[1], maxArray[2]) < 0.0f
&& plane.dot(minArray[0], maxArray[1], maxArray[2]) < 0.0f
for (i in 0 until Planes.VALUES.size) { && plane.dot(maxArray[0], maxArray[1], maxArray[2]) < 0.0f
if (
(planes[i] dot Vec4d(min.x, min.y, min.z, 1.0f)) < 0.0f
&& (planes[i] dot Vec4d(max.x, min.y, min.z, 1.0f)) < 0.0f
&& (planes[i] dot Vec4d(min.x, max.y, min.z, 1.0f)) < 0.0f
&& (planes[i] dot Vec4d(max.x, max.y, min.z, 1.0f)) < 0.0f
&& (planes[i] dot Vec4d(min.x, min.y, max.z, 1.0f)) < 0.0f
&& (planes[i] dot Vec4d(max.x, min.y, max.z, 1.0f)) < 0.0f
&& (planes[i] dot Vec4d(min.x, max.y, max.z, 1.0f)) < 0.0f
&& (planes[i] dot Vec4d(max.x, max.y, max.z, 1.0f)) < 0.0f
) { ) {
return false return false
} }
} }
fun checkPoint(check: (Vec3) -> Boolean): Boolean { fun checkPoint(check: (Vec3) -> Boolean): Boolean {
var out = 0 var successFullChecks = 0
for (i in 0 until 8) { for (i in 0 until 8) {
if (check(normals[i])) { if (check(normals[i])) {
out++ successFullChecks++
} }
} }
return out == 8 return successFullChecks == 8
} }
val checks: List<(Vec3) -> Boolean> = listOf( val checks: Array<(Vec3) -> Boolean> = arrayOf(
{ it.x > max.x }, { it.x > max.x },
{ it.x < min.x }, { it.x < min.x },
@ -171,6 +161,8 @@ class Frustum(private val camera: Camera) {
return containsRegion(Vec3(aabb.min), Vec3(aabb.max)) return containsRegion(Vec3(aabb.min), Vec3(aabb.max))
} }
private data class FrustumData(val normals: Array<Vec3>, val planes: Array<Vec4>)
private enum class Planes { private enum class Planes {
LEFT, LEFT,
RIGHT, RIGHT,
@ -181,6 +173,7 @@ class Frustum(private val camera: Camera) {
; ;
companion object : ValuesEnum<Planes> { companion object : ValuesEnum<Planes> {
const val SIZE = 6
override val VALUES: Array<Planes> = values() override val VALUES: Array<Planes> = values()
override val NAME_MAP: Map<String, Planes> = KUtil.getEnumValues(VALUES) override val NAME_MAP: Map<String, Planes> = KUtil.getEnumValues(VALUES)
} }

View File

@ -15,7 +15,7 @@ package de.bixilon.minosoft.gui.rendering.modding.events
import de.bixilon.minosoft.gui.rendering.RenderWindow import de.bixilon.minosoft.gui.rendering.RenderWindow
import de.bixilon.minosoft.gui.rendering.Rendering import de.bixilon.minosoft.gui.rendering.Rendering
import de.bixilon.minosoft.gui.rendering.input.camera.Frustum import de.bixilon.minosoft.gui.rendering.input.camera.frustum.Frustum
class FrustumChangeEvent( class FrustumChangeEvent(
renderWindow: RenderWindow = Rendering.currentContext!!, renderWindow: RenderWindow = Rendering.currentContext!!,

View File

@ -13,6 +13,7 @@
package de.bixilon.minosoft.gui.rendering.util.vec.vec3 package de.bixilon.minosoft.gui.rendering.util.vec.vec3
import glm_.vec3.Vec3
import glm_.vec3.Vec3d import glm_.vec3.Vec3d
object Vec3dUtil { object Vec3dUtil {
@ -29,4 +30,9 @@ object Vec3dUtil {
val Vec3d.Companion.MAX: Vec3d val Vec3d.Companion.MAX: Vec3d
get() = Vec3d(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE) get() = Vec3d(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE)
fun Vec3d.toVec3(): Vec3 {
val array = array
return Vec3(floatArrayOf(array[0].toFloat(), array[1].toFloat(), array[2].toFloat()))
}
} }

View File

@ -25,4 +25,6 @@ object Vec4Util {
val Vec4.Companion.MAX: Vec4 val Vec4.Companion.MAX: Vec4
get() = Vec4(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE) get() = Vec4(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE)
fun FloatArray.dot(x: Float, y: Float, z: Float) = this[0] * x + this[1] * y + this[2] * z + this[3]
} }

View File

@ -259,6 +259,10 @@ object KUtil {
return this[enum.ordinal] return this[enum.ordinal]
} }
operator fun <T> Array<T>.get(enum: Enum<*>): T {
return this[enum.ordinal]
}
fun <K, V> Map<K, Any>.extend(vararg pairs: Pair<K, Any>): Map<K, V> { fun <K, V> Map<K, Any>.extend(vararg pairs: Pair<K, Any>): Map<K, V> {
val map: MutableMap<K, V> = mutableMapOf() val map: MutableMap<K, V> = mutableMapOf()