From f30d8feb4b777576017998c473365cac82dfd664 Mon Sep 17 00:00:00 2001 From: Lukas Date: Wed, 24 Feb 2021 18:24:25 +0100 Subject: [PATCH] rendering: WIP: chunk culling --- .../minosoft/data/world/ChunkLocation.kt | 79 ++++++++++++++++++- .../bixilon/minosoft/gui/rendering/Camera.kt | 2 +- .../minosoft/gui/rendering/RenderWindow.kt | 18 ++--- .../{WorldMesh.kt => ChunkSectionMesh.kt} | 4 +- .../minosoft/gui/rendering/chunk/Frustum.kt | 15 ++++ .../minosoft/gui/rendering/chunk/Plane.kt | 5 ++ .../{ChunkRenderer.kt => WorldRenderer.kt} | 11 +-- .../models/renderable/ElementRenderer.kt | 2 +- .../clientbound/play/PacketBlockChange.java | 2 +- .../clientbound/play/PacketChunkBulk.java | 2 +- .../clientbound/play/PacketChunkData.java | 2 +- .../play/PacketMultiBlockChange.java | 2 +- .../clientbound/play/PacketRespawn.java | 2 +- .../clientbound/play/PacketUnloadChunk.java | 2 +- 14 files changed, 121 insertions(+), 27 deletions(-) rename src/main/java/de/bixilon/minosoft/gui/rendering/chunk/{WorldMesh.kt => ChunkSectionMesh.kt} (96%) create mode 100644 src/main/java/de/bixilon/minosoft/gui/rendering/chunk/Frustum.kt create mode 100644 src/main/java/de/bixilon/minosoft/gui/rendering/chunk/Plane.kt rename src/main/java/de/bixilon/minosoft/gui/rendering/chunk/{ChunkRenderer.kt => WorldRenderer.kt} (96%) diff --git a/src/main/java/de/bixilon/minosoft/data/world/ChunkLocation.kt b/src/main/java/de/bixilon/minosoft/data/world/ChunkLocation.kt index 752ea1c78..661555287 100644 --- a/src/main/java/de/bixilon/minosoft/data/world/ChunkLocation.kt +++ b/src/main/java/de/bixilon/minosoft/data/world/ChunkLocation.kt @@ -13,10 +13,11 @@ 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 -/** - * Chunk X and Z location (block position / 16, rounded down) - */ data class ChunkLocation(val x: Int, val z: Int) { 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") } } + + 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 + } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/Camera.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/Camera.kt index 0799dda3b..6737dc891 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/Camera.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/Camera.kt @@ -29,7 +29,7 @@ import glm_.vec3.Vec3 import kotlin.math.cos 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 movementSpeed = 7 var cameraPosition = Vec3(0.0f, 0.0f, 0.0f) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.kt index 6545be5fb..208aa95b4 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/RenderWindow.kt @@ -1,6 +1,6 @@ /* * 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. * @@ -20,7 +20,7 @@ import de.bixilon.minosoft.config.key.KeyAction import de.bixilon.minosoft.config.key.KeyBinding import de.bixilon.minosoft.config.key.KeyCodes 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.elements.RenderStats 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 // 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 renderQueue = ConcurrentLinkedQueue() @@ -205,7 +205,7 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering) // Make the OpenGL context current glfwMakeContextCurrent(windowId) // Enable v-sync - glfwSwapInterval(1) + glfwSwapInterval(0) // 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) - chunkRenderer.init() + worldRenderer.init() hudRenderer.init() - chunkRenderer.postInit() + worldRenderer.postInit() hudRenderer.postInit() @@ -275,7 +275,7 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering) hudRenderer.screenChangeResizeCallback(screenWidth, screenHeight) - camera.addShaders(chunkRenderer.chunkShader) + camera.addShaders(worldRenderer.chunkShader) camera.screenChangeResizeCallback(screenWidth, screenHeight) @@ -304,7 +304,7 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering) - chunkRenderer.draw() + worldRenderer.draw() hudRenderer.draw() renderStats.endDraw() @@ -351,7 +351,7 @@ class RenderWindow(private val connection: Connection, val rendering: Rendering) } if (this.renderingStatus == RenderingStates.PAUSED) { renderQueue.clear() - chunkRenderer.refreshChunkCache() + worldRenderer.refreshChunkCache() } this.renderingStatus = renderingStatus } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/WorldMesh.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkSectionMesh.kt similarity index 96% rename from src/main/java/de/bixilon/minosoft/gui/rendering/chunk/WorldMesh.kt rename to src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkSectionMesh.kt index 018481fde..c1df83cbc 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/WorldMesh.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkSectionMesh.kt @@ -1,6 +1,6 @@ /* * 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. * @@ -19,7 +19,7 @@ import org.lwjgl.opengl.GL20.glEnableVertexAttribArray import org.lwjgl.opengl.GL20.glVertexAttribPointer import org.lwjgl.opengl.GL30.* -class WorldMesh(data: FloatArray) { +class ChunkSectionMesh(data: FloatArray) { var vAO: Int = glGenVertexArrays() var vBO: Int = glGenBuffers() var trianglesCount: Int = data.size / FLOATS_PER_VERTEX diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/Frustum.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/Frustum.kt new file mode 100644 index 000000000..aaa229da5 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/Frustum.kt @@ -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() + ) +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/Plane.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/Plane.kt new file mode 100644 index 000000000..3af5fbd96 --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/Plane.kt @@ -0,0 +1,5 @@ +package de.bixilon.minosoft.gui.rendering.chunk + +class Plane { + +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/WorldRenderer.kt similarity index 96% rename from src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt rename to src/main/java/de/bixilon/minosoft/gui/rendering/chunk/WorldRenderer.kt index d1e325914..1d0f20b41 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/ChunkRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/WorldRenderer.kt @@ -1,6 +1,6 @@ /* * 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. * @@ -30,10 +30,10 @@ import org.lwjgl.opengl.GL11.glEnable import org.lwjgl.opengl.GL13.glDisable 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 lateinit var chunkShader: Shader - private val chunkSectionsToDraw = ConcurrentHashMap>() + private val chunkSectionsToDraw = ConcurrentHashMap>() private var currentTick = 0 // for animation usage 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) { mesh.draw() } @@ -157,7 +158,7 @@ class ChunkRenderer(private val connection: Connection, private val world: World chunkSectionsToDraw[chunkLocation] = sectionMap } renderWindow.renderQueue.add { - val newMesh = WorldMesh(data) + val newMesh = ChunkSectionMesh(data) sectionMap[sectionHeight]?.unload() sectionMap[sectionHeight] = newMesh } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/ElementRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/ElementRenderer.kt index aba1ce8dd..1b6355132 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/ElementRenderer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/chunk/models/renderable/ElementRenderer.kt @@ -31,7 +31,7 @@ import glm_.vec4.Vec4 class ElementRenderer(element: BlockModelElement, rotation: Vec3, uvlock: Boolean, rescale: Boolean) { private val fullFaceDirections: MutableSet = mutableSetOf() - private val faces: MutableMap = HashMap(element.faces) + private val faces: MutableMap = element.faces.toMutableMap() private var positions: Array = element.positions.clone() private val directionMapping: MutableMap = mutableMapOf() diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketBlockChange.java b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketBlockChange.java index e11b10eb4..ff4e8dbe7 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketBlockChange.java +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketBlockChange.java @@ -63,7 +63,7 @@ public class PacketBlockChange extends ClientboundPacket { 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 diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkBulk.java b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkBulk.java index 4773db577..9202afbb4 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkBulk.java +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkBulk.java @@ -87,7 +87,7 @@ public class PacketChunkBulk extends ClientboundPacket { 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 diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkData.java b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkData.java index 046299329..bb4534a16 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkData.java +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketChunkData.java @@ -124,7 +124,7 @@ public class PacketChunkData extends ClientboundPacket { connection.getPlayer().getWorld().setChunk(getLocation(), getChunk()); connection.getPlayer().getWorld().setBlockEntityData(getBlockEntities()); - connection.getRenderer().getRenderWindow().getChunkRenderer().prepareChunk(this.location, this.chunk); + connection.getRenderer().getRenderWindow().getWorldRenderer().prepareChunk(this.location, this.chunk); } @Override diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketMultiBlockChange.java b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketMultiBlockChange.java index 69842e3e4..3aecc6e0d 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketMultiBlockChange.java +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketMultiBlockChange.java @@ -110,7 +110,7 @@ public class PacketMultiBlockChange extends ClientboundPacket { for (var sectionHeight : sectionHeights) { ChunkSection section = chunk.getSectionOrCreate(sectionHeight); - connection.getRenderer().getRenderWindow().getChunkRenderer().prepareChunkSection(getLocation(), sectionHeight, section); + connection.getRenderer().getRenderWindow().getWorldRenderer().prepareChunkSection(getLocation(), sectionHeight, section); } } diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketRespawn.java b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketRespawn.java index 0dd880749..61f3ba05d 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketRespawn.java +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketRespawn.java @@ -91,7 +91,7 @@ public class PacketRespawn extends ClientboundPacket { connection.getPlayer().setSpawnConfirmed(false); connection.getPlayer().setGameMode(getGameMode()); - connection.getRenderer().getRenderWindow().getChunkRenderer().clearChunkCache(); + connection.getRenderer().getRenderWindow().getWorldRenderer().clearChunkCache(); } @Override diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketUnloadChunk.java b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketUnloadChunk.java index 87d6fb42c..7c2aef26e 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketUnloadChunk.java +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/clientbound/play/PacketUnloadChunk.java @@ -31,7 +31,7 @@ public class PacketUnloadChunk extends ClientboundPacket { @Override public void handle(Connection connection) { connection.getPlayer().getWorld().unloadChunk(getLocation()); - connection.getRenderer().getRenderWindow().getChunkRenderer().unloadChunk(this.location); + connection.getRenderer().getRenderWindow().getWorldRenderer().unloadChunk(this.location); } @Override