mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-16 19:05:02 -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
|
package de.bixilon.minosoft.data.world
|
||||||
|
|
||||||
import de.bixilon.minosoft.data.Directions
|
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) {
|
data class ChunkLocation(val x: Int, val z: Int) {
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "($x $z)"
|
return "($x $z)"
|
||||||
}
|
}
|
||||||
@ -33,76 +28,4 @@ data class ChunkLocation(val x: Int, val z: Int) {
|
|||||||
else -> throw IllegalArgumentException("Chunk location is just 2d")
|
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.config.key.KeyCodes
|
||||||
import de.bixilon.minosoft.data.entities.EntityRotation
|
import de.bixilon.minosoft.data.entities.EntityRotation
|
||||||
import de.bixilon.minosoft.data.entities.Location
|
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.gui.rendering.shader.Shader
|
||||||
import de.bixilon.minosoft.protocol.network.Connection
|
import de.bixilon.minosoft.protocol.network.Connection
|
||||||
import de.bixilon.minosoft.protocol.packets.serverbound.play.PacketPlayerPositionAndRotationSending
|
import de.bixilon.minosoft.protocol.packets.serverbound.play.PacketPlayerPositionAndRotationSending
|
||||||
@ -34,6 +35,7 @@ class Camera(
|
|||||||
var fov: Float,
|
var fov: Float,
|
||||||
private val renderWindow: RenderWindow,
|
private val renderWindow: RenderWindow,
|
||||||
) {
|
) {
|
||||||
|
lateinit var viewProjectionMatrix: Mat4
|
||||||
private var mouseSensitivity = Minosoft.getConfig().config.game.camera.moseSensitivity
|
private var mouseSensitivity = Minosoft.getConfig().config.game.camera.moseSensitivity
|
||||||
private var movementSpeed = 7
|
private var movementSpeed = 7
|
||||||
var cameraPosition = Vec3(0.0f, 0.0f, 0.0f)
|
var cameraPosition = Vec3(0.0f, 0.0f, 0.0f)
|
||||||
@ -46,8 +48,8 @@ class Camera(
|
|||||||
private var lastPositionChange = 0L
|
private var lastPositionChange = 0L
|
||||||
private var currentPositionSent = false
|
private var currentPositionSent = false
|
||||||
|
|
||||||
private var cameraFront = Vec3(0.0f, 0.0f, -1.0f)
|
var cameraFront = Vec3(0.0f, 0.0f, -1.0f)
|
||||||
private var cameraRight = 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 cameraUp = Vec3(0.0f, 1.0f, 0.0f)
|
||||||
|
|
||||||
private var screenHeight = 0
|
private var screenHeight = 0
|
||||||
@ -170,12 +172,14 @@ class Camera(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun recalculateViewProjectionMatrix() {
|
private fun recalculateViewProjectionMatrix() {
|
||||||
|
val matrix = calculateProjectionMatrix(screenWidth, screenHeight) * calculateViewMatrix()
|
||||||
for (shader in shaders) {
|
for (shader in shaders) {
|
||||||
shader.use().setMat4("viewProjectionMatrix", calculateProjectionMatrix(screenWidth, screenHeight) * calculateViewMatrix())
|
shader.use().setMat4("viewProjectionMatrix", matrix)
|
||||||
}
|
}
|
||||||
// recalculate sky color for current biome
|
// recalculate sky color for current biome
|
||||||
val blockPosition = Location(cameraPosition).toBlockPosition()
|
val blockPosition = Location(cameraPosition).toBlockPosition()
|
||||||
renderWindow.setSkyColor(connection.player.world.getChunk(blockPosition.getChunkLocation())?.biomeAccessor?.getBiome(blockPosition)?.skyColor ?: RenderConstants.DEFAULT_SKY_COLOR)
|
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 {
|
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.config.key.KeyCodes
|
||||||
import de.bixilon.minosoft.data.mappings.ResourceLocation
|
import de.bixilon.minosoft.data.mappings.ResourceLocation
|
||||||
import de.bixilon.minosoft.data.text.RGBColor
|
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.HUDRenderer
|
||||||
import de.bixilon.minosoft.gui.rendering.hud.elements.RenderStats
|
import de.bixilon.minosoft.gui.rendering.hud.elements.RenderStats
|
||||||
import de.bixilon.minosoft.modding.event.EventInvokerCallback
|
import de.bixilon.minosoft.modding.event.EventInvokerCallback
|
||||||
|
@ -1,15 +1,51 @@
|
|||||||
package de.bixilon.minosoft.gui.rendering.chunk
|
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
|
import glm_.vec3.Vec3
|
||||||
|
|
||||||
class Frustum(matrix: Mat4) {
|
class Frustum(val camera: Camera) {
|
||||||
val normals =
|
val normals: Array<Vec3>
|
||||||
arrayOf(
|
|
||||||
Vec3(
|
init {
|
||||||
matrix.a3 + matrix.a0,
|
val realFront = Vec3(camera.cameraFront)
|
||||||
matrix.b3 + matrix.b0,
|
// realFront.y = 0f
|
||||||
matrix.c3 + matrix.c0).normalize(),
|
realFront.normalize()
|
||||||
Vec3()
|
// 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.GL20.glVertexAttribPointer
|
||||||
import org.lwjgl.opengl.GL30.*
|
import org.lwjgl.opengl.GL30.*
|
||||||
|
|
||||||
class ChunkSectionMesh(data: FloatArray) {
|
class WorldMesh(data: FloatArray) {
|
||||||
var vAO: Int = glGenVertexArrays()
|
var vAO: Int = glGenVertexArrays()
|
||||||
var vBO: Int = glGenBuffers()
|
var vBO: Int = glGenBuffers()
|
||||||
var trianglesCount: Int = data.size / FLOATS_PER_VERTEX
|
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 {
|
class WorldRenderer(private val connection: Connection, private val world: World, val renderWindow: RenderWindow) : Renderer {
|
||||||
private lateinit var minecraftTextures: TextureArray
|
private lateinit var minecraftTextures: TextureArray
|
||||||
lateinit var chunkShader: Shader
|
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 currentTick = 0 // for animation usage
|
||||||
private var lastTickIncrementTime = 0L
|
private var lastTickIncrementTime = 0L
|
||||||
|
|
||||||
private fun prepareChunk(chunkLocation: ChunkLocation, sectionHeight: Int, section: ChunkSection, chunk: Chunk): FloatArray {
|
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 data: MutableList<Float> = mutableListOf()
|
||||||
|
|
||||||
val below = world.allChunks[chunkLocation]?.sections?.get(sectionHeight - 1)
|
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) {
|
for ((chunkLocation, map) in chunkSectionsToDraw) {
|
||||||
if (chunkLocation.isVisibleFrom(connection.renderer.renderWindow.camera))
|
if (! visibleChunks.contains(chunkLocation)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
for ((_, mesh) in map) {
|
for ((_, mesh) in map) {
|
||||||
mesh.draw()
|
mesh.draw()
|
||||||
}
|
}
|
||||||
@ -161,7 +168,7 @@ class WorldRenderer(private val connection: Connection, private val world: World
|
|||||||
chunkSectionsToDraw[chunkLocation] = sectionMap
|
chunkSectionsToDraw[chunkLocation] = sectionMap
|
||||||
}
|
}
|
||||||
renderWindow.renderQueue.add {
|
renderWindow.renderQueue.add {
|
||||||
val newMesh = ChunkSectionMesh(data)
|
val newMesh = WorldMesh(data)
|
||||||
sectionMap[sectionHeight]?.unload()
|
sectionMap[sectionHeight]?.unload()
|
||||||
sectionMap[sectionHeight] = newMesh
|
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) {
|
for ((chunkLocation, chunk) in world.allChunks) {
|
||||||
prepareChunk(chunkLocation, chunk)
|
prepareChunk(chunkLocation, chunk)
|
||||||
}
|
}
|
||||||
@ -201,4 +208,14 @@ class WorldRenderer(private val connection: Connection, private val world: World
|
|||||||
clearChunkCache()
|
clearChunkCache()
|
||||||
prepareWorld(connection.player.world)
|
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.world.dimension = dimension
|
||||||
connection.player.isSpawnConfirmed = false
|
connection.player.isSpawnConfirmed = false
|
||||||
connection.player.gameMode = gameMode
|
connection.player.gameMode = gameMode
|
||||||
connection.renderer.renderWindow.chunkRenderer.clearChunkCache()
|
connection.renderer.renderWindow.worldRenderer.clearChunkCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun log() {
|
override fun log() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user