rendering: chunk culling behind the player

This commit is contained in:
Lukas 2021-02-25 21:00:11 +01:00
parent 2ea14c0802
commit 54df8f73a0
8 changed files with 77 additions and 102 deletions

View File

@ -13,13 +13,8 @@
package de.bixilon.minosoft.data.world
import de.bixilon.minosoft.data.Directions
import de.bixilon.minosoft.gui.rendering.Camera
import glm_.Java.Companion.glm
import glm_.vec2.Vec2
import kotlin.math.abs
data class ChunkLocation(val x: Int, val z: Int) {
override fun toString(): String {
return "($x $z)"
}
@ -33,76 +28,4 @@ data class ChunkLocation(val x: Int, val z: Int) {
else -> throw IllegalArgumentException("Chunk location is just 2d")
}
}
fun isVisibleFrom(camera: Camera): Boolean {
val from = Vec2(x * 16, z * 16)
val to = from + Vec2(16, 16)
val frustrum: Frustrum
// val origin = Vec2(camera.cameraPosition.x, camera.cameraPosition.z)
// if (isInCone(from, origin, camera.yaw, camera.fov)) {
// return true
// }
// if (isInCone(to, origin, camera.yaw, camera.fov)) {
// return true
// }
// if (intersectsQuad(from, to, origin, -glm.radians(camera.yaw + camera.fov / 2))) {
// return true
// }
// if (intersectsQuad(from, to, origin, -glm.radians(camera.yaw - camera.fov / 2))) {
// return true
// }
// return false
}
private fun isInCone(point: Vec2, origin: Vec2, yaw: Double, fov: Float): Boolean {
val difference = (point - origin).normalize()
val angle = Math.toDegrees(glm.asin(difference.y).toDouble())
val realYaw = if (yaw > 0) {
yaw
} else {
yaw + 360
}
val realAngle = if (angle > 0) {
angle
} else {
angle + 180
}
return abs(angle) < fov
}
private fun intersectsQuad(from: Vec2, to: Vec2, origin: Vec2, angle: Double): Boolean {
val direction = Vec2(glm.cos(angle), glm.sin(angle))
if (intersect(origin, direction, from, Vec2(from.x, to.y))) {
return true
}
if (intersect(origin, direction, from, Vec2(to.x, from.y))) {
return true
}
if (intersect(origin, direction, to, Vec2(from.x, to.y))) {
return true
}
if (intersect(origin, direction, to, Vec2(to.x, from.y))) {
return true
}
return false
}
private fun intersectLines(v1: Vec2, v2: Vec2, v3: Vec2, v4: Vec2): Vec2 {
// formula from https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
val d = (v1.x - v2.x) * (v3.y - v4.y) - (v1.y - v2.y) * (v3.x - v4.x)
val x = (v1.x * v2.y - v1.y * v2.x) * (v3.x - v4.x) - (v1.x - v2.x) * (v3.x * v4.y - v3.y * v4.x)
val y = (v1.x * v2.y - v1.y * v2.x) * (v4.x - v3.x) - (v1.y - v2.y) * (v3.x * v4.y - v3.y * v4.x)
return Vec2(x / d, y / d)
}
private fun intersect(origin: Vec2, direction: Vec2, p1: Vec2, p2: Vec2): Boolean {
val normal = Vec2(direction.yx)
val first = dotProduct(normal, p1-origin)
val second = dotProduct(normal, p2-origin)
return (first.toBits() and 0x80000000.toInt() != second.toBits() and 0x80000000.toInt())
}
private fun dotProduct(v1: Vec2, v2: Vec2): Float {
return v1.x * v2.x + v1.y * v2.y
}
}

View File

@ -19,6 +19,7 @@ import de.bixilon.minosoft.config.key.KeyAction
import de.bixilon.minosoft.config.key.KeyCodes
import de.bixilon.minosoft.data.entities.EntityRotation
import de.bixilon.minosoft.data.entities.Location
import de.bixilon.minosoft.gui.rendering.chunk.Frustum
import de.bixilon.minosoft.gui.rendering.shader.Shader
import de.bixilon.minosoft.protocol.network.Connection
import de.bixilon.minosoft.protocol.packets.serverbound.play.PacketPlayerPositionAndRotationSending
@ -34,6 +35,7 @@ class Camera(
var fov: Float,
private val renderWindow: RenderWindow,
) {
lateinit var viewProjectionMatrix: Mat4
private var mouseSensitivity = Minosoft.getConfig().config.game.camera.moseSensitivity
private var movementSpeed = 7
var cameraPosition = Vec3(0.0f, 0.0f, 0.0f)
@ -46,8 +48,8 @@ class Camera(
private var lastPositionChange = 0L
private var currentPositionSent = false
private var cameraFront = Vec3(0.0f, 0.0f, -1.0f)
private var cameraRight = Vec3(0.0f, 0.0f, -1.0f)
var cameraFront = Vec3(0.0f, 0.0f, -1.0f)
var cameraRight = Vec3(0.0f, 0.0f, -1.0f)
private var cameraUp = Vec3(0.0f, 1.0f, 0.0f)
private var screenHeight = 0
@ -170,12 +172,14 @@ class Camera(
}
private fun recalculateViewProjectionMatrix() {
val matrix = calculateProjectionMatrix(screenWidth, screenHeight) * calculateViewMatrix()
for (shader in shaders) {
shader.use().setMat4("viewProjectionMatrix", calculateProjectionMatrix(screenWidth, screenHeight) * calculateViewMatrix())
shader.use().setMat4("viewProjectionMatrix", matrix)
}
// recalculate sky color for current biome
val blockPosition = Location(cameraPosition).toBlockPosition()
renderWindow.setSkyColor(connection.player.world.getChunk(blockPosition.getChunkLocation())?.biomeAccessor?.getBiome(blockPosition)?.skyColor ?: RenderConstants.DEFAULT_SKY_COLOR)
connection.renderer.renderWindow.worldRenderer.recalculateFrustum(Frustum(this))
}
private fun calculateProjectionMatrix(screenWidth: Int, screenHeight: Int): Mat4 {

View File

@ -21,7 +21,7 @@ import de.bixilon.minosoft.config.key.KeyBinding
import de.bixilon.minosoft.config.key.KeyCodes
import de.bixilon.minosoft.data.mappings.ResourceLocation
import de.bixilon.minosoft.data.text.RGBColor
import de.bixilon.minosoft.gui.rendering.chunk.ChunkRenderer
import de.bixilon.minosoft.gui.rendering.chunk.WorldRenderer
import de.bixilon.minosoft.gui.rendering.hud.HUDRenderer
import de.bixilon.minosoft.gui.rendering.hud.elements.RenderStats
import de.bixilon.minosoft.modding.event.EventInvokerCallback

View File

@ -1,15 +1,51 @@
package de.bixilon.minosoft.gui.rendering.chunk
import glm_.mat4x4.Mat4
import de.bixilon.minosoft.data.world.ChunkLocation
import de.bixilon.minosoft.gui.rendering.Camera
import de.bixilon.minosoft.protocol.network.Connection
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition
import glm_.vec3.Vec3
class Frustum(matrix: Mat4) {
val normals =
arrayOf(
Vec3(
matrix.a3 + matrix.a0,
matrix.b3 + matrix.b0,
matrix.c3 + matrix.c0).normalize(),
Vec3()
)
class Frustum(val camera: Camera) {
val normals: Array<Vec3>
init {
val realFront = Vec3(camera.cameraFront)
// realFront.y = 0f
realFront.normalize()
// val left = BlockModelElement.rotateVector(realFront, -glm.radians(camera.fov.toDouble() - 90), Axes.Y).normalize()
// val right = BlockModelElement.rotateVector(realFront, glm.radians(camera.fov.toDouble() - 90), Axes.Y).normalize()
// TODO: up, down, left, right, not working correctly
normals = arrayOf(
camera.cameraFront.normalize(),
// left.normalize(),
// right.normalize(),
)
}
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
if (dotProduct(normal, min - camera.cameraPosition) < 0f) {
return false // region lies outside of frustum
}
}
return true
}
private fun dotProduct(v1: Vec3, v2: Vec3): Float {
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
}
fun containsChunk(chunkLocation: ChunkLocation, connection: Connection): Boolean {
val from = Vec3(chunkLocation.x * ProtocolDefinition.SECTION_WIDTH_X, connection.player.world.dimension.minY, chunkLocation.z * ProtocolDefinition.SECTION_WIDTH_Z)
val to = from + Vec3(ProtocolDefinition.SECTION_WIDTH_X, connection.player.world.dimension.logicalHeight, ProtocolDefinition.SECTION_WIDTH_Z)
val frustum = Frustum(connection.renderer.renderWindow.camera)
return frustum.containsRegion(from, to)
}
}

View File

@ -1,5 +0,0 @@
package de.bixilon.minosoft.gui.rendering.chunk
class Plane {
}

View File

@ -19,7 +19,7 @@ import org.lwjgl.opengl.GL20.glEnableVertexAttribArray
import org.lwjgl.opengl.GL20.glVertexAttribPointer
import org.lwjgl.opengl.GL30.*
class ChunkSectionMesh(data: FloatArray) {
class WorldMesh(data: FloatArray) {
var vAO: Int = glGenVertexArrays()
var vBO: Int = glGenBuffers()
var trianglesCount: Int = data.size / FLOATS_PER_VERTEX

View File

@ -34,11 +34,16 @@ import java.util.concurrent.ConcurrentHashMap
class WorldRenderer(private val connection: Connection, private val world: World, val renderWindow: RenderWindow) : Renderer {
private lateinit var minecraftTextures: TextureArray
lateinit var chunkShader: Shader
private val chunkSectionsToDraw = ConcurrentHashMap<ChunkLocation, ConcurrentHashMap<Int, ChunkSectionMesh>>()
private val chunkSectionsToDraw = ConcurrentHashMap<ChunkLocation, ConcurrentHashMap<Int, WorldMesh>>()
private val visibleChunks: MutableSet<ChunkLocation> = mutableSetOf()
private lateinit var frustum: Frustum
private var currentTick = 0 // for animation usage
private var lastTickIncrementTime = 0L
private fun prepareChunk(chunkLocation: ChunkLocation, sectionHeight: Int, section: ChunkSection, chunk: Chunk): FloatArray {
if (frustum.containsChunk(chunkLocation, connection)) {
visibleChunks.add(chunkLocation)
}
val data: MutableList<Float> = mutableListOf()
val below = world.allChunks[chunkLocation]?.sections?.get(sectionHeight - 1)
@ -122,7 +127,9 @@ class WorldRenderer(private val connection: Connection, private val world: World
}
for ((chunkLocation, map) in chunkSectionsToDraw) {
if (chunkLocation.isVisibleFrom(connection.renderer.renderWindow.camera))
if (! visibleChunks.contains(chunkLocation)) {
continue
}
for ((_, mesh) in map) {
mesh.draw()
}
@ -161,7 +168,7 @@ class WorldRenderer(private val connection: Connection, private val world: World
chunkSectionsToDraw[chunkLocation] = sectionMap
}
renderWindow.renderQueue.add {
val newMesh = ChunkSectionMesh(data)
val newMesh = WorldMesh(data)
sectionMap[sectionHeight]?.unload()
sectionMap[sectionHeight] = newMesh
}
@ -191,7 +198,7 @@ class WorldRenderer(private val connection: Connection, private val world: World
}
}
fun prepareWorld(world: World) {
private fun prepareWorld(world: World) {
for ((chunkLocation, chunk) in world.allChunks) {
prepareChunk(chunkLocation, chunk)
}
@ -201,4 +208,14 @@ class WorldRenderer(private val connection: Connection, private val world: World
clearChunkCache()
prepareWorld(connection.player.world)
}
fun recalculateFrustum(frustum: Frustum) {
visibleChunks.clear()
this.frustum = frustum
for ((chunkLocation, _) in chunkSectionsToDraw.entries) {
if (frustum.containsChunk(chunkLocation, connection)) {
visibleChunks.add(chunkLocation)
}
}
}
}

View File

@ -91,7 +91,7 @@ class PacketRespawn : ClientboundPacket() {
connection.player.world.dimension = dimension
connection.player.isSpawnConfirmed = false
connection.player.gameMode = gameMode
connection.renderer.renderWindow.chunkRenderer.clearChunkCache()
connection.renderer.renderWindow.worldRenderer.clearChunkCache()
}
override fun log() {