rendering: WIP: chunk culling

This commit is contained in:
Lukas 2021-02-24 18:24:25 +01:00
parent c67965f40b
commit f30d8feb4b
14 changed files with 121 additions and 27 deletions

View File

@ -13,10 +13,11 @@
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
/**
* Chunk X and Z location (block position / 16, rounded down)
*/
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 {
@ -32,4 +33,76 @@ 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
}
} }

View File

@ -29,7 +29,7 @@ import glm_.vec3.Vec3
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
class Camera(private val connection: Connection, private var fov: Float) { class Camera(private val connection: Connection, var fov: Float) {
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)

View File

@ -1,6 +1,6 @@
/* /*
* Minosoft * Minosoft
* Copyright (C) 2020 Moritz Zwerger * Copyright (C) 2020 Moritz Zwerger, Lukas Eisenhauer
* *
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* *
@ -20,7 +20,7 @@ import de.bixilon.minosoft.config.key.KeyAction
import de.bixilon.minosoft.config.key.KeyBinding 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.ModIdentifier import de.bixilon.minosoft.data.mappings.ModIdentifier
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
@ -59,7 +59,7 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering)
private var mouseCatch = !StaticConfiguration.DEBUG_MODE private var mouseCatch = !StaticConfiguration.DEBUG_MODE
// all renderers // all renderers
val chunkRenderer: ChunkRenderer = ChunkRenderer(connection, connection.player.world, this) val worldRenderer: WorldRenderer = WorldRenderer(connection, connection.player.world, this)
val hudRenderer: HUDRenderer = HUDRenderer(connection, this) val hudRenderer: HUDRenderer = HUDRenderer(connection, this)
val renderQueue = ConcurrentLinkedQueue<Runnable>() val renderQueue = ConcurrentLinkedQueue<Runnable>()
@ -205,7 +205,7 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering)
// Make the OpenGL context current // Make the OpenGL context current
glfwMakeContextCurrent(windowId) glfwMakeContextCurrent(windowId)
// Enable v-sync // Enable v-sync
glfwSwapInterval(1) glfwSwapInterval(0)
// Make the window visible // Make the window visible
@ -216,10 +216,10 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
chunkRenderer.init() worldRenderer.init()
hudRenderer.init() hudRenderer.init()
chunkRenderer.postInit() worldRenderer.postInit()
hudRenderer.postInit() hudRenderer.postInit()
@ -275,7 +275,7 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering)
hudRenderer.screenChangeResizeCallback(screenWidth, screenHeight) hudRenderer.screenChangeResizeCallback(screenWidth, screenHeight)
camera.addShaders(chunkRenderer.chunkShader) camera.addShaders(worldRenderer.chunkShader)
camera.screenChangeResizeCallback(screenWidth, screenHeight) camera.screenChangeResizeCallback(screenWidth, screenHeight)
@ -304,7 +304,7 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering)
chunkRenderer.draw() worldRenderer.draw()
hudRenderer.draw() hudRenderer.draw()
renderStats.endDraw() renderStats.endDraw()
@ -351,7 +351,7 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering)
} }
if (this.renderingStatus == RenderingStates.PAUSED) { if (this.renderingStatus == RenderingStates.PAUSED) {
renderQueue.clear() renderQueue.clear()
chunkRenderer.refreshChunkCache() worldRenderer.refreshChunkCache()
} }
this.renderingStatus = renderingStatus this.renderingStatus = renderingStatus
} }

View File

@ -1,6 +1,6 @@
/* /*
* Minosoft * Minosoft
* Copyright (C) 2020 Moritz Zwerger * Copyright (C) 2020 Moritz Zwerger, Lukas Eisenhauer
* *
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* *
@ -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 WorldMesh(data: FloatArray) { class ChunkSectionMesh(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

View File

@ -0,0 +1,15 @@
package de.bixilon.minosoft.gui.rendering.chunk
import glm_.mat4x4.Mat4
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()
)
}

View File

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

View File

@ -1,6 +1,6 @@
/* /*
* Minosoft * Minosoft
* Copyright (C) 2020 Moritz Zwerger * Copyright (C) 2020 Moritz Zwerger, Lukas Eisenhauer
* *
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* *
@ -30,10 +30,10 @@ import org.lwjgl.opengl.GL11.glEnable
import org.lwjgl.opengl.GL13.glDisable import org.lwjgl.opengl.GL13.glDisable
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
class ChunkRenderer(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, WorldMesh>>() private val chunkSectionsToDraw = ConcurrentHashMap<ChunkLocation, ConcurrentHashMap<Int, ChunkSectionMesh>>()
private var currentTick = 0 // for animation usage private var currentTick = 0 // for animation usage
private var lastTickIncrementTime = 0L private var lastTickIncrementTime = 0L
@ -118,7 +118,8 @@ class ChunkRenderer(private val connection: Connection, private val world: World
} }
} }
for ((_, map) in chunkSectionsToDraw) { for ((chunkLocation, map) in chunkSectionsToDraw) {
if (chunkLocation.isVisibleFrom(connection.renderer.renderWindow.camera))
for ((_, mesh) in map) { for ((_, mesh) in map) {
mesh.draw() mesh.draw()
} }
@ -157,7 +158,7 @@ class ChunkRenderer(private val connection: Connection, private val world: World
chunkSectionsToDraw[chunkLocation] = sectionMap chunkSectionsToDraw[chunkLocation] = sectionMap
} }
renderWindow.renderQueue.add { renderWindow.renderQueue.add {
val newMesh = WorldMesh(data) val newMesh = ChunkSectionMesh(data)
sectionMap[sectionHeight]?.unload() sectionMap[sectionHeight]?.unload()
sectionMap[sectionHeight] = newMesh sectionMap[sectionHeight] = newMesh
} }

View File

@ -31,7 +31,7 @@ import glm_.vec4.Vec4
class ElementRenderer(element: BlockModelElement, rotation: Vec3, uvlock: Boolean, rescale: Boolean) { class ElementRenderer(element: BlockModelElement, rotation: Vec3, uvlock: Boolean, rescale: Boolean) {
private val fullFaceDirections: MutableSet<Directions> = mutableSetOf() private val fullFaceDirections: MutableSet<Directions> = mutableSetOf()
private val faces: MutableMap<Directions, BlockModelFace> = HashMap(element.faces) private val faces: MutableMap<Directions, BlockModelFace> = element.faces.toMutableMap()
private var positions: Array<Vec3> = element.positions.clone() private var positions: Array<Vec3> = element.positions.clone()
private val directionMapping: MutableMap<Directions, Directions> = mutableMapOf() private val directionMapping: MutableMap<Directions, Directions> = mutableMapOf()

View File

@ -63,7 +63,7 @@ public class PacketBlockChange extends ClientboundPacket {
section.setRawBlock(getPosition().getInChunkLocation().getInChunkSectionLocation(), getBlock()); section.setRawBlock(getPosition().getInChunkLocation().getInChunkSectionLocation(), getBlock());
} }
connection.getRenderer().getRenderWindow().getChunkRenderer().prepareChunkSection(getPosition().getChunkLocation(), sectionHeight, section); connection.getRenderer().getRenderWindow().getWorldRenderer().prepareChunkSection(getPosition().getChunkLocation(), sectionHeight, section);
} }
@Override @Override

View File

@ -87,7 +87,7 @@ public class PacketChunkBulk extends ClientboundPacket {
connection.getPlayer().getWorld().setChunks(getChunks()); connection.getPlayer().getWorld().setChunks(getChunks());
getChunks().forEach(((location, chunk) -> connection.getRenderer().getRenderWindow().getChunkRenderer().prepareChunk(location, chunk))); getChunks().forEach(((location, chunk) -> connection.getRenderer().getRenderWindow().getWorldRenderer().prepareChunk(location, chunk)));
} }
@Override @Override

View File

@ -124,7 +124,7 @@ public class PacketChunkData extends ClientboundPacket {
connection.getPlayer().getWorld().setChunk(getLocation(), getChunk()); connection.getPlayer().getWorld().setChunk(getLocation(), getChunk());
connection.getPlayer().getWorld().setBlockEntityData(getBlockEntities()); connection.getPlayer().getWorld().setBlockEntityData(getBlockEntities());
connection.getRenderer().getRenderWindow().getChunkRenderer().prepareChunk(this.location, this.chunk); connection.getRenderer().getRenderWindow().getWorldRenderer().prepareChunk(this.location, this.chunk);
} }
@Override @Override

View File

@ -110,7 +110,7 @@ public class PacketMultiBlockChange extends ClientboundPacket {
for (var sectionHeight : sectionHeights) { for (var sectionHeight : sectionHeights) {
ChunkSection section = chunk.getSectionOrCreate(sectionHeight); ChunkSection section = chunk.getSectionOrCreate(sectionHeight);
connection.getRenderer().getRenderWindow().getChunkRenderer().prepareChunkSection(getLocation(), sectionHeight, section); connection.getRenderer().getRenderWindow().getWorldRenderer().prepareChunkSection(getLocation(), sectionHeight, section);
} }
} }

View File

@ -91,7 +91,7 @@ public class PacketRespawn extends ClientboundPacket {
connection.getPlayer().setSpawnConfirmed(false); connection.getPlayer().setSpawnConfirmed(false);
connection.getPlayer().setGameMode(getGameMode()); connection.getPlayer().setGameMode(getGameMode());
connection.getRenderer().getRenderWindow().getChunkRenderer().clearChunkCache(); connection.getRenderer().getRenderWindow().getWorldRenderer().clearChunkCache();
} }
@Override @Override

View File

@ -31,7 +31,7 @@ public class PacketUnloadChunk extends ClientboundPacket {
@Override @Override
public void handle(Connection connection) { public void handle(Connection connection) {
connection.getPlayer().getWorld().unloadChunk(getLocation()); connection.getPlayer().getWorld().unloadChunk(getLocation());
connection.getRenderer().getRenderWindow().getChunkRenderer().unloadChunk(this.location); connection.getRenderer().getRenderWindow().getWorldRenderer().unloadChunk(this.location);
} }
@Override @Override