mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-16 02:45:13 -04:00
rendering: chunk culling behind the player
This commit is contained in:
parent
2ea14c0802
commit
54df8f73a0
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
package de.bixilon.minosoft.gui.rendering.chunk
|
||||
|
||||
class Plane {
|
||||
|
||||
}
|
@ -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
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user