Liquid rendering in regions

This commit is contained in:
DBotThePony 2023-09-05 22:02:48 +07:00
parent 36d83b6a8e
commit f71b561ad7
Signed by: DBot
GPG Key ID: DCC23B5715498507
4 changed files with 99 additions and 218 deletions

View File

@ -26,75 +26,9 @@ import java.util.LinkedList
const val Z_LEVEL_BACKGROUND = 60000
const val Z_LEVEL_LIQUID = 10000
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos), Closeable {
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos){
val state: GLStateTracker get() = world.client.gl
private inner class TileLayerRenderer(private val view: ITileAccess, private val isBackground: Boolean) : AutoCloseable {
private val layers = TileLayerList()
val bakedMeshes = LinkedList<Pair<ConfiguredStaticMesh, Int>>()
var isDirty = false
fun bake() {
if (!isDirty) return
isDirty = false
if (state.isSameThread()) {
for (mesh in bakedMeshes) {
mesh.first.close()
}
}
bakedMeshes.clear()
layers.clear()
for (x in 0 until CHUNK_SIZE) {
for (y in 0 until CHUNK_SIZE) {
if (!world.inBounds(x, y)) continue
val tile = view.getTile(x, y) ?: continue
val material = tile.material
if (material != null) {
world.client.tileRenderers.getTileRenderer(material.materialName).tesselate(tile, view, layers, Vector2i(x, y), background = isBackground)
}
val modifier = tile.modifier
if (modifier != null) {
world.client.tileRenderers.getModifierRenderer(modifier.modName).tesselate(tile, view, layers, Vector2i(x, y), background = isBackground, isModifier = true)
}
}
}
}
fun upload() {
if (layers.isNotEmpty) {
for (mesh in bakedMeshes) {
mesh.first.close()
}
bakedMeshes.clear()
for ((baked, builder, zLevel) in layers.buildSortedLayerList()) {
bakedMeshes.add(ConfiguredStaticMesh(baked, builder) to zLevel)
}
layers.clear()
}
}
override fun close() {
for (mesh in bakedMeshes) {
mesh.first.close()
}
}
}
val posVector2d = Vector2d(x = pos.x * CHUNK_SIZEd, y = pos.y * CHUNK_SIZEd)
private val foregroundRenderer = TileLayerRenderer(worldForegroundView, isBackground = false)
private val backgroundRenderer = TileLayerRenderer(worldBackgroundView, isBackground = true)
override fun foregroundChanges(cell: Cell) {
super.foregroundChanges(cell)
@ -111,99 +45,11 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
}
}
fun bake() {
backgroundRenderer.bake()
foregroundRenderer.bake()
override fun liquidChanges(cell: Cell) {
super.liquidChanges(cell)
if (state.isSameThread())
upload()
}
fun upload() {
backgroundRenderer.upload()
foregroundRenderer.upload()
}
private val liquidTypes = ReferenceArraySet<LiquidDefinition>()
private var liquidTypesVer = 0
private fun getLiquidTypes(): Collection<LiquidDefinition> {
if (liquidTypesVer != liquidChangeset) {
liquidTypes.clear()
liquidTypesVer = liquidChangeset
for (x in 0 until CHUNK_SIZE) {
for (y in 0 until CHUNK_SIZE) {
getCell(x, y).liquid.def?.let { liquidTypes.add(it) }
}
}
}
return liquidTypes
}
inner class Renderer(val renderOrigin: Vector2f) {
fun addLayers(layers: LayeredRenderer) {
for ((baked, zLevel) in backgroundRenderer.bakedMeshes) {
layers.add(zLevel + Z_LEVEL_BACKGROUND) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
baked.renderStacked(it)
it.pop()
}
}
for ((baked, zLevel) in foregroundRenderer.bakedMeshes) {
layers.add(zLevel) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
baked.renderStacked(it)
it.pop()
}
}
for (renderer in entityRenderers.values) {
layers.add(renderer.layer) {
val relative = renderer.renderPos - posVector2d
it.push().last().translateWithMultiplication(renderOrigin.x + relative.x.toFloat(), renderOrigin.y + relative.y.toFloat())
renderer.render(it)
it.pop()
}
}
val types = getLiquidTypes()
if (types.isNotEmpty()) {
layers.add(Z_LEVEL_LIQUID) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
val program = state.programs.liquid
program.use()
program.transform = it.last()
val builder = program.builder
for (type in types) {
builder.builder.begin()
for (x in 0 until CHUNK_SIZE) {
for (y in 0 until CHUNK_SIZE) {
val state = getCell(x, y)
if (state.liquid.def === type) {
builder.builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state.liquid.level)
}
}
}
program.baselineColor = type.color
builder.upload()
builder.draw()
}
it.pop()
}
}
world.forEachRenderRegion(cell) {
it.liquidIsDirty = true
}
}
@ -226,13 +72,4 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
override fun onEntityRemoved(entity: Entity) {
entityRenderers.remove(entity)!!.close()
}
override fun close() {
backgroundRenderer.close()
foregroundRenderer.close()
for (renderer in entityRenderers.values) {
renderer.close()
}
}
}

View File

@ -3,9 +3,12 @@ package ru.dbotthepony.kstarbound.client
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import it.unimi.dsi.fastutil.longs.LongArraySet
import it.unimi.dsi.fastutil.objects.ReferenceArraySet
import ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
import ru.dbotthepony.kstarbound.client.render.Mesh
import ru.dbotthepony.kstarbound.client.render.TileLayerList
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
import ru.dbotthepony.kstarbound.world.*
@ -15,6 +18,7 @@ import ru.dbotthepony.kstarbound.world.api.TileView
import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kvector.api.IStruct2i
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2d
import ru.dbotthepony.kvector.vector.Vector2f
import ru.dbotthepony.kvector.vector.Vector2i
@ -95,7 +99,7 @@ class ClientWorld(
bakedMeshes.clear()
for ((baked, builder, zLevel) in layers.buildSortedLayerList()) {
for ((baked, builder, zLevel) in layers.layers()) {
bakedMeshes.add(ConfiguredStaticMesh(baked, builder) to zLevel)
}
@ -104,6 +108,9 @@ class ClientWorld(
}
}
private val liquidMesh = ArrayList<Pair<Mesh, RGBAColor>>()
var liquidIsDirty = true
val view = OffsetCellAccess(this@ClientWorld, x * renderRegionWidth, y * renderRegionHeight)
val background = Layer(TileView.Background(view), true)
@ -113,6 +120,37 @@ class ClientWorld(
background.bake()
foreground.bake()
if (liquidIsDirty) {
liquidIsDirty = false
liquidMesh.clear()
val liquidTypes = ReferenceArraySet<LiquidDefinition>()
for (x in 0 until renderRegionWidth) {
for (y in 0 until renderRegionHeight) {
view.getCell(x, y)?.liquid?.def?.let { liquidTypes.add(it) }
}
}
for (type in liquidTypes) {
val builder = client.gl.programs.liquid.builder.builder
builder.begin()
for (x in 0 until renderRegionWidth) {
for (y in 0 until renderRegionHeight) {
val state = view.getCell(x, y)
if (state?.liquid?.def === type) {
builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state!!.liquid.level)
}
}
}
liquidMesh.add(Mesh(client.gl, builder) to type.color)
}
}
for ((baked, zLevel) in background.bakedMeshes) {
layers.add(zLevel + Z_LEVEL_BACKGROUND) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
@ -129,50 +167,23 @@ class ClientWorld(
}
}
/*for (renderer in entityRenderers.values) {
layers.add(renderer.layer) {
val relative = renderer.renderPos - posVector2d
it.push().last().translateWithMultiplication(renderOrigin.x + relative.x.toFloat(), renderOrigin.y + relative.y.toFloat())
renderer.render(it)
it.pop()
}
}*/
/*val types = getLiquidTypes()
if (types.isNotEmpty()) {
if (liquidMesh.isNotEmpty()) {
layers.add(Z_LEVEL_LIQUID) {
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
val program = state.programs.liquid
val program = client.gl.programs.liquid
program.use()
program.transform = it.last()
val builder = program.builder
for (type in types) {
builder.builder.begin()
for (x in 0 until CHUNK_SIZE) {
for (y in 0 until CHUNK_SIZE) {
val state = getCell(x, y)
if (state.liquid.def === type) {
builder.builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state.liquid.level)
}
}
}
program.baselineColor = type.color
builder.upload()
builder.draw()
for ((mesh, color) in liquidMesh) {
program.baselineColor = color
mesh.render()
}
it.pop()
}
}*/
}
}
}

View File

@ -0,0 +1,43 @@
package ru.dbotthepony.kstarbound.client.render
import org.lwjgl.opengl.GL46
import ru.dbotthepony.kstarbound.client.gl.GLStateTracker
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder
class Mesh(state: GLStateTracker) {
constructor(state: GLStateTracker, builder: VertexBuilder) : this(state) {
load(builder, GL46.GL_STATIC_DRAW)
}
val vao = state.newVAO()
val vbo = state.newVBO()
val ebo = state.newEBO()
var indexCount = 0
private set
var indexType = 0
private set
fun load(builder: VertexBuilder, mode: Int = GL46.GL_DYNAMIC_DRAW) {
vao.bind()
vbo.bind()
ebo.bind()
builder.upload(vbo, ebo, mode)
builder.attributes.apply(vao, true)
indexCount = builder.indexCount
indexType = builder.indexType
vao.unbind()
vbo.unbind()
ebo.unbind()
}
fun render() {
vao.bind()
GL46.glDrawElements(GL46.GL_TRIANGLES, indexCount, indexType, 0L)
checkForGLError()
}
}

View File

@ -1,7 +1,7 @@
package ru.dbotthepony.kstarbound.client.render
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import org.apache.logging.log4j.LogManager
import org.lwjgl.opengl.GL46.*
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
@ -16,6 +16,7 @@ import ru.dbotthepony.kstarbound.world.api.ITileState
import ru.dbotthepony.kstarbound.world.api.TileColor
import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2i
import java.util.stream.Stream
import kotlin.collections.HashMap
data class TileLayer(
@ -25,7 +26,7 @@ data class TileLayer(
)
class TileLayerList {
private val layers = HashMap<ConfiguredShaderProgram<GLTileProgram>, Int2ObjectAVLTreeMap<TileLayer>>()
private val layers = HashMap<ConfiguredShaderProgram<GLTileProgram>, Int2ObjectOpenHashMap<TileLayer>>()
/**
* Получает геометрию слоя ([DynamicVertexBuilder]), который имеет программу для отрисовки [program] и располагается на [zLevel].
@ -33,24 +34,13 @@ class TileLayerList {
* Если такого слоя нет, вызывается [compute] и создаётся новый [TileLayer], затем возвращается результат [compute].
*/
fun computeIfAbsent(program: ConfiguredShaderProgram<GLTileProgram>, zLevel: Int, compute: () -> VertexBuilder): VertexBuilder {
return layers.computeIfAbsent(program) { Int2ObjectAVLTreeMap() }.computeIfAbsent(zLevel, Int2ObjectFunction {
return layers.computeIfAbsent(program) { Int2ObjectOpenHashMap() }.computeIfAbsent(zLevel, Int2ObjectFunction {
return@Int2ObjectFunction TileLayer(program, compute.invoke(), zLevel)
}).vertices
}
fun buildSortedLayerList(): List<TileLayer> {
val list = ArrayList<TileLayer>()
for (getList in layers.values) {
list.addAll(getList.values)
}
list.sortBy {
// унарный минус для инвентирования порядка (сначала большие, потом маленькие)
return@sortBy -it.zPos
}
return list
fun layers(): Stream<TileLayer> {
return layers.values.stream().flatMap { it.values.stream() }
}
fun clear() = layers.clear()