mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-17 11:24:56 -04:00
improve frustum culling a LOT
This commit is contained in:
parent
b742f2b2cd
commit
30f02488ee
@ -52,7 +52,15 @@ class Camera(
|
||||
var cameraRight = Vec3(0.0f, 0.0f, -1.0f)
|
||||
private var cameraUp = Vec3(0.0f, 1.0f, 0.0f)
|
||||
|
||||
val frustum: Frustum = Frustum(this)
|
||||
val fov: Float
|
||||
get() {
|
||||
val fov = Minosoft.config.config.game.camera.fov / (zoom + 1.0f)
|
||||
|
||||
if (!Minosoft.config.config.game.camera.dynamicFov) {
|
||||
return fov
|
||||
}
|
||||
return fov * entity.fovMultiplier
|
||||
}
|
||||
|
||||
|
||||
var viewMatrix = calculateViewMatrix()
|
||||
@ -62,15 +70,8 @@ class Camera(
|
||||
var viewProjectionMatrix = projectionMatrix * viewMatrix
|
||||
private set
|
||||
|
||||
val fov: Float
|
||||
get() {
|
||||
val fov = Minosoft.config.config.game.camera.fov
|
||||
|
||||
if (!Minosoft.config.config.game.camera.dynamicFov) {
|
||||
return fov
|
||||
}
|
||||
return fov * entity.fovMultiplier
|
||||
}
|
||||
val frustum: Frustum = Frustum(this)
|
||||
|
||||
|
||||
fun mouseCallback(xPos: Double, yPos: Double) {
|
||||
@ -151,7 +152,7 @@ class Camera(
|
||||
}
|
||||
|
||||
private fun calculateProjectionMatrix(screenDimensions: Vec2): Mat4 {
|
||||
return glm.perspective((fov / (zoom + 1.0f)).rad, screenDimensions.x / screenDimensions.y, 0.1f, 1000f)
|
||||
return glm.perspective(fov.rad, screenDimensions.x / screenDimensions.y, 0.1f, 1000f)
|
||||
}
|
||||
|
||||
private fun calculateViewMatrix(): Mat4 {
|
||||
|
@ -15,85 +15,171 @@ package de.bixilon.minosoft.gui.rendering.input.camera
|
||||
|
||||
|
||||
import de.bixilon.minosoft.gui.rendering.RenderConstants
|
||||
import de.bixilon.minosoft.gui.rendering.util.VecUtil.rotate
|
||||
import de.bixilon.minosoft.gui.rendering.util.VecUtil.EMPTY
|
||||
import de.bixilon.minosoft.gui.rendering.util.VecUtil.of
|
||||
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
|
||||
import de.bixilon.minosoft.util.KUtil.synchronizedListOf
|
||||
import glm_.func.cos
|
||||
import glm_.func.rad
|
||||
import glm_.func.sin
|
||||
import de.bixilon.minosoft.util.KUtil
|
||||
import de.bixilon.minosoft.util.KUtil.get
|
||||
import de.bixilon.minosoft.util.enum.ValuesEnum
|
||||
import glm_.mat3x3.Mat3
|
||||
import glm_.vec2.Vec2i
|
||||
import glm_.vec3.Vec3
|
||||
import glm_.vec3.Vec3i
|
||||
import glm_.vec4.Vec4
|
||||
|
||||
// Bit thanks to: https://gist.github.com/podgorskiy/e698d18879588ada9014768e3e82a644
|
||||
class Frustum(private val camera: Camera) {
|
||||
private val normals: MutableList<Vec3> = synchronizedListOf(
|
||||
camera.cameraFront.normalize(),
|
||||
)
|
||||
private var normals: List<Vec3> = listOf()
|
||||
private var planes: List<Vec4> = listOf()
|
||||
|
||||
init {
|
||||
recalculate()
|
||||
}
|
||||
|
||||
fun recalculate() {
|
||||
synchronized(normals) {
|
||||
normals.clear()
|
||||
normals.add(camera.cameraFront.normalize())
|
||||
val matrix = camera.viewProjectionMatrix.transpose()
|
||||
val planes = listOf(
|
||||
matrix[3] + matrix[0],
|
||||
matrix[3] - matrix[0],
|
||||
|
||||
calculateSideNormals()
|
||||
calculateVerticalNormals()
|
||||
matrix[3] + matrix[1],
|
||||
matrix[3] - matrix[1],
|
||||
|
||||
matrix[3] + matrix[2],
|
||||
matrix[3] - matrix[2],
|
||||
)
|
||||
|
||||
val crosses = listOf(
|
||||
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.TOP]),
|
||||
Vec3(planes[Planes.LEFT]) cross Vec3(planes[Planes.NEAR]),
|
||||
Vec3(planes[Planes.LEFT]) cross Vec3(planes[Planes.FAR]),
|
||||
|
||||
Vec3(planes[Planes.RIGHT]) cross Vec3(planes[Planes.BOTTOM]),
|
||||
Vec3(planes[Planes.RIGHT]) cross Vec3(planes[Planes.TOP]),
|
||||
Vec3(planes[Planes.RIGHT]) cross Vec3(planes[Planes.NEAR]),
|
||||
Vec3(planes[Planes.RIGHT]) cross Vec3(planes[Planes.FAR]),
|
||||
|
||||
Vec3(planes[Planes.BOTTOM]) cross Vec3(planes[Planes.TOP]),
|
||||
Vec3(planes[Planes.BOTTOM]) cross Vec3(planes[Planes.NEAR]),
|
||||
Vec3(planes[Planes.BOTTOM]) cross Vec3(planes[Planes.FAR]),
|
||||
|
||||
Vec3(planes[Planes.TOP]) cross Vec3(planes[Planes.NEAR]),
|
||||
Vec3(planes[Planes.TOP]) cross Vec3(planes[Planes.FAR]),
|
||||
|
||||
Vec3(planes[Planes.NEAR]) cross Vec3(planes[Planes.FAR]),
|
||||
)
|
||||
|
||||
fun ij2k(i: Planes, j: Planes): Int {
|
||||
return i.ordinal * (9 - i.ordinal) / 2 + j.ordinal - 1
|
||||
}
|
||||
|
||||
fun intersections(a: Planes, b: Planes, c: Planes): Vec3 {
|
||||
val d = Vec3(planes[a]) dot crosses[ij2k(b, c)]
|
||||
val res = Mat3(crosses[ij2k(b, c)], -crosses[ij2k(a, c)], crosses[ij2k(a, b)]) * Vec3(planes[a].w, planes[b].w, planes[c].w)
|
||||
return res * (-1.0f / d)
|
||||
}
|
||||
|
||||
val normals: List<Vec3> = listOf(
|
||||
intersections(Planes.LEFT, Planes.BOTTOM, Planes.NEAR),
|
||||
intersections(Planes.LEFT, Planes.TOP, Planes.NEAR),
|
||||
intersections(Planes.RIGHT, Planes.BOTTOM, Planes.NEAR),
|
||||
intersections(Planes.RIGHT, Planes.TOP, Planes.NEAR),
|
||||
|
||||
intersections(Planes.LEFT, Planes.BOTTOM, Planes.FAR),
|
||||
intersections(Planes.LEFT, Planes.TOP, Planes.FAR),
|
||||
intersections(Planes.RIGHT, Planes.BOTTOM, Planes.FAR),
|
||||
intersections(Planes.RIGHT, Planes.TOP, Planes.FAR),
|
||||
)
|
||||
|
||||
synchronized(this.normals) {
|
||||
this.normals = normals
|
||||
}
|
||||
|
||||
synchronized(this.planes) {
|
||||
this.planes = planes
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateSideNormals() {
|
||||
val cameraRealUp = (camera.cameraRight cross camera.cameraFront).normalize()
|
||||
val angle = (camera.fov - 90.0f).rad
|
||||
val sin = angle.sin
|
||||
val cos = angle.cos
|
||||
normals.add(camera.cameraFront.rotate(cameraRealUp, sin, cos).normalize())
|
||||
normals.add(camera.cameraFront.rotate(cameraRealUp, -sin, cos).normalize()) // negate angle -> negate sin
|
||||
}
|
||||
|
||||
private fun calculateVerticalNormals() {
|
||||
val aspect = camera.renderWindow.screenDimensions.y.toFloat() / camera.renderWindow.screenDimensions.x // ToDo: x/y or y/x
|
||||
val angle = (camera.fov * aspect - 90.0f).rad
|
||||
val sin = angle.sin
|
||||
val cos = angle.cos
|
||||
normals.add(camera.cameraFront.rotate(camera.cameraRight, sin, cos).normalize())
|
||||
normals.add(camera.cameraFront.rotate(camera.cameraRight, -sin, cos).normalize()) // negate angle -> negate sin
|
||||
}
|
||||
private fun containsRegion(min: Vec3, max: Vec3): Boolean {
|
||||
if (!RenderConstants.FRUSTUM_CULLING_ENABLED) {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun containsRegion(from: Vec3, to: Vec3): Boolean {
|
||||
val min = Vec3()
|
||||
for (normal in normals) {
|
||||
// get the point most likely to be in the frustum
|
||||
min.x = if (normal.x < 0) {
|
||||
from.x
|
||||
} else {
|
||||
to.x
|
||||
}
|
||||
min.y = if (normal.y < 0) {
|
||||
from.y
|
||||
} else {
|
||||
to.y
|
||||
}
|
||||
min.z = if (normal.z < 0) {
|
||||
from.z
|
||||
} else {
|
||||
to.z
|
||||
}
|
||||
val normals: List<Vec3>
|
||||
synchronized(this.normals) {
|
||||
normals = this.normals
|
||||
}
|
||||
val planes: List<Vec4>
|
||||
synchronized(this.planes) {
|
||||
planes = this.planes
|
||||
}
|
||||
|
||||
if (normal dot (min - camera.entity.eyePosition) < 0.0f) {
|
||||
return false // region is outside of frustum
|
||||
for (i in 0 until Planes.VALUES.size) {
|
||||
if (
|
||||
(planes[i] dot Vec4(min.x, min.y, min.z, 1.0f)) < 0.0f &&
|
||||
(planes[i] dot Vec4(max.x, min.y, min.z, 1.0f)) < 0.0f &&
|
||||
(planes[i] dot Vec4(min.x, max.y, min.z, 1.0f)) < 0.0f &&
|
||||
(planes[i] dot Vec4(max.x, max.y, min.z, 1.0f)) < 0.0f &&
|
||||
(planes[i] dot Vec4(min.x, min.y, max.z, 1.0f)) < 0.0f &&
|
||||
(planes[i] dot Vec4(max.x, min.y, max.z, 1.0f)) < 0.0f &&
|
||||
(planes[i] dot Vec4(min.x, max.y, max.z, 1.0f)) < 0.0f &&
|
||||
(planes[i] dot Vec4(max.x, max.y, max.z, 1.0f)) < 0.0f
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
fun checkPoint(check: (Vec3) -> Boolean): Boolean {
|
||||
var out = 0
|
||||
for (i in 0 until 8) {
|
||||
if (check(normals[i])) {
|
||||
out++
|
||||
}
|
||||
}
|
||||
return out == 8
|
||||
}
|
||||
|
||||
val checks: List<(Vec3) -> Boolean> = listOf(
|
||||
{ it.x > max.x },
|
||||
{ it.x < min.x },
|
||||
|
||||
{ it.y > max.y },
|
||||
{ it.y < min.y },
|
||||
|
||||
{ it.z > max.z },
|
||||
{ it.z < min.z },
|
||||
)
|
||||
|
||||
for (check in checks) {
|
||||
if (checkPoint(check)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun containsChunk(chunkPosition: Vec2i, lowestBlockHeight: Int, highestBlockHeight: Int): Boolean {
|
||||
if (!RenderConstants.FRUSTUM_CULLING_ENABLED) {
|
||||
return true
|
||||
val from = Vec3i.of(chunkPosition, 0, Vec3i.EMPTY)
|
||||
from.y = lowestBlockHeight
|
||||
val to = Vec3(from.x + ProtocolDefinition.SECTION_WIDTH_X, highestBlockHeight, from.z + ProtocolDefinition.SECTION_WIDTH_Z)
|
||||
return containsRegion(Vec3(from), to)
|
||||
}
|
||||
|
||||
private enum class Planes {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
BOTTOM,
|
||||
TOP,
|
||||
NEAR,
|
||||
FAR,
|
||||
;
|
||||
|
||||
companion object : ValuesEnum<Planes> {
|
||||
override val VALUES: Array<Planes> = values()
|
||||
override val NAME_MAP: Map<String, Planes> = KUtil.getEnumValues(VALUES)
|
||||
}
|
||||
val from = Vec3(chunkPosition.x * ProtocolDefinition.SECTION_WIDTH_X, lowestBlockHeight, chunkPosition.y * ProtocolDefinition.SECTION_WIDTH_Z)
|
||||
val to = from + Vec3(ProtocolDefinition.SECTION_WIDTH_X, highestBlockHeight, ProtocolDefinition.SECTION_WIDTH_Z)
|
||||
return containsRegion(from, to)
|
||||
}
|
||||
}
|
||||
|
@ -153,4 +153,8 @@ object KUtil {
|
||||
}
|
||||
return entities.toSet()
|
||||
}
|
||||
|
||||
operator fun <T> List<T>.get(enum: Enum<*>): T {
|
||||
return this[enum.ordinal]
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user